您当前的位置: 首页 > 

恐龙弟旺仔

暂无认证

  • 1浏览

    0关注

    282博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

TCP协议解析与实战

恐龙弟旺仔 发布时间:2022-05-22 12:36:00 ,浏览量:1

前言:

之前我们介绍过IP协议和ICMP协议,这两个协议在OSI模型中都位于网络层,主要负责数据包的传递。

但是IP协议只负责传输,如果在包传输过程中出现分片乱序、包丢失等问题,IP协议是不负责解决的。

所以针对这种异常情况的解决,就需要更上层协议来解决了,这个就是我们本次介绍的TCP协议。

1.TCP协议概述 1.1 什么是TCP协议?

TCP(Transmission Control Protocol)传输控制协议。

该协议是一种面向连接的、可靠的、基于IP的传输层协议。

1)传输中需要控制什么呢?

就是前言中,我们提到的包顺序、丢失等问题的控制,还有一些更高级的功能,比如:控制数据包传输的速度。

2)如何理解面向连接?

TCP协议在传输数据包之前,需要客户端先与服务端创建一个连接。

如何创建呢?那么就是通过三次握手的方式来创建。

而为了保证客户端与服务端在完成通信后正常的结束连接,会通过四次挥手的方式来关闭连接。

3)如何理解可靠?

可靠是相对UDP协议而言的,UDP协议使用和实现都比较简单,但是其可靠性较差,一旦数据包发出,我们也无法知道对端是否接收到。

针对这种情况,TCP协议要求每传输出去一个数据包,都要求对端返回一个确认包。这样就能确认对方已经收到该包。

1.2 OSI模型中的TCP协议

还是来通过一张OSI模型图来展示下TCP协议所处的位置(图片来自 百度安全验证):

2.TCP协议包

TCP协议为了实现面向连接、可靠的连接的承诺,所以其功能点相对而言比较多。

本文我们重点关注下TCP协议头内容和其是如何面向连接的这两方面知识点。

2.1 TCP协议头

TCP协议头部具体如下(图片来自: TCP头部详解_却道天凉_好个秋的博客-CSDN博客_tcp头部)

 

固定头部为20字节(不带TCP选项),如果带上选项,TCP头部可达60字节。

常见选项包括:最大段大小、时间戳、窗口缩放、选择性ACK等。

关于TCP头部各字段含义如下:

1.源端口和目的端口,各占2个字节,分别写入源端口和目的端口;
2.序号,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始;
3.确认号,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B收到了A发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明B正确的收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701;
4.数据偏移,占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;
5.保留,占6位,保留今后使用,但目前应都位0;
6.状态位
6.1 紧急URG,当URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;
6.2 确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;
6.3 推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1;
6.4 复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;
6.5 同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
6.6 终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
7.窗口,占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;
8.检验和,占2字节,校验首部和数据这两部分;
9.紧急指针,占2字节,指出本报文段中的紧急数据的字节数;
10.选项,长度可变,定义一些其他的可选的参数。
3.wireshark抓包TCP协议三次握手

下面我们通过一个实战来模拟下TCP协议的整个创建连接和关闭连接过程。

具体模拟场景如下:

启动一个ServerSocket进程,端口为9999,用于接收客户端连接和请求,如果接收到客户端的请求信息为bye,那么服务端主动发起close socket操作,关闭当前连接。

客户端启动一个命令行即可,使用telnet命令来与服务端创建连接。

3.1 服务端ServerSocket相关程序

public class BIOServerSocket {

    private String address;
    private int port;

    public BIOServerSocket(String address, int port) {
        this.address = address;
        this.port = port;
    }

    public void startServer() {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(address, port));
            System.out.println("bio server start...");

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("client connect...");

                // 写入 thread
                ServerWriteThread serverWriteThread = new ServerWriteThread(clientSocket);
                serverWriteThread.start();

                // 读取数据
                read(clientSocket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void read(Socket clientSocket) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String msg = "";
            while ((msg = bufferedReader.readLine()) != null) {
                System.out.println("receive msg: " + msg);

                if (msg.equals("bye")) {
                    bufferedReader.close();
                    clientSocket.close();
                }
            }
        } catch (IOException e) {
            try {
                clientSocket.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        String address = "192.168.3.8";
        int port = 9999;

        BIOServerSocket bioServerSocket = new BIOServerSocket(address, port);
        bioServerSocket.startServer();
    }
}

/**
 * 从Scanner获取输入信息,并写回到client
 */
class ServerWriteThread extends Thread {
    private Socket socket;
    private PrintWriter writer;
    private Scanner scanner;

    public ServerWriteThread(Socket socket) throws IOException{
        this.socket = socket;
        scanner = new Scanner(System.in);
        this.writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
    }

    @Override
    public void run() {
        String msg = "";
        try {
            while ((msg = scanner.nextLine()) != null) {
                if (msg.equals("bye")) {
                    socket.close();
                    break;
                }
                writer.println(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码比较简单,就是最基本的BIO类型的ServerSocket。

3.2 客户端启动telnet
hxw@hxwdeMacBook-Pro /dev % telnet 192.168.3.8 9999
Trying 192.168.3.8...
Connected to 192.168.3.8.
Escape character is '^]'.
3.3 wireshark抓取三次握手包

在客户端启动telnet之前,wireshark需要先启动完成。

 三次握手过程如上所示,用一张图来表示的话就是如下过程:

3.3.1 SYN包分析

三次包信息传递,我们逐个来了解下。

首先看第一个,客户端发起SYN包传递

No.     Time           Source                Destination           Protocol Length Info
     28 13.342124      192.168.3.18          192.168.3.8           TCP      78     58432 → 9999 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=64 TSval=2555585745 TSecr=0 SACK_PERM=1

Frame 28: 78 bytes on wire (624 bits), 78 bytes captured (624 bits) on interface 0
Ethernet II, Src: bc:d0:74:13:40:02 (bc:d0:74:13:40:02), Dst: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7)
Internet Protocol Version 4, Src: 192.168.3.18, Dst: 192.168.3.8
Transmission Control Protocol, Src Port: 58432, Dst Port: 9999, Seq: 0, Len: 0
    Source Port: 58432													# 源端口
    Destination Port: 9999												# 目标端口
    [Stream index: 7]													# 流节点号
    [TCP Segment Len: 0]												
    Sequence number: 0    (relative sequence number)					# 序列号
    [Next sequence number: 0    (relative sequence number)]
    Acknowledgment number: 0
    1011 .... = Header Length: 44 bytes (11)							# TCP头部长度
    Flags: 0x002 (SYN)													# 标志位
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...0 .... = Acknowledgment: Not set
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..1. = Syn: Set										# SYN包
        .... .... ...0 = Fin: Not set
        [TCP Flags: ··········S·]
    Window size value: 65535											# 窗口大小
    [Calculated window size: 65535]										# 估算窗口大小
    Checksum: 0x96ee [unverified]										# 校验和
    [Checksum Status: Unverified]
    Urgent pointer: 0
    																	# 以下是可选的选项信息
    Options: (24 bytes), Maximum segment size, No-Operation (NOP), Window scale, No-Operation (NOP), No-Operation (NOP), Timestamps, SACK permitted, End of Option List (EOL)
        TCP Option - Maximum segment size: 1460 bytes					# MSS
        TCP Option - No-Operation (NOP)
        TCP Option - Window scale: 6 (multiply by 64)
        TCP Option - No-Operation (NOP)
        TCP Option - No-Operation (NOP)
        TCP Option - Timestamps: TSval 2555585745, TSecr 0				# 时间戳
        TCP Option - SACK permitted
        TCP Option - End of Option List (EOL)
    [Timestamps]

我们可以拿这个包头信息与上述2.1中的图进行一个比对,基本是符合我们TCP协议头的定义的。

3.3.2 SYN+ACK包

服务端收到客户端的SYN包后,返回一个SYN+ACK标志位的包

No.     Time           Source                Destination           Protocol Length Info
     29 13.342428      192.168.3.8           192.168.3.18          TCP      66     9999 → 58432 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=256 SACK_PERM=1

Frame 29: 66 bytes on wire (528 bits), 66 bytes captured (528 bits) on interface 0
Ethernet II, Src: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7), Dst: bc:d0:74:13:40:02 (bc:d0:74:13:40:02)
Internet Protocol Version 4, Src: 192.168.3.8, Dst: 192.168.3.18
Transmission Control Protocol, Src Port: 9999, Dst Port: 58432, Seq: 0, Ack: 1, Len: 0
    Source Port: 9999
    Destination Port: 58432
    [Stream index: 7]
    [TCP Segment Len: 0]
    Sequence number: 0    (relative sequence number)					# 设置序列号
    [Next sequence number: 0    (relative sequence number)]
    Acknowledgment number: 1    (relative ack number)					# ACK number在接收到的SYN基础上+1
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x012 (SYN, ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set							# ACK标志
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..1. = Syn: Set										# SYN标志
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······A··S·]
    Window size value: 65535
    [Calculated window size: 65535]
    Checksum: 0x4c73 [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    Options: (12 bytes), Maximum segment size, No-Operation (NOP), Window scale, No-Operation (NOP), No-Operation (NOP), SACK permitted
    [SEQ/ACK analysis]
    [Timestamps]

相对于客户端发送的SYN包,服务端的响应包的主要差别在于:

标志位为:SYN+ACK;

Acknowledgment number为:接收到的客户端Sequence number + 1;

3.3.3 ACK包

客户端接收到服务端的SYN+ACK包之后,返回最后一次握手信息

No.     Time           Source                Destination           Protocol Length Info
     30 13.346319      192.168.3.18          192.168.3.8           TCP      54     58432 → 9999 [ACK] Seq=1 Ack=1 Win=262144 Len=0

Frame 30: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
Ethernet II, Src: bc:d0:74:13:40:02 (bc:d0:74:13:40:02), Dst: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7)
Internet Protocol Version 4, Src: 192.168.3.18, Dst: 192.168.3.8
Transmission Control Protocol, Src Port: 58432, Dst Port: 9999, Seq: 1, Ack: 1, Len: 0
    Source Port: 58432
    Destination Port: 9999
    [Stream index: 7]
    [TCP Segment Len: 0]
    Sequence number: 1    (relative sequence number)
    [Next sequence number: 1    (relative sequence number)]					# 下一个序列号
    Acknowledgment number: 1    (relative ack number)						# 响应服务端的序列化,在其基础上+1
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x010 (ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set								# 只有ACK标志位
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······A····]
    Window size value: 4096
    [Calculated window size: 262144]
    [Window size scaling factor: 64]
    Checksum: 0x7d46 [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    [SEQ/ACK analysis]
    [Timestamps]

同样,客户端接收到服务端的sequence number之后,在其基础上+1,作为Acknowledgment number,同时设置标志位为ACK。

这样就完成了整个连接(三次握手)。

3.4 wireshark抓取四次挥手包

基于我们的ServerSocket程序可知,如果客户端发送信息为bye,那么服务端会主动发起close,来关闭当前Socket连接。

基于此,我们来抓取下关闭连接包(四次挥手)。

先来看下,我们测试完成后的抓取包信息,如下所示:

用一张图来表示四次挥手过程,如下所示: 

3.4.1 FIN+ACK(一次挥手)

可以看出,是服务端(192.168.3.8)主动发起了挥手(关闭)动作,具体包信息如下:

No.     Time           Source                Destination           Protocol Length Info
     48 25.812209      192.168.3.8           192.168.3.18          TCP      54     9999 → 58432 [FIN, ACK] Seq=1 Ack=9 Win=131328 Len=0

Frame 48: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
Ethernet II, Src: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7), Dst: bc:d0:74:13:40:02 (bc:d0:74:13:40:02)
Internet Protocol Version 4, Src: 192.168.3.8, Dst: 192.168.3.18
Transmission Control Protocol, Src Port: 9999, Dst Port: 58432, Seq: 1, Ack: 9, Len: 0
    Source Port: 9999
    Destination Port: 58432
    [Stream index: 7]
    [TCP Segment Len: 0]
    Sequence number: 1    (relative sequence number)
    [Next sequence number: 1    (relative sequence number)]
    Acknowledgment number: 9    (relative ack number)
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x011 (FIN, ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set							# 发起对上一次接收到信息的ack
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...1 = Fin: Set										# 同时发起FIN标志,主动关闭连接
        [TCP Flags: ·······A···F]
    Window size value: 513
    [Calculated window size: 131328]
    [Window size scaling factor: 256]
    Checksum: 0x8b3c [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    [Timestamps]

服务端在ack的同时,主动发起了FIN标志位,表示服务端需要主动关闭该连接

3.4.2 ACK(二次挥手)

No.     Time           Source                Destination           Protocol Length Info
     49 25.815917      192.168.3.18          192.168.3.8           TCP      54     58432 → 9999 [ACK] Seq=9 Ack=2 Win=262144 Len=0

Frame 49: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
Ethernet II, Src: bc:d0:74:13:40:02 (bc:d0:74:13:40:02), Dst: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7)
Internet Protocol Version 4, Src: 192.168.3.18, Dst: 192.168.3.8
Transmission Control Protocol, Src Port: 58432, Dst Port: 9999, Seq: 9, Ack: 2, Len: 0
    Source Port: 58432
    Destination Port: 9999
    [Stream index: 7]
    [TCP Segment Len: 0]
    Sequence number: 9    (relative sequence number)
    [Next sequence number: 9    (relative sequence number)]
    Acknowledgment number: 2    (relative ack number)
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x010 (ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set							# 客户端接收到服务端包之后,发起一次ACK
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······A····]
    Window size value: 4096
    [Calculated window size: 262144]
    [Window size scaling factor: 64]
    Checksum: 0x7d3d [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    [SEQ/ACK analysis]
    [Timestamps]

客户端接收到服务端包之后,回复一次ACK,此时进入CLOSE_WAIT状态

3.4.3 FIN+ACK(三次挥手)

No.     Time           Source                Destination           Protocol Length Info
     50 25.816313      192.168.3.18          192.168.3.8           TCP      54     58432 → 9999 [FIN, ACK] Seq=9 Ack=2 Win=262144 Len=0

Frame 50: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
Ethernet II, Src: bc:d0:74:13:40:02 (bc:d0:74:13:40:02), Dst: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7)
Internet Protocol Version 4, Src: 192.168.3.18, Dst: 192.168.3.8
Transmission Control Protocol, Src Port: 58432, Dst Port: 9999, Seq: 9, Ack: 2, Len: 0
    Source Port: 58432
    Destination Port: 9999
    [Stream index: 7]
    [TCP Segment Len: 0]
    Sequence number: 9    (relative sequence number)
    [Next sequence number: 9    (relative sequence number)]
    Acknowledgment number: 2    (relative ack number)
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x011 (FIN, ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set							# 客户端处理完FIN成后,发起ACK+FIN
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...1 = Fin: Set										# 发起FIN后,表示不再发送数据到服务端
        [TCP Flags: ·······A···F]
    Window size value: 4096
    [Calculated window size: 262144]
    [Window size scaling factor: 64]
    Checksum: 0x7d3c [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    [Timestamps]

客户端第三次挥手后,进入LAST_ACK状态,而此时服务端接收到该FIN包后,则进入TIME_WAIT状态;

3.4.4 ACK(四次挥手)

No.     Time           Source                Destination           Protocol Length Info
     51 25.816464      192.168.3.8           192.168.3.18          TCP      54     9999 → 58432 [ACK] Seq=2 Ack=10 Win=131328 Len=0

Frame 51: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
Ethernet II, Src: IntelCor_40:2c:f7 (10:f0:05:40:2c:f7), Dst: bc:d0:74:13:40:02 (bc:d0:74:13:40:02)
Internet Protocol Version 4, Src: 192.168.3.8, Dst: 192.168.3.18
Transmission Control Protocol, Src Port: 9999, Dst Port: 58432, Seq: 2, Ack: 10, Len: 0
    Source Port: 9999
    Destination Port: 58432
    [Stream index: 7]
    [TCP Segment Len: 0]
    Sequence number: 2    (relative sequence number)
    [Next sequence number: 2    (relative sequence number)]
    Acknowledgment number: 10    (relative ack number)
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x010 (ACK)
        000. .... .... = Reserved: Not set
        ...0 .... .... = Nonce: Not set
        .... 0... .... = Congestion Window Reduced (CWR): Not set
        .... .0.. .... = ECN-Echo: Not set
        .... ..0. .... = Urgent: Not set
        .... ...1 .... = Acknowledgment: Set						# 服务端回复客户端的FIN包,表示收到该包
        .... .... 0... = Push: Not set
        .... .... .0.. = Reset: Not set
        .... .... ..0. = Syn: Not set
        .... .... ...0 = Fin: Not set
        [TCP Flags: ·······A····]
    Window size value: 513
    [Calculated window size: 131328]
    [Window size scaling factor: 256]
    Checksum: 0x8b3b [unverified]
    [Checksum Status: Unverified]
    Urgent pointer: 0
    [SEQ/ACK analysis]
    [Timestamps]

服务端发送ACK包来回复客户端的FIN包,表示收到该FIN包,最终关闭当前连接。

在等待最多2MSL后,服务端从TIME_WAIT状态进入正式close,这时再通过netstat命令查看连接信息,已经查看不到了。

总结:

上述整个连接开启和关闭过程可用下一张图来总结

本文主要介绍了TCP协议三次握手与四次挥手的过程。

有关于其他TCP协议的细节,后续会有更多文章讲述。

关注
打赏
1655041699
查看更多评论
立即登录/注册

微信扫码登录

0.0382s