zoukankan      html  css  js  c++  java
  • Java IO学习笔记四:Socket基础

    作者:Grey

    原文地址:Java IO学习笔记四:Socket基础

    准备两个Linux实例(安装好jdk1.8),我准备的两个实例的ip地址分别为:

    io1实例:192.168.205.138
    io2实例:192.168.205.149

    安装必要工具:

    yum install -y strace lsof  pmap tcpdump
    

    准备服务端代码

    import java.io.*;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * BIO Socket Server
     */
    public class SocketServerBIOTest {
        private static final int PORT = 9090;
        private static final int BACK_LOG = 2;
    
        public static void main(String[] args) {
            ServerSocket server = null;
            try {
                server = new ServerSocket();
                server.bind(new InetSocketAddress(PORT), BACK_LOG);
                System.out.println("server started , port : " + PORT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                // 接受客户端连接
                while (true) {
                    // 先阻塞,这样客户端暂时无法连接进来
                    System.in.read();
    
                    // 这个方法也是阻塞的,如果没有客户端连接进来,会一直阻塞在这里,除非设置了超时时间
                    Socket client = server.accept();
    
                    System.out.println("client " + client.getPort() + " connected!!!");
                    // 客户端连接进来后,开辟一个新的线程去接收并处理
                    new Thread(() -> {
                        try {
                            InputStream inputStream = client.getInputStream();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                            char[] data = new char[1024];
                            while (true) {
                                int num = reader.read(data);
                                if (num > 0) {
                                    System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));
                                } else if (num == 0) {
                                    System.out.println("client read nothing!");
                                    continue;
                                } else {
                                    System.out.println("client read -1...");
                                    System.in.read();
                                    client.close();
                                    break;
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }).start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    

    死循环中,由于第一句:

    System.in.read();
    

    导致

     Socket client = server.accept();
    

    无法执行,即:服务端此时是无法接收客户端的。

    准备客户端代码:

    import java.io.*;
    import java.net.Socket;
    
    /**
     * Socket Client
     */
    public class SocketClientTest {
    
        public static void main(String[] args) {
    
            try {
                Socket client = new Socket("192.168.205.138", 9090);
                OutputStream out = client.getOutputStream();
                InputStream in = System.in;
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                while (true) {
                    String line = reader.readLine();
                    if (line != null) {
                        byte[] bb = line.getBytes();
                        for (byte b : bb) {
                            out.write(b);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    在实例io1中启动服务端代码:

    javac SocketServerBIOTest.java && java SocketServerBIOTest
    

    在io1中开启抓包工具:

    tcpdump -nn -i ens33 port 9090
    

    在io2中执行客户端代码:

    javac SocketClientTest.java && java SocketClientTest
    

    由于我们在服务端加了一段:

    System.in.read()
    

    方法,导致服务端其实没办法执行accept()

    但是在服务端查看抓包信息:

    [root@io socket]# tcpdump -nn -i ens33 port 9090
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
    15:19:56.021974 IP 192.168.205.149.56944 > 192.168.205.138.9090: Flags [S], seq 391962776, win 29200, options [mss 1460,sackOK,TS val 16515471 ecr 0,nop,wscale 7], length 0
    15:19:56.022035 IP 192.168.205.138.9090 > 192.168.205.149.56944: Flags [S.], seq 2744580571, ack 391962777, win 28960, options [mss 1460,sackOK,TS val 16517545 ecr 16515471,nop,wscale 7], length 0
    15:19:56.022349 IP 192.168.205.149.56944 > 192.168.205.138.9090: Flags [.], ack 1, win 229, options [nop,nop,TS val 16515472 ecr 16517545], length 0
    
    

    内核已经为客户端和服务端建立了连接并完成了三次握手,在服务端使用netstat查看:

    [root@io socket]# netstat -ntap
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    ...
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:56944   ESTABLISHED -          
    ...
    

    显示已经建立了连接,只不过还没有分配:PID/Program name。

    在客户端输入一些信息,

    [root@io2 socket]# javac SocketClientTest.java && java SocketClientTest
    asdfasdfasdfasf
    

    在服务端再次执行netstat

    [root@io socket]# netstat -ntap
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    ...
    tcp6      15      0 192.168.205.138:9090    192.168.205.149:56944   ESTABLISHED -        
    ...
    

    也显示出接收到了数据。

    服务端再次开启抓包:

    tcpdump -nn -i ens33 port 9090
    

    再次在客户端输入一些数据:

    [root@io2 socket]# javac SocketClientTest.java && java SocketClientTest
    asdfasdfasdfasf
    dfasdfasdfasdas
    

    可以看到抓包信息:

    [root@io ~]# tcpdump -nn -i ens33 port 9090
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
    15:26:48.632564 IP 192.168.205.149.56944 > 192.168.205.138.9090: Flags [P.], seq 391962792:391962793, ack 2744580572, win 229, options [nop,nop,TS val 16928082 ecr 16757410], length 1
    15:26:48.632609 IP 192.168.205.138.9090 > 192.168.205.149.56944: Flags [.], ack 1, win 227, options [nop,nop,TS val 16930156 ecr 16928082], length 0
    15:26:48.632791 IP 192.168.205.149.56944 > 192.168.205.138.9090: Flags [P.], seq 1:15, ack 1, win 229, options [nop,nop,TS val 16928082 ecr 16930156], length 14
    15:26:48.632825 IP 192.168.205.138.9090 > 192.168.205.149.56944: Flags [.], ack 15, win 227, options [nop,nop,TS val 16930156 ecr 16928082], length 0
    

    以上实验主要说明了一个问题:

    虽然在应用层面,服务端没有调用accept() 去接收客户端,但是,内核其实已经完成了客户端和服务端的三次握手以及数据传输。

    接下来,我们触发服务端:

    Socket client = server.accept();
    

    这段逻辑

    服务端可以显示客户端的数据

    [root@io socket]# java SocketServerBIOTest
    server started , port : 9090
    
    client 56944 connected!!!
    client read some data is :30 val :asdfasdfasdfasfdfasdfasdfasdas
    
    

    服务端执行nestat -ntap,

    [root@io ~]# netstat -ntap
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    ...
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:56944   ESTABLISHED 2266/java 
    ...
    

    可以看到已经分配了一个PID,通过:

    lsof -p 2266
    

    查看这个java进程相关的文件描述符

    [root@io ~]# lsof -p 2266
    COMMAND  PID USER   FD   TYPE             DEVICE  SIZE/OFF      NODE NAME
    ...
    java    2266 root    6u  IPv6              26479       0t0       TCP 192.168.205.138:websm->192.168.205.149:56944 (ESTABLISHED)
    ...
    

    文件描述符6u就对应了服务端和客户端连接的一个Socket。

    在创建服务端的时候,我们指定了一个参数:backlog=2

    // 指定了BACK_LOG = 2
    server.bind(new InetSocketAddress(PORT), BACK_LOG);
    

    backlog的解释是

    requested maximum length of the queue of incoming connections.

    重新启动我们的服务端

    [root@io socket]# javac SocketServerBIOTest.java && java SocketServerBIOTest
    server started , port : 9090
    
    

    启动三个客户端连接这个服务端, 然后再服务端执行netstat -ntap

    [root@io socket]# netstat -ntap
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    ...
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:50532   ESTABLISHED -                   
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:50536   ESTABLISHED -                   
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:50534   ESTABLISHED -    
    ...
    

    可以看到,服务端创建了三个连接,但是,当我们再启动一个客户端连接进来的时候,新增的这个连接的状态为:SYN_RECV

    [root@io socket]# netstat -ntap
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    ....
    tcp        0      0 192.168.205.138:9090    192.168.205.149:50538   SYN_RECV    -                  
    ....
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:50532   ESTABLISHED -                   
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:50536   ESTABLISHED -                   
    tcp6       0      0 192.168.205.138:9090    192.168.205.149:50534   ESTABLISHED -               
    

    SYN_RECV表示:

    服务端收到报文后,向客户端发送确认的报文,服务端进入SYNC_RECV状态,但是因为设置了backlog=2
    超过了服务端设置的最大连接数,服务端就不再继续向客户端发送报文。

    在具体的编程中,服务端和客户端都有很多配置的参数。

    详见:

    ServerSocket

    socketOpt

    java socket 参数

    关于MTU和MSS: MTU TCP-MSS详解

    源码:Github

  • 相关阅读:
    单机部署redis主从备份
    【 D3.js 进阶系列 — 2.1 】 力学图的事件 + 顶点的固定
    java生成二维码(带logo)
    求一个序列的全部排列
    【C/C++学院】(24)Oracle数据库编程--管理oracle
    php学习之道:mysql SELECT FOUND_ROWS()与COUNT(*)使用方法差别
    用"池"来提升对象的复用
    迷茫的一代人
    VMWARE安装MAC时无法移动鼠标?
    小心两个共享库共用同一个静态库
  • 原文地址:https://www.cnblogs.com/greyzeng/p/14879244.html
Copyright © 2011-2022 走看看