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

  • 相关阅读:
    Android(java)学习笔记68:使用proguard混淆android代码
    SGU 194 Reactor Cooling
    关于流量有上下界的网络流问题的求解
    关于最小割的求解方法
    HDU 5311 Hidden String
    POJ 3548 Restoring the digits
    POJ 2062 HDU 1528 ZOJ 2223 Card Game Cheater
    ZOJ 1967 POJ 2570 Fiber Network
    HDU 1969 Pie
    HDU 1956 POJ 1637 Sightseeing tour
  • 原文地址:https://www.cnblogs.com/greyzeng/p/14879244.html
Copyright © 2011-2022 走看看