tcp
tcp报文格式
源/目的端口号(Source/Destination Port)
标识应用程序,通过端口号+IP地址可以确认一条唯一的连接。
序列号(Sequence Number)
发送方要发送的序列编号。
序列号其初始值,在头两次握手(SYN=1)时,通过算法来随机生成序号。
在ESTABLISHED(建立连接后),序列号的变化规则为,seq=上一条已方发送报文段的seq+TCP报文的净荷字节数(不包含TCP头)=上一条已方收到报文段的ACK number。
第一,二次握手(SYN=1)和第一,三次挥手(FIN=1)时,虽然没有传输数据,但认为净荷为1。
确认序号(Acknowledgment Number)
期望下次对方发来的数据的起始编号,同时表示小于该确认的序号的数据以成功接收。
当控制标志ACK=1时有效,ACK只有在第1次握手时为0。
ack number=已方收到的上一条报文段的seq+TCP报文的净荷字节数(不包含TCP头),你一条报文段没收到,哪来的ack number。
首部长度(Data Offset)
TCP头部的长度,单位为4字节。在没有可以选项字段时,TCP头部固定长度为20字节,即头部长度为20/4=5。TCP头部长度最大为4*(2^4-1)=60
字节
保留(Reserved)
目前没用到,当前必须为0
控制标志(Control Bits)
- URG:紧急标志,为1表示当前报文段存在被发送端上层实体置为“紧急”的数据。接收方应当优先处理这些数据。紧急指针字段指出了这部分数据的结束位置。
- ACK(Acknowledgment)确认
- SYN(Synchronize)同步
- FIN(Finish)结束
窗口大小(Window Size)
一个16位的数值,用于控制对方发送的数据量,以避免接收方缓冲区溢出。窗口大小是流量控制的一部分,可以动态调整。
校验和(Checksum)
一个16位的字段,用于检查整个TCP报文段的错误。校验和确保数据在传输过程中的完整性。
紧急指针(Urgent Pointer)
一个16位的字段,如果URG标志被设置则为正数,表明从当前序列号开始的紧急数据的字节偏移量。
选项(Options)
TCP头部可能包含各种可选字段,这些字段不是固定的,可能包括如最大报文段大小(MSS)、窗口缩放选项、选择性确认(SACK)等,用于优化TCP连接的性能。
最常见的选项字段是MSS(Maximum Segment Size最长报文大小),每个连接方通常都在通信的第一个报文段(SYN标志为1的那个段)中指明这个选项字段,表示当前连接方所能接受的最大报文段的长度。
三次握手(TCP连接的建立)
- TCP服务器准备好接受连接,进入LISTEN状态,这一过程称为被动打开。
- 第一次握手:客户端发送SYN标志为1(表示这是一个同步报文段),且seq随机的报文段,请求建立连接。此时的seq记为ISN©(Initial Sequence Number,初始序列号),括号中的c表示这是和客户端的序列号。客户端发送后变为SYN-SENT状态。
- 第二次握手:服务端收到客户端的第一次握手信号,变为SYN-RCVD状态。随即确认客户端的SYN报文段,发送一个ACK和SYN标志均为1的报文段。该报文段中ack=ISN©+1,seq随机,标记为ISN(s),此处的s表示这是服务端的序列号。服务端变为SYN-RCVD状态。
- 第三次握手:客户端收到服务端的第二次握手信号,变为ESTABLISHED状态,随即确认服务端的报文段,发送ACK标志为1的报文段。该报文段中ack=ISN(s)+1,seq=ISN©+1。服务端收到客户端的第三次握手信号之后变为ESTABLISHED状态。
四次挥手(TCP连接的释放)
- 第一次挥手:客户端发送FIN标志为1(即FINISH,表示通信结束)的报文段,请求断开连接,执行主动关闭(active close)。此时,报文段中包含对于服务端数据的确认,ACK为 1,假设ack=V。连接断开前已经历了一系列的数据传输,seq取决于之前已发送的报文段,假设seq=U。客户端状态变为FIN-WAIT-1。
- 第二次挥手:服务端接收到第一次挥手信息,切换为CLOSE-WAIT状态,随即发送ACK标志为1,ack=U+1的报文段,此时seq=V。客户端接收到服务端的第二次挥手信号,变为FIN-WAIT-2状态。第二次挥手后,服务端仍可发送数据,客户端仍可接收。
- 第三次挥手:服务端完成数据传送后,发送FIN标志和ACK标志均为1的报文段,ack=U+1,seq大于V,假设为W,请求断开连接,这一过程称为被动关闭。服务端发送第三次挥手信号后,变为LAST-ACK状态。
- 第四次挥手:客户端收到第三次挥手信号,随即发送ACK标志为1,seq=U+1,ack=W+1的报文段,变为TIME-WAIT状态。服务端收到第四次挥手信号,变为CLOSED状态。客户端从变为TIME-WAIT状态开始计时,等待2MSL(2倍最大报文时长,约定值)后进入CLOSED状态。四次挥手结束。
三次握手,四次挥手补充
-
为什么要三次握手,握两次行不行?
假设只握两次手,那么server在第二次握手后就会跳过syn_receved(同步接受状态)直接进入established(连接已建立状态)。
如果第二次握手丢失,client会一直卡在syn_send(同步已发送状态),等待对方(SYN=1)信号。当此时server已经在established状态,不会再发送(SYN=1)的回应信号。而是发送数据,但一直没收到client的ACK确认信号,就一直超时重传,陷入了死锁的情况。 -
为什么关闭连接的需要四次挥手,而建立连接却只要三次握手呢?
其根本原因在于TCP是全双工通信,即客户端和服务器之间的数据传输是独立的。一方数据传输完,不代表另一方的数据也传输完。
- 当客户端发送FIN包时,只表示客户端完成了数据发送,但服务器可能还有未发送完的数据。因此,服务器需要先回复一个ACK包,确认客户端的FIN包,然后在完成数据发送后再发送自己的FIN包,表示自己也完成了数据发送。(个人认为四次挥手比三次握手多的部分就是,第二次挥手和第二次挥手到第三次挥手间的close-wait阶段)
- 为什么主动断开方在TIME-WAIT状态必须等待2MSL的时间?
MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“最大报文生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
2MSL即表示报文段一个来回所需要花费的最大时间。
原因1:确保最后的ACK报文能够被对方收到
如果第四次挥手丢失,主动断开方没有TIME-WAIT状态直接进入close状态。那么被动接受方会一直卡在,last-ack状态一直重传FIN报文,但自动接受方已经关闭,不在接受该消息。当如果有TIME-WAIT状态,就可以正确接收该重传消息。
原因2:防止旧连接中的报文段干扰新连接
TCP连接是通过四元组(源IP地址、源端口号、目的IP地址、目的端口号)唯一标识的。如果在短时间内(小于2MSL)重新建立相同四元组的新连接,网络中可能仍然存在的旧连接的报文段可能会被误认为是新连接的报文段。通过在TIME-WAIT状态等待2MSL,能够确保旧连接中的所有报文段已经从网络中消失,避免它们对新连接产生干扰。
- 如果已经建立了连接,但是Client端突然出现故障了怎么办?
TCP还设有一个保活计时器,Client端如果出现故障,Server端不能一直等下去,这样会浪费系统资源。每收到一次Client客户端的数据帧后,Server端都的保活计时器会复位。计时器的超时时间通常是设置为2小时,若2小时还没有收到Client端的任何数据帧,Server端就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,Server端就认为Client端出了故障,接着就关闭连接。如果觉得保活计时器的两个多小时的间隔太长,可以自行调整TCP连接的保活参数。
TCP数据传输
可靠性保障
- 累计确认
即seq=上一条已方发送报文段的seq+TCP报文的净荷字节数(不包含TCP头)=上一条已方收到报文段的ACK number。
ack number=已方收到的上一条报文段的seq+TCP报文的净荷字节数(不包含TCP头) - 延时确认:
接收方在接收到每个报文段后,不会立即发送确认报文(ACK),而是会等待一段时间,看是否有其他报文段到达或者接收方是否有数据要发送。在这种情况下,接收方可以将多个确认合并为一个ACK报文。这种机制被称为延迟确认,目的是减少ACK报文的数量,从而降低网络开销。 - 超时重传
当发送方发送数据后,如果在预定时间内未收到接收方的确认(ACK),发送方会假设该数据段丢失,并重新发送该数据段。
滑动窗口
动态变化,表示发送方当前可用的缓冲区即接收窗口大小。
大小变为0时,发送方会暂停数据发送。一段时间后,接收方腾出了足够的空间来接收数据,它会发送报文段通知发送方继续发送。
拥塞控制
滑动窗口表示发送方当前接收能力,那如何表示网络传输能力?
拥塞窗口(Congestion Window,cwnd),拥塞窗口是发送方根据网络状况计算得到,其动态变化。
基于拥塞窗口,结合下面的五种机制或策略,发送方实现了流量控制。
慢启动
连接建立后将拥塞窗口的初始大小cwnd设置为一倍的SMSS(发送方最大报文段大小)(协议的实现可能并不是一倍,为了便于讨论,我们假设为一倍),即cwnd=1*SMSS。为了便于分析,我们省略SMSS,称初始的cwnd为1,下文同理。
在慢启动阶段,每接收到一个ACK,cwnd翻倍,这一阶段窗口大小呈指数增长。
拥塞避免
当cwnd达到慢启动阈值ssthresh(Slow Start Threshold)时,进入拥塞避免阶段。此阶段每接收到一个ACK,cwnd+1。
超时重传
随着窗口的增长,网络中传输的数据越来越多,最终超出带宽限制,就会出现报文段的丢失。发送方为每条数据维护一个定时器,如果限定时间内未收到ACK则判定超时。发送端检测到超时,将ssthresh调整为当前cwnd的一半,然后将cwnd重置为1,重新开始慢启动过程。
快速重传
我们知道,TCP采用了累积确认的方式,如果某个报文段发生了丢失,即便收到了后面的数据,接收方返回的ACK也是一样的。当发送方连续收到3个重复的ACK时,判定相应的报文段丢失,立即重传。(超时重传是,超过超时时间了。这个是没超过超时时间,但已经连续收到3个重复的ACK,判断相应报文段丢失)。这样可以在不等待超时的情况下重传丢失的报文段,避免了数据丢失时的长期等待,提升传输效率。
快速恢复
当快速重传发生时,sshthresh调整为当前cwnd的一半,cwnd不必重新从1开始,而是与新的sshthresh相等,重新执行拥塞避免策略。
发送窗口
发送窗口(Send Window,swnd),是拥塞窗口和接收窗口的最小值。