tcp连接过程探究

为了研究tcp连接建立过程,用java编写了一个tcp客户端(client),一个tcp服务端(Server)。程序的逻辑很简单,client连接server,向server发送消息,server收到消息作出响应,如果发送的消息是”close”字符串,双方关闭连接。

用抓包工具wireshark进行抓包,分析整个通信过程。

server

public class TcpServer {
    private final int SERVER_PORT = 8001;
    private ServerSocket servSocket;
    private Socket socket;
    public static void main(String[] args) throws Exception {
        TcpServer server = new TcpServer();
        server.acceptRequest();
    }
    /**
     * 接收客户端请求
     *
     * @throws Exception
     */
    public void acceptRequest() throws Exception {
        servSocket = new ServerSocket(SERVER_PORT);
        while (true) {
            System.out.println("准备接受请求....");
            socket = servSocket.accept();
            System.out.println("接收来自" + socket.getInetAddress().getHostName() + "的请求");
            while (true) {
                try {
                    InputStream in = socket.getInputStream();
                    int size;
                    byte[] msg = new byte[1024];
                    ByteArrayOutputStream bout = new ByteArrayOutputStream();
                    size = in.read(msg);
                    if (size == -1) {
                        System.out.println("关闭连接:" + socket.getInetAddress());
                        socket.close();
                        break;
                    }
                    bout.write(msg, 0, size);
                    String content = bout.toString();
                    bout.close();
                    System.out.println("接收到的报文为:");
                    System.out.println(content);
                    String response = "recived message:" + content;
                    socket.getOutputStream().write(response.getBytes("utf-8"));
                    socket.getOutputStream().flush();
                    System.out.println("报文已发送,等待接收");
                } catch (Exception ex) {
                    ex.printStackTrace();
                    socket.close();
                    break;
                }
            }
        }
    }
}


client

public class TcpClient {
    public static void main(String[] cmd) throws Exception {
        waiteForUserInput("回车建立连接");
        Socket socket = new Socket("localhost", 8001);
        OutputStream os = socket.getOutputStream();
        InputStream is = socket.getInputStream();
        while (true) {
            String content = waiteForUserInput("连接建立,回车发送消息(close关闭)");
            os.write(content.getBytes());
            os.flush();
            ByteArrayOutputStream fos = new ByteArrayOutputStream();
            copyStream(is, fos);
            System.out.println("收到响应:" + fos.toString("UTF-8"));
            if ("close".equals(content)) {
                socket.shutdownInput();
                socket.shutdownOutput();
                socket.close();
                break;
            }
        }
        System.out.println("bye bye");
    }
    private static String waiteForUserInput(String tip) {
        System.out.println(tip);
        String content = "";
        Scanner scanner = new Scanner(System.in);
        if (scanner.hasNextLine()) {
            content = scanner.nextLine();
        }
        return content;
    }
    /**
     * 流复制
     *
     * @param from
     * @param to
     */
    public static void copyStream(InputStream from, OutputStream to) {
        byte[] buf = new byte[1024];
        int size = 0;
        do {
            try {
                size = from.read(buf);
                if (size > 0) {
                    to.write(buf, 0, size);
                    if (size < 1024) {
                        break;
                    }
                } else {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } while (true);
    }
}

首先启动server端,接受请求,再启动client端,配置好Wireshark后,在client端按回车开始建立连接。

根据程序监听的端口配置好过滤条件,如监听的是8001端口,过滤条件可以设置为tcp port 8001

如果你是在自己本地PC启动client和server,由于程序默认监听地址绑定localhost,localhost的流量都是通过lo0网卡进行流转,因此wireshark在配置的时候网卡需要选择lo0回传网卡。

连接建立

当client连接server端时,也就是client执行代码Socket socket = new Socket("localhost", 8001),报文如下:

前面三个是tcp建立连接三次握手报文,第四个是连接建立后client和server重新调整窗口大小,我们主要分析前面三个报文。

  • client向server发送一个SYN包请求建立连接,SYN报文就是将报文SYN标志位设置为1
  • server向client发送ack包表示确认收到上面发送的syn,并设置SYN位,同意建立连接
  • client向server发送ack包表示确认收到,双方连接正式建立

发送数据

我们通过client向server发送一个hello的消息,该消息未超过MSS(Maximum Segment Size)即最大报文段长度,因此不会被拆分。

报文5client向server发送数据

在该报文里可以看到三个信息

  • ack标志位被设置,表示这是一个确认包
  • psh标志位被设置,表示发送方已经发送完所有数据或者已经达到最大缓冲区,期望接收方尽快将数据交付给应用程序(即read返回),一般用于数据传输时设置。
  • 可以在报文里看到数据hello

报文6server响应ack报文,表示已收到数据

报文7server向client发送数据,同样设置ack标志位和psh标志位

报文8client向server发送ack包,确认收到数据

至此,整个数据传输过程结束,简单来说就是这么一个过程

client:server,我给你发了一堆数据

server:client,我确认收到了

server:client,我也给你发送了一堆数据

client:server,我也确认收到了

连接关闭

client输入close,表明会话结束,双方进行四次挥手关闭连接,结束会话。

报文13client向server发送FIN包,请求关闭连接

报文14 server向client回复ack报文,表示收到client的报文

报文15 server向client发送FIN包,请求关闭连接

报文16 client向server回复ack报文,表示收到server的报文

至此连接断开,会话结束,简单概述就是以下过程

client:我要求请求断开连接

server:好的,我知道了。

server:我这边可以断开连接了

client:好的,我知道了,那我们就断开了

至于为什么要四次挥手,因为tcp连接是双工的,也就是两边互相连接,互相发送数据,一方请求断开连接,另外一方的数据可能还未发送完,因此client发送FIN后,server依然可以向client发送数据,但client无法向server发送数据了,server的FIN被client确认后,双方就完全断开连接。这个跟男女朋友一样

男方:分手吧

女方:好的,让我考虑下

女方:我想好了,分手吧

男方:好的,再见

附件

wireshark文件下载

,
Trackback

no comment untill now

Add your comment now