TCP首部
TCP首部被封装在一个IP数据报中
TCP首部格式
源端口号/目标端口号 标记TCP连接通信双方主机的端口,而双方的IP地址在IP协议的报文首部中已经记录。端口号最大值为65535
32位序号 发送方报文数据段的起始序号,用于标记每一个发送字节;
32位确认序号 表示接收方已经成功接收(确认序号-1)的字节。假设B发送给A的TCP报文中,确认需要为700,则表示B已经成功接收了A发送的序号为700之前(不包括700)的所有数据 若确认序号 = N, 则表明:到需要N-1为止的所有数据都已经正确收到
4位首部长度
TCP首部前20位是固定的,选项
部分的长度是可选的,但是长度为4N(即4的倍数)。首部长度代表整个报文首部的长度,单位是4字节(32位)。4位首部长度最大值是15,即TCP报文首部最大长度是15 * 4 = 60 个字节,首部可选长度为 60 - 20 = 40 个字节
保留(6位) 保留今后使用,目前置为0
6个控制位
- URG: 表明该报文段的紧急指针字段有效,它告诉系统此报文段中有紧急数据,紧急指针指明了紧急数据的最后一个字节位于数据段中哪个序号
- ACK: 仅当ACK=1时,确认序号字段才生效;当ACK=0时,确认序号字段无效。TCP规定,在连接建立后所有传送的TCP报文段都必须把ACK置为1
- PSH: TCP接收方收到PSH=1的报文段时,会尽快的交付到给应用程序处理,而不需要等待整个接收缓存都填满了再向上交付
- RST: 当RST=1时,表明TCP连接出现严重差错,必须断开后重新建立连接,同时可以用于拒绝一个非法报文或拒绝打开一个连接
- SYN:SYN置为1时,表示这是一个连接请求或接受连接报文;
- FIN:用于释放一个连接,当FIN为1时,表示发送方的数据已经发送完毕,并要求释放连接
16位窗口大小 接收窗口,窗口值告诉对方,当前报文确认序号算起,接收方允许发送的数据量 接收窗口值是发送发设置其发送窗口的依据,且该值是动态变化的
16位校验和 当接收方接收到一个报文段后,会使用该字段校验整个报文是否完整且正确
16位紧急指针 与控制位URG搭配使用
选项 长度可变,最大可达40字节。可以用于存放:
- 最大报文长度 MSS (maximum segment size)
- 窗口扩大选项:解决高速网络下窗口字段不够用问题
- 时间戳戳选项:用于计算往返时间和防止序号绕回
数据 存放要发送的报文数据
可靠服务
TCP的底层网络层并不是一个可靠服务,它只提供尽最大努力服务。要在一个不可靠的协议上建立一个可靠的协议,TCP采用了发送-确认机制:即接收方每发送一个报文,就向发送方发送一个确认,发送方只有收到这个报文的确认才认为这次发送才是成功的。
停止等待协议
每发送一个报文段,等待这个报文段的确认,直到收到确认后再发送下一个报文段;或者是超时后重传
优点是实现简单。缺点是信道利用率低
连续ARQ协议
维持一个发送窗口,在发送窗口内的报文段全部发送出去,接收方采用累计确认的方式,对按序到达的最后一个报文发送确认
优点是容易实现。缺点是不能向发送方反应接收方已经正确收到的所有分组信息。比如发送了5个报文,如果第3个报文丢失了,则第3,4,5个报文都要重传。这样在通信线路不好的情况下,连续ARQ协议会带来负面的影响
滑动窗口协议
TCP的滑动窗口是以字节为单位的,假设A为发送方,B为接收方
发送窗口
发送方会在字节流中维护一个发送窗口。窗口中包含三个指针:P1, P2, P3
- P1之前的字节是TCP已经发送且接收到确认的字节
- P2-P1的字节是TCP已经发送出去,但是还没有接收到确认的字节
- P3-P2的字节是TCP允许发送但还没有发送出去的字节
- P3之后的字节是TCP还不允许发送的字节
发送方发送一个字节后,P2指针会向右移动,最大移动到P3之前一个指针
当发送方按序号收到一个确认后,会把P1指针向右移动。此时P3指针可能存在以下情况:
- 向右移动:接收窗口不变,正常移动;接收窗口增大,整个发送窗口也跟着增大
- 不移动:接收方的接收窗口变小;网络拥塞,发送方主动减小窗口值
- 向左移动:接收方的接收窗口变小,但TCP标准强烈不建议这么做,原因是容易导致错误 总的来说,发送窗口主要受到对方接收窗口和网络环境影响
已发送但没有收到确认的字节仍然会保留在窗口中,当TCP超过一定时间没有接收到确认时,TCP会重新发送这些字节
发送方TCP缓存 发送方缓存会保存以下两种数据:
- 发送方应用程序传送给发送方TCP准备发送的数据
- TCP已发送但尚未收到确认的数据
接收窗口
接收方窗口包含两种类型的字节:1. 未按序接收字节;2. 未接收字节
当接收方接收到一个报文后存在以下几种状况
- 报文中的字节序号已经确认过了,这时则不向上交付,但会再次发送这个报文的确认报文,防止发送方超时重传
- 报文中的字节序号是属于按需到达的最大序号,TCP则把这个报文交付给应用程序,并移动窗口(上图中窗口左指针往右移动)
- 报文中的字节在接收窗口内,且不是按需到达的最大序号。TCP则保留这个报文,但不发送确认,等待前面确实的报文到达后一并发送确认
接收方TCP缓存 接收方缓存会保存以下两种数据:
- 发送方应用程序传送给发送方TCP准备发送的数据
- TCP已发送但尚未收到确认的数据
超时重传 当发送方发送一个报文后在规定时间内没有收到确认就要重传已发送的报文 TCP采用自适应算法,使用加权平均往返时间确认超时时间。具体是算法会稍微复杂一点
选择ACK 当接收方收到未按序到达的报文时,会把报文保存在TCP缓存中,但不发送确认报文。等待前面缺失的未按序到达报文都接收后,才一并发送确认报文。这种做法会导致发送方检测到确认超时从而发生超时重传
TCP支持对未按序到达报文进行选择确认。主要手段就是利用首部选项部分的40个字节保存需要选择确认的字节序号
建立连接
三次握手
当客户端A向服务器B发起连接请求时
- 客户端A先创建传输控制模块TCB,向B发送一个连接请求报文,SYN=1, seq=x, 不携带数据,但是消耗掉一个序号;同时A进入 SYN-SEND(同步已发送状态)
- 服务器B收到报文后也创建传输控制模块TCB;向A发送一个确认报文,SYN=1, ACK=1; 确认号ack=x+1;为自己选择一个初始报文seq=y; 报文不携带数据,但消耗序号;服务器进入SYN-RCVD(同步已接收)
- 客户端A收到服务器B的确认报文后,还要向B发出确认;确认报文ACK=1, 确认号ack=y+1, 自己的序号seq=x+1(不携带数据的情况);该报文可以携带数据,如果不携带数据则不消耗序号;客户端A发送了ACK报文后,则进入了ESTABLISHED(已建立)状态;B收到确认报文后,也进入ESTABLISHED状态
为什么客户端在收到了一次确认后,还要对服务器进行再一次的确认? 这主要是为了防止已失效的连接请求报文段又突然传输到了给B;假如以下情况:
- A发送了一个连接请求报文a1给B,由于网络原因,a1停留在了网络某处
- A超时未收到确认后又发送了一个连接请求报文a2给B, 并且正常建立了连接
- 由于网络的延迟,a1又突然传输到了服务器B;服务器B认为这是一个新的链接请求,就会对A发出确认; 如果没有客户端A对服务器B的再次确认,服务器就会再次新建一个连接;而如果采用再次确认,当B接收到a1后向A发出确认时,A发现这个确认包已经失效,则会拒绝发出第二次确认,B没有收到确认,则不会建立连接
四次挥手
前两次挥手
- 客户端A向服务器B发送一个释放连接报文:FIN=1,seq=u(A最后一个发送序号+1);同时A进入 FIN-WAIT-1状态,等待B的确认
- 服务器B收到断开连接请求后会发出确认报文:ack=u+1, seq=v(B最后一个发送序号+1), 同时进入CLOSE-WAIT 状态。
- A收到B的确认状态后,就进入 FIN-WAIT-2 状态
此时TCP通知高层应用,A-B这个方向的连接已经释放了,这时TCP连接处于半关闭状态,A已经没有数据要发送给B了;但B若发送数据,A仍要接收。这个状态可能会持续一些时间
后两次挥手
- 当B没有数据要发送给A后,会发送一个释放报文: FIN=1,seq=w(B最后一个发送序号+1),ack=u+1; B进入TIME-WAIT状态.
- A收到B的释放报文段后,发送一个确认报文: ACK=1, ack=w+1, seq=u+1;然后进入TIME-WAIT状态。此时TCP连接还没有释放掉,需要经过 时间等待计时器设置的时间 2MSL(最长报文寿命)后,A才进入CLOSE状态
- B收到确认报文后就释放连接,并撤销传输控制块TCP
A为什么要等待2MSL时间才释放连接?
防止确认报文丢失,以便超时重传。如果A发送的确认报文丢失了,那么B会进行超时重传;若A发送完
流量控制
流量控制就是发送方的发送速率不要太快,要让接受方来的及接收;TCP是利用滑动窗口实现流量控制的
假设接收方的接收窗口是400:
- 发送方发了一个200字节的报文后,接收方接收到了200字节,并发送一个确认报文,报文的窗口字段为200
- 此时发送方就最多就不能发送超过200个字节的报文,除非接收方的窗口值改变了
拥塞控制
拥塞控制就是防止过多的数据注入到网络中
慢开始和拥塞避免算法
该算法在TCP中维护两个值:拥塞窗口(cwnd)和 慢开始门限(ssthresh) TCP开始发送数据时,令cwnd = 发送窗口大小,且值为 1 * MSS(最大报文段),ssthreash = 16 * MSS
网络正常时
- 发送方根据发送窗口,发送报文M1;当发送方接收到M1的确认时,就把cwnd和发送窗口增加1个报文的大小(这里为了方便说明,使用报文为单位,实际上是以字节为单温)
- 接下来发送方根据发送窗口的大小,发送了M2, M3两个报文,且接收到了M2,M3的确认;这时cwnd和发送窗口就会增加两个报文的大小;
- 当cwnd <= ssthresh时,cwnd是以指数增长的
- 当cwnd > ssthreah时,拥塞窗口每次的增加步长将变成1
1~3阶段就是属于慢开始阶段,4阶段属于拥塞避免阶段
网络拥塞时 当一个报文发生超时重传时,TCP则认为此时网络出现拥塞,因为由于线路问题导致丢包的概率是很小的
假设此时cwnd等于发送窗口,且值为30(依旧是报文为单位),ssthresh为16
-
当TCP检测到一个报文需要超时重传时,TCP认为此时出现了网络拥塞。cwnd的值将重置为1,且发送窗口也重置为1;ssthresh的值将减少为发送窗口的一半,即15
-
TCP重新进行慢开始阶段;当cwnd > ssthresh时,就开始拥塞避免阶段
总结 当cwnd <= ssthresh 时,TCP进行慢开始阶段,拥塞窗口是指数增长的; 当cwnd > ssthresh 时, TCP进行拥塞避免阶段,拥塞窗口是线性增长的; 当发送超时重传时,TCP认为网络出现拥塞,将ssthresh设置为出现拥塞时的发送窗口的一半,同时拥塞窗口和发送窗口将重置为1,并重新开始慢开始和拥塞避免
快重传和快恢复
快重传
- 当发送放发送了两个报文M1, M2,M3时,接收方只收到了M1, M3,而M2没有收到;
- 当接收方收到了M3时,则针对M1再次发送一个确认;
- 接着发送方又发送了M4,M5;同时接收方接收到了M4时,重传一次M1的确认,接收到M5时,也重传一个M1的确认
当发送方连续收到3个重复确认时,则应该尽快重复发送M2报文,而不是等待超时,同时进行快恢复算法。
快恢复 当发送方连续收到3个重复确认时,则:
- 将ssthresh减半,同时将拥塞窗口设置为ssthresh减半后的值
- 由于发送方连续接收到了3个确认报文,此时认为网络很可能没有发生拥塞。所以不进行慢开始算法,而是进行拥塞避免算法,拥塞窗口将线性增长
随机早期检测RED(Random Early Drop)
路由器的缓存队列长度是有限的,且按照先进先出(FIFO)处理IP数据包。当队列满了之后就会丢弃尾部一系列的数据包,就会导致发送方出现超时重传。这可能会影响到多个TCP连接,使得这些连接发生慢开始。这种情况称为全局同步。此时全网的通信量可能会突然下降很多。而在网络恢复后,通信录又突然增大很多。 为了避免全局同步的发生,可以在路由器中使用随机早期检测。
RED工作使用三个关键参数:最小门限THmin, 最大门限THmax,丢弃概率p
- 当缓存队列中的数据包数量少于THmin时,路由器不会丢弃数据包。丢弃概率p=0
- 当缓存队列中的数据包数量多于THmin, 但少于THmax时,会有p的概率丢弃数据包。这种做法就是为了在个别的TCP连接上进行拥塞控制,避免全局同步。 0 < p < 1
- 当缓存队列中的数据包多于THmax时,则丢弃后面来的数据包。 p = 1
概率P是动态改变的,主要取决于平均队列长度
参考
计算机网络第五版 TCP-IP详解卷1:协议