TCP协议

发布于 2020-09-28  93 次阅读


TCP协议

首部格式

<16bit源端口号><16bit目的端口号>
<32bit序号>
<32bit确认序号>
<4bit数据偏移><6bit保留字段><URG><ACK><PSH><RST><SYN><FIN><16bit窗口字段>
<16bit校验和><16bit紧急指针>
<0~40Byte选项>

序号

本报文第一个字节的序号.

序列号的初始值并不一定是0, 建立连接后随机生成.

确认序号

期望收到的下一个报文的第一个字节的序号.

数据偏移

报文段的首部长度, 单位为2Byte.

URG

等于一时表示该报文段有紧急数据.

ACK

等于一时表示确认序号有效.

PSH

等于一时表示尽快提交至应用层.

RST

等于一时表示连接出错, 需要重新连接.

SYN

等于一时表示连接请求或者连接接受.

FIN

等于一时表示数据传输结束, 释放连接.

窗口字段

当前cwnd/rwnd窗口大小, 单位Byte.

校验和

伪首部+首部+数据段, 分成多个16bit字段, 进行二进制反码加法运算, 得到校验和.

紧急指针

本报文中紧急数据数目, 单位Byte.

紧急数据放在本报文最前面.

选项

长度可变, 但必须为4Byte的整数倍. 常见选项如MSS(最大段大小), 时间戳字段等.

MSS一般设置为MTU-IP首部-TCP首部, 即刚好不被IP层分片的最大值, 可在建立TCP连接的时候设置.

伪首部格式

<32bit源IP地址>
<32bit目的IP地址>
<8bit保留字段><8bit协议版本><16bit首部长度>

建立TCP连接(三次握手)

第一次握手

第一次握手请求建立连接, seq=x, SYN=1.

此时不能携带数据, 但会消耗掉一个序号.

第二次握手

第二次握手确认连接请求收到, seq=y, ack=x+1, SYN=1, ACK=1.

此时依然不能携带数据且会消耗掉一个序号.

第三次握手

第三次握手确认连接请求的确认收到, seq=x+1, ack=y+1, SYN=1, ACK=1.

此时可以携带数据, 如果不携带数据就会消耗掉一个序号.

第三次握手的必要性

<<计算机网络>>中的原文"为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误", 翻译一下就是: 如果客户端因为网络问题, 重发了第二次握手的报文, 而第二次握手的报文在路由器中滞留后延时到达服务器端, 服务器端由于只有两次握手便建立了一个新的TCP连接. 浪费了服务器端资源.

可靠传输

TCP协议使用连续ARQ协议控制滑动窗口内的报文段发送.

连续ARQ协议

连续发送滑动窗口内的报文段, 收到对某个分组的确认时, 默认该分组之前的分组都接受到了(用这种累计确认方式的主要原因是因为internet出错几率极低). 滑动窗口向前移动.

TCP协议是全双工通信, 所以客户端和服务器端都有发送窗口和接受窗口.

流量控制

接受方发送接受窗口大小rwnd来控制发送方的发送速率, 防止数据发送过快来不及接受. 当rwnd=0时发送方不再发送任何数据, 但启动持续计时器, 当时间到了后, 发送零窗口探测报文来获知接受方现在能不能接受数据.

拥塞控制

慢启动

建立TCP连接时或则超时重发时, 启动慢启动算法. 将cwnd的值设置为1, 每收到1次接收端的确认报文cwnd值便增加1, 每次发送数据时要将接收端的rwnd与cwnd比较, 选小的一个来发送数据包.

超时重传时的ssthresh也要变成当时cwnd的一半.

拥塞避免

cwnd在以2n指数增长到一个慢启动阈值(ssthresh)时, 启动拥塞避免算法, 每发送1轮数据包cwnd增加1.

快重传和快恢复

当发送方一连收到三个重复确认就应当立即重传对方尚未收到的报文段. 然后启动快恢复算法. 将ssthresh设置为现在cwnd的一半, 然后将cwnd降低到ssthresh, 开始执行拥塞避免算法.

重发控制

超时设置

每次发包的时候都会计算RTT(Round Trip Time). TCP协议中保存了RTTs, 即平滑往返时间(Smoothed). 通过以下公式计算RTTs. a值根据RTT变化幅度取值, 一般取0.125.

一旦发生重传则将该次报文的RTT丢弃, 不更新RTT, 直到不发生重传再更新RTT.

RTT_s = (1 - a) * RTT_s + a * RTT;

除了计算RTTs, 还要计算RTTD(RTT的偏差), 以下公式计算RTTD. RTTD初值取RTTs的一半. b值推荐0.25.

RTT_D = (1 - b) * RTT_D + b * abs(RTT - RTT_s)

下面是超时重传时间RTO(Retransmission Time-Out)的计算公式.

RTO = RTT_s + 4 * RTT_D

因为超时以0.5s为单位进行控制, 所以超时时间都是0.5s的整数倍. RTO初始值设置为6s左右.

报文段每重发一次便将重发时间变为以前的y倍, y一般为2.

四次挥手

第一次挥手

第一次挥手请求释放连接. seq=x, FIN=1. 发送完毕后客户端进入FIN_WAIT_1状态.

第二次挥手

第二次挥手确认收到释放连接的请求. seq=y, ack=x+1, ACK=1. 服务器端进入CLOSE_WAIT状态并发送需要传输的数据. 客户端接受到确认请求后进入FIN_WAIT_2状态.

第三次挥手

服务器端传送完数据后, 发出第三次挥手的请求. seq=z, ack=x+1, ACK=1, FIN=1. 服务器端进入LAST_ACK状态.

第四次挥手

客户端接受到服务器段的请求释放连接后发出确认请求即第四次挥手. seq=x+1, ack=z+1, FIN=1, ACK=1. 发出后便进入TIME_WAIT状态, 在该状态中等待两倍的最长报文段寿命(MSL)时间后便关闭TCP连接, 服务器端接受到确认连接后便关闭TCP连接.

TIME_WAIT状态是为了防止客户端的确认请求在网络中丢失. 在TIME_WAIT状态中若收到服务器端重发的请求释放连接可重发确认请求. 同时为了在等待时间内使所有滞留在网络中的报文段消失, 防止同一个Socket四元组复用时接受到这些报文段.

第三, 四次挥手的必要性

当客户端发送FIN释放连接请求时仅仅表示客户端没有数据传输给服务器端了, 服务器端可能还有数据未传输, 所以要等服务器端传输完数据后服务器端再发送一次FIN释放连接请求.

其他

端口号分类

  1. 熟知端口(0~1023).

  2. 注册端口(1024~49151).

  3. 临时端口(49152~65535).


人生如逆旅,我亦是行人。