💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## TCP协议的特点 1. 使用TCP协议协议通信的双方必须先建立连接,然后才能开始数据的读写。 2. TCP协议连接是一对一的,基于广播和多播的应用程序不能使用TCP服务。无连接的UPD适合于广播和多播。 3. 当发送端应用程序连续执行多次写操作时,TCP模块先将这些数据放入TCP发送缓冲区中,当TCP模块真正开始发送数据时,发送缓冲区中这些等待发送的数据可能被封装成一个或多个TCP报文。因此TCP模块发出的TCP报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系。 4. 当接收端收到一个或多个TCP报文后,TCP模块将他们携带的应用程序数据按照TCP报文段的序号依次放入TCP接收缓冲区中,并通知应用程序读取数据。因此应用程序执行的读操作次数和TCP模块接收的TCP报文段的个数之间没有固定的数量关系。 5. UDP协议在发送端应用程序每执行一次写操作,UDP模块就将其封装成一个UDP数据发送之,接收端必须及时针对每一个UDP数据报执行读操作,否则就丢包。 ![](https://files.catbox.moe/aanrvl.png) >应用程序执行的写操作次数和接收端执行的读操作次数没有任何数量关系,这就是字节流的概念 ## TCP固定头部结构 TCP头部信息出现在每个TCP报文中。 ![](https://files.catbox.moe/pms3l0.png) 16位端口号:来自哪里,以及传给哪个上层协议或应用程序。 32位序号:一次TCP通信过程中某一个传输方向上的字节流的每个字节编号。序号之被初始化为某个随机值ISN。后续TCP报文段中序号值被系统设置为ISN+该报文段所携带数据的第一个字节在整个字节流中的偏移。 32位确认号:用作另一方发送来的TCP报文段的响应。其值是收到的TCP报文段的序号值加1。 4位头部长度:标识该TCP头部有多少32位(4字节),因为4位最大能表示15,所以TCP头部最长是60字节。 6位标志包含如下选项: * URG标志,表示紧急指针是否有效 * ACK标志,表示确认号是否有效。携带ACK标志的TCP报文段为确认报文段。 * PSH标志:提示接收端应立即从TCP接收缓冲区读走数据,为接收后续数据腾出空间。 * RST标志,表示要求对方重新建立连接。我们称携带RST标志的TCP报文段为复位报文段 * SYN标志,表示请求建立一个连接,我们称携带SYN标志的TCP报文段为同步报文段。 * FIN标志,表示通知对方本端要关闭连接了。携带FIN标志的报文段为结束报文段。 16位窗口大小:是TCP流量控制的一个手段,这里说的窗口,指的是接收通告窗口,它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样就可以控制发送数据的速度 16位校验和:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分,这也是TCP可靠传输的一个重要保障。 16位紧急指针:是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切的说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。 ## TCP头部选项 TCP头部的最后一个选项字段,是可变长的可选信息,这部分最多包含40字节,因为TCP头部最长是60字节。 ![](https://files.catbox.moe/8zs7y8.png) ## TCP的连接和断开 ### 准备 server ``` $ nc -l 8881 ``` client: ``` nc 127.0.0.1 8881 ``` ### tcpdump ``` sudo tcpdumo port 8881 -S ``` > -S 的作用是为了显示绝对值,否则第三次握手的时候tcpdumo会简化输出,ack的值是1. tcpdump参数 ``` 使用tcpdump抓包查看tcp三次握手过程 参数说明: -c        指定包个数 -n        IP、端口用数字方式显示 -i         指定网卡,默认为eth0 -X       把协议头和包内容以16进制和ASCII的形式显示出来,对进行协议分析时很有用 -e       输出增加以太网帧头部信息 -F       指定过滤表达式所在的文件 -w      将流量保存到文件中,二进制格式 -r       读取参数-w保存的文件 port   指定端口 ``` ### 连接 建立TCP连接需要三次握手 ``` 12:16:12.918224 IP 115.231.93.68.37210 > VM-0-7-ubuntu.8881: Flags [S], seq 3339588145, win 64240, options [mss 1424,sackOK,TS val 2782499184 ecr 0,nop,wscale 7], length 0 12:16:12.918294 IP VM-0-7-ubuntu.8881 > 115.231.93.68.37210: Flags [S.], seq 1394745198, ack 3339588146, win 65160, options [mss 1460,sackOK,TS val 897553608 ecr 2782499184,nop,wscale 7], length 0 12:16:12.933025 IP 115.231.93.68.37210 > VM-0-7-ubuntu.8881: Flags [.], ack 1394745199, win 502, options [nop,nop,TS val 2782499199 ecr 897553608], length 0 ``` x = 3339588145 y = 1394745198 * 第一次握手(SYN=1, seq=x): 客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。 发送完毕后,客户端进入`SYN_SEND`状态。 * 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1): 服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入`SYN_RCVD`状态。 * 第三次握手(ACK=1,ACKnum=y+1) 客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1 发送完毕后,客户端进入`ESTABLISHED`状态,当服务器端接收到这个包时,也进入`ESTABLISHED`状态,TCP 握手结束。 ### 连接断开 TCP连接的断开需要四次挥手。 ``` 12:17:01.598449 IP 115.231.93.68.37210 > VM-0-7-ubuntu.8881: Flags [F.], seq 3339588146, ack 1394745199, win 502, options [nop,nop,TS val 2782547865 ecr 897553608], length 0 12:17:01.598546 IP VM-0-7-ubuntu.8881 > 115.231.93.68.37210: Flags [F.], seq 1394745199, ack 3339588147, win 510, options [nop,nop,TS val 897602288 ecr 2782547865], length 0 12:17:01.612711 IP 115.231.93.68.37210 > VM-0-7-ubuntu.8881: Flags [.], ack 1394745200, win 502, options [nop,nop,TS val 2782547879 ecr 897602288], length 0 ``` x = 3339588146 y= 1394745199 * **第一次挥手**(FIN=1,seq=x) 假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。 发送完毕后,客户端进入`FIN_WAIT_1`状态。 * **第二次挥手**(ACK=1,ACKnum=x+1) 服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。 发送完毕后,服务器端进入`CLOSE_WAIT`状态,客户端接收到这个确认包之后,进入`FIN_WAIT_2`状态,等待服务器端关闭连接。 * **第三次挥手**(FIN=1,seq=y) 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。 发送完毕后,服务器端进入`LAST_ACK`状态,等待来自客户端的最后一个ACK。 * **第四次挥手**(ACK=1,ACKnum=y+1) 客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入`TIME_WAIT`状态,等待可能出现的要求重传的 ACK 包。 服务器端接收到这个确认包之后,关闭连接,进入`CLOSED`状态。 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入`CLOSED`状态。 可以看到实际tcpdump打印出来的数据是,第二次挥手和第三次挥手整合成了一个包返回给客户端。 这是因为关闭连接有两种方式,当一方关闭连接,另外一方没有数据发送时,马上关闭连接,也就将第二步的ack与第三步的fin合并为一步了,这个优化在RFC793 3.5节: ``` CLOSE is an operation meaning "I have no more data to send." The notion of closing a full-duplex connection is subject to ambiguous interpretation, of course, since it may not be obvious how to treat the receiving side of the connection. We have chosen to treat CLOSE in a simplex fashion. The user who CLOSEs may continue to RECEIVE until he is told that the other side has CLOSED also. Thus, a program could initiate several SENDs followed by a CLOSE, and then continue to RECEIVE until signaled that a RECEIVE failed because the other side has CLOSED. We assume that the TCP will signal a user, even if no RECEIVEs are outstanding, that the other side has closed, so the user can terminate his side gracefully. A TCP will reliably deliver all buffers SENT before the connection was CLOSED so a user who expects no data in return need only wait to hear the connection was CLOSED successfully to know that all his data was received at the destination TCP. Users must keep reading connections they close for sending until the TCP says no more data.\[Page 37\] ``` >**注意,在我们准备退出这个tcp连接的时候,我们不能使用ctrl+c退出,不然会出现只能退出一半这种尴尬的情景。请分别在对话两端ctrl+d**, ## TCP半关闭状态 TCP是双全工,所以它允许两个方向的数据传输被独立关闭。通信的一端可以发送结束报文段给对方,告诉它本端已经完成了数据的发送,但允许继续接收来自对方的数据,直到对方也发送结束报文段以关闭连接。TCP连接的这种状态称为半关闭状态。 >服务器和客户端应用程序判断对方是否已经关闭连接的方法是:read系统调用返回0(收到结束报文段)。