目录
部分图例摘取自《计算机网络(第7版)》- 谢希仁
TCP是一种面向连接的传输层协议。在无连接的IP业务中实现面向连接,需要实现:
- 传输前要知会对端传输即将开始;
- 传输过程中信息必须按顺序完整送达;
- 传输结束要知会对端传输完毕。
对于1、3而言,TCP使用3-WAY握手、4-WAY挥手机制建立和拆除连接。对于2,TCP采用序列号和确认来确保信息的按顺序完整送达。而且使用了拥塞避免的方法,尽可能匹配发送方和接收方的速率。
TCP头部
首先介绍一下TCP的头部,TCP的标准头部由20字节构成:
源端口 | 16bit | 标识发送方端口 |
目的端口 | 16bit | 标识接收方端口 |
序列号 | 32bit | 本地正在发送的字节在段的位置 |
确认号 | 32bit | 要求对方发送的字节在段的位置 |
长度 | 4bit | TCP头部长度,单位4字节,最多60字节 |
保留 | 6bit | 全0 |
特殊位 | 6bit | URG、ACK、PSH、RST、SYN、FIN |
窗口 | 16bit | CWND,防止本地缓冲区溢出 |
校验 | 16bit | 检查整个TCP段的完整性 |
紧急指针 | 16bit | 配合URG,指示紧急字节的段位置 |
TCP连接建立和拆除
TCP 3-WAY握手
状态\接收 | 无/超时 | SYN | SYN/ACK | ACK | RST |
CLOSED | Client发送SYN,打开连接计时器,进入SYN_SEND | / | / | / | / |
LISTENEING | Server打开端口侦听 | 进入SYN_RCV,发送SYN/ACK | / | / | 进入CLOSED |
SYN_SEND | CLOSED | 进入SYN_RCV,发送SYN/ACK | 发送ACK,进入ESTABLISHED | / | 进入CLOSED |
SYN_RCV | / | / | / | 进入ESTABLISHED | 进入CLOSED |
ESTABLISHED | 应用程序定义 | / | / | / | 进入CLOSED |
TCP 4-WAY 挥手
状态\接收 | 无/超时 | FIN | ACK | FIN/ACK |
ESTABLISHED | 发送FIN,进入FIN_WAIT1 | 发送ACK,进入CLOSE_WAIT | ||
FIN _WAIT1 | 发送ACK,进入CLOSING | 进入FIN_WAIT2 | 发送ACK,进入TIME_WAIT | |
FIN _WAIT2 | 发送ACK,进入TIME_WAIT | |||
CLOSING | 进入TIME_WAIT | |||
TIME _WAIT | 等待超过2MSL,进入CLOSED | |||
CLOSE _WAIT | 发送FIN,进入LAST_ACK | |||
LAST _ACK | 进入CLOSED |
TCP可靠传输
TCP通过3次握手建立连接,4次挥手拆除连接,并通过以下3个机制保障传输的可靠性。这3个机制是:
- 校验。发送端发送每个数据段都进行校验,接收端校验失败的数据段直接丢弃;
- 序列号。发送端对每个待发送字节记序列号,并在数据段中表明本段第一个字节的序列号。接收端在接收窗口(RWND)对接收字节进行排序。
- 连续ARQ。接收端对接收窗口内的字节进行排序,并通过连续ARQ机制,对排序好的字节进行一次性确认。
- 超时重传。发送端对发送窗口(SWND)内每个数据段计时器。若计时器超时前未收到某一个数据段的确认,将重传该数据段。
- 滑窗。发送端动态调整发送窗口的大小,使其匹配接收窗口和拥塞控制要求。滑窗不是严格意义上可靠传输的手段,但由于滑窗能够使得发送方不会因为发送过快,而接收方缓存太少导致丢包,宏观意义上也有利于可靠传输。
缓存区与窗口
SBUFF和RBUFF可以看作一个环形FIFO队列。在发送端,发送程序把字节按顺序推入SBUFF指针所在位置,TCP从SWND首部取出字节封装发送;在接收端,TCP把接收到的字节推入RWND尾部,接收程序从RBUFF指针处取出字节。以下以发送端发送“HELLOHOWAREYOU”为例子介绍TCP发送和接收缓存。假设MSS(最大数据段尺寸)= 1 Byte,即每个数据段携带1个字节。
发送缓存SBUFF和发送窗口SWND
SWND是在SBUFF内定义的一个区域。SWND的作用是保留尚未确认的字节准备重发。发送端TCP维护3个指针值,包括SWND首部、SWND尾部、SBUFF指针。SWND内的字节已经或正在发送,但未收到确认的字节;从SWND右侧到SBUFF指针的字节是应用程序推入SBUFF等待发送的字节。
- 发送端应用程序把待发送字节加入到SBUFF指针处(即将加入的字节是“YOU”)。
- 在收到SWND中部分字节的确认后(这里等待确认的是“HOW”),TCP将SWND内,从SWND首部一直到被确认字节的数据段删除,同时SWND首部和尾部向SBUFF后移。例子中若“HOW”被确认,则SWND内将后移至“ARE”,这里假设SWND大小没有发生变化。
- SWND内有新的未发送字节,TCP立即发送发送并等待确认。
接收缓存RBUFF和接收窗口RWND
同样,RWND是RBUFF的一个区域。RWND作用主要有两个:拥塞通知和排序。接收端TCP维护3个指针值,包括RBUFF指针值,RWND首部,RWND尾部。RWND首部记录着第一个未收到字节序列号的下一位,RWND尾部记录着从RBUFF指针到RWND首部的字节是缓存区等待TCP送往接收程序的字节;RWND内的字节是等待排序并发送确认的字节。
- 检查RWND内是否有从当前ACK值开始的连续字节,有则后移RWND,直到没有连续已排序字节,更新当前ACK值并适时发送ACK确认。这里“HELLO”已按顺序接收,RWND后移。
- 接收端TCP检查RWND的余量,有则接收对应字节数,无则拒绝接收。这里TCP检查RWND余量为3,传入“OW”,并等待接收“H”。
- 接收端应用程序从RBUFF指针处接收字节并从RBUFF删除。在这里接收端从RBUFF接收了“H”并从RBUFF中删除。RBUFF指针后移。
一般情况下,只有当接收缓存区满了以后,tcp才一次性提交所有数据到应用层。例外情况是接收到psh字段置1的数据段,这要求tcp立即提交缓存区所有数据。但目前很多实现都不会等到缓存区满才进行提交,而是有则提交,因此该标志用途较少。
TCP确认机制
TCP是面向字节的,采用连续ARQ确认机制,由接收方对收到的字节进行确认,确保传输的完整性。
对于SWND内没有发送的字节,TCP设定重传计时器,然后一次性发送完,然后进行下一步操作:
- 若收到确认信息,(ack-1)在SWND内,则认为SWND内这一部分的字节均已被完整接收,可以从SWND删除;
- 若收到确认信息,(ack-1)不在SWND内,则认为该确认是无效的,直接忽略;
- 若没有收到任何确认信息,SWND内部分字节的重传计时器超时,则重置重传计时器,并重发这部分字节。
接收端需要对接收的所有字节进行确认,但不需要对每一个字节都发送一次确认消息。根据连续ARQ要求,当发送方收到一个字节的确认,即可认为该字节本身和其之前的字节都已经被完整接收;接收端也不需要立即确认收到的字节,RFC建议可延迟不超过0.5s。
拥塞窗口(CWND)
接收窗口余量变小,一是发送端发送速度过快,接收端性能不足;二是由于RWND没完成排序。第一种情况下,接收端一方面在发送确认时,把当前RWND余量作为CWND发送给发送端,由发送端调整使得SWND不大于CWND;另一方面将丢弃新接收的字节,由发送端重传定时器超时后重新发送,并启用慢启动。第二种情况下,接收端在收到非等待字节时立即发送等待字节的确认,发送端收到3个同样的确认后启动快重传和快恢复。
TCP拥塞控制
TCP对传输的控制体现在每流和全局两个方面。通过校验码,序列号和确认,TCP确保每流的成功传输。通过拥塞控制,TCP确保在传输路径上的整体效率最大化。
拥塞控制主要实施在发送端,通过2个值和4个机制实现。2个值分别是拥塞窗口CWND,慢启动阈值SSTHRESH;4个机制分别是:慢启动,拥塞避免,快重传,快恢复。以下说明的单位是数据段字节数。
慢启动
发送端在开始发送数据前,首先设置CWND为1,接着按照以下规则计算CWND:
若CWND<SSTHRESH,则每收到1个连续数据段的ACK,CWND+1;
若CWDN>SSTHRESH,则每收到CWND内所有数据段的ACK时,CWND+1。
在CWND<SSTHRESH之前CWND以指数增长,TCP迅速提高发送速率;在CWND>=SSTHRESH,CWND以线性增长,缓慢提高发送速率。
拥塞避免
当重传计时器超时时,发送端设置SSTHRESH=CWND/2,CWND=1
发送端的数据段无法送达或者接收端的ACK无法返回,使发送端TCP认为网络出现了拥塞。这时发送端TCP重新开始慢启动,并更早使CWND进入线性增长。
快重传
当接收端收到了一个非等待的字节时,将立即向发送端发送一个确认,ACK为所等待字节的下一个序列号,请求发送端发送丢失的字节。如果接下来的字节仍然不是所期待的字节,接收端将继续发送相同的确认。
快恢复
当发送端收到3个确认,ACK均为发送窗口的首部Seq,将认为该数据段已经在网络中丢失。发送端将:
- 立即向接收端重新发送该数据段;
- SSTHRESH=CWND/2;
- CWND=SSTHRESH。
由于CWND并没有像拥塞避免一样重新开始慢启动,发送速度只是减半。而且CWND=SSTHRESH,其增长速度为线性增加,降低了网络拥塞的可能性。
全局同步
TCP的拥塞控制虽然单独看起来很完善,但在全局应用中会有很大问题,这就是全局同步(Global Synchronization)。全局同步的含义是,多个TCP流同时慢启动,又同时拥塞避免。
假设在T=0时,一个终端同时对另一个终端发起10个TCP连接。这里不考虑TCP、IP头部消耗,SSTHREST、RWND足够大,MSS=1KB,网络带宽为80KB/s,RTT=2s,重传计时器也是1s。
T=1,CWND均为1,每个TCP流占用1KB/s,流量=10KB/s;
T=2,CWND均为2,每个TCP流占用2KB/s,流量=20KB/s;
T=3,CWND均为4,每个TCP流占用4KB/s,流量=40KB/s;
T=4,CWND均为8,每个TCP流占用8KB/s,流量=80KB/s;
T=5,CWND均为16,每个TCP流占用16KB/s,流量=160KB/s。但带宽只有80KB/s,出现了拥塞。这意味着50%包要被丢弃。这只能由TCP发送端重传计时器超时后重新发送。这里假设所有TCP流都有数据段被丢弃,即所有TCP连接均需要重传。根据拥塞避免,所有TCP流的CWND重置为1。因此:
T=6,CWND均为1,每个TCP流占用1KB/s,流量=10KB/s;
T=7,CWND均为2,每个TCP流占用2KB/s,流量=20KB/s;
T=8,CWND均为4,每个TCP流占用4KB/s,流量=40KB/s;
T=9,CWND均为8,每个TCP流占用8KB/s,流量=80KB/s;
T=10,CWND均为16,每个TCP流占用16KB/s,流量=160KB/s。再一次发生拥塞避免。这个过程将一直循环。
可以看到,全局同步很大程度上将影响网络的传输效率。超过1半时间,网络的利用率都低于50%,还有一些时间网络过载。
目前较为合理应对全局同步的方式,就是在网络中部署RED(随机早期检测/随机早期丢弃),在每个TCP流接近链路总带宽的百分比时,随机丢弃该流的部分数据段。这样可以使TCP进入快重传和快恢复,提早进入线性增长,控制TCP流速率。