《计算机网络传输层 TCP协议》
《计算机网络传输层 TCP协议》
1. TCP 协议特点
-
在 IP 协议之上 ,解决网络通讯可依赖问题
- 点对点(不能广播,多播),面向连接
- 双向传递 (全双工)
- 字节流传输:打包成报文段、保证有序接收、重复报文自动丢弃
- 缺点: 不维护应用报文的边界(对比HTTP、GRPC)
- 优点: 不强制要求应用必须离散的创建数据块、不限制数据块的大小
- 流量缓冲: 解决速度不匹配问题
- 可靠的传输服务 (保证可达、丢包时通过重发时延实现可靠性)
- 拥塞控制
-
既然TCP是面向连接的,那么如何标识一个连接?
-
通过TCP的四元组(源地址,源端口,目的地址,目的端口)
- 所以对于 IPV4 地址,单主机最大TCP连接数为\[2^{32+16+32+16} \]
- 所以对于 IPV4 地址,单主机最大TCP连接数为
-
2. TCP协议的任务
- 主机内的进程寻址(通过端口号)
- 创建、管理、终止连接
- 处理并将字节 (8 bit) 流 打包成报文段 ( 如IP报文 )
- 传输数据
- 保持可靠性与传输质量
- 流量控制与拥塞避免
3. TCP Segment 报文段
每个TCP报文段由固定的20Byte头部组成,TCP报文头部 选项可以跟在固定标头之后。 带有标头,使其最多可以标记 65535 个数据字节。
-
16位源端口号和16位目的端口号。
-
32位序号:一次TCP通信过程中某一个传输方向上的字节流的每个字节的编号,通过这个来确认发送的顺序
-
32位确认号:用来响应TCP报文段,给收到的TCP报文段的序号加1,三握时还要携带自己的序号。
-
4位头部长度:标识该TCP头部有多少个4字节,共表示最长15*4=60字节。同IP头部。
-
6位保留。6位标志。URG(紧急指针是否有效)ACK(表示确认号是否有效)PSH(提示接收端应用程序应该立即从TCP接收缓冲区读走数据)RST(表示要求对方重新建立连接)SYN(表示请求建立一个连接)FIN(表示通知对方本端要关闭连接)
-
16位窗口大小:TCP流量控制的一个手段,用来告诉对端TCP缓冲区还能容纳多少字节。
-
16位校验和:由发送端填充,接收端对报文段执行CRC算法以检验TCP报文段在传输中是否损坏。
-
16位紧急指针:一个正的偏移量,它和序号段的值相加表示最后一个紧急数据的下一字节的序号。
4. TCP 三次握手及状态变迁
4.1 为什么TCP不能两次握手进行连接?
TCP 连接需要完成两项工作一是做好发送数据前的准备工作(即双方都知道对方准备好了) 二是完成序列号(sequence number )的同步,这个序列号在握手的过程中被发送和确认
根据上述如果进行两次连接 Server 在接收到Client的SYN请求时直接进入到Establishment状态,并且给Client发送 SYN和ACK 包。但是这个SYN+ACK 包在传输的过程中可能丢失,因此Client无法准确的知道Server是否建立起连接。因此Client会忽略Server发送过来数据包,只等连接确认包。而Server在发送数据包分组超时后会重复发送超时的数据包,这样就造成了死锁。
上述回答是通过一个博客看到了,但是我从RFC793 [page 32] 文档看到了如下的说明:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion. To deal with this, a special control message, reset, has been devised. If the receiving TCP is in a non-synchronized state (i.e., SYN-SENT,SYN-RECEIVED), it returns to LISTEN on receiving an acceptable reset. If the TCP is in one of the synchronized states (ESTABLISHED,FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), it aborts the connection and informs its user. We discuss this latter case under "half-open" connections below.
大概意思就是说三次握手是为了防止旧的重复初始化连接造成混乱
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
4.2 TCP 在Handshake中如果第三次发送包发生了丢失会怎么样?
-
服务器角度: 如果第三次ACK确认包丢失。Server的状态为SYN_REVD ,并根据TCP超时重传机制,会等待 3秒、6秒、12秒 后重新发送SYN+ACK包,以便Client重新发送ACK包。 而Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5.
如果重发指定次数之后,仍然未收到 client 的ACK应答,那么一段时间后,Server自动关闭这个连接。
-
客户端角度:因为客户端发送完ACK包已经进入了Establishment状态。客户端向服务端发送数据,服务端会以RST包进行相应,客户端就能感知Server的链接错误了。
5. TCP的可靠传输
5.1 TCP的流与报文段
-
流分段的依据
-
MSS: 防止IP层分段 IP层分段的效率低下
-
流控制: 控制端的接收能力
-
-
MSS(Max Segment Size):仅指TCP承载数据,不包含TCP的头部长度
-
MSS选择的目的
- 尽量每个Segment携带更多的数据 以减少TCP头部空间占用率
- 防止Segment被某个设备的IP层基于MTU拆分
-
默认 MSS 536字节 (默认MTU 576个字节,20字节IP头部,20字节TCP头部)
5.2 TCP是如何实现可靠传输的?
-
TCP通过序号机制和重传机制来实现TCP字节的可靠传输的
-
Sequence 序列和 ACK确认号设计目的
-
跟踪应用层的发送端数据是否达到
-
确定接收端有序的接收到字符流
-
-
序列号的值针对的是
字节
而不是报文
5.2.1 序列号存在的问题
在TCP报文头部序列号(Sequence Number)所占32bit 也就是最多能表达的字节也就是2^32 个也就是4GB,当如果一次TCP连接传输的数据超过4G时,在1-4G传输过程中,如果数据报传输有延迟晚到底,可能会让接收端分不清那个是先用这个序列号的,那个报文是后用这个序号的
这个也就是PAWS问题(Protect Against Wrapped Sequence numbers )防止序列号回绕
5.3 RTT时间与RTO时间
下图是RTT的图示,简单的来说就是一个报文从发送到接收到响应的时间,但是下面是理想的情况,现实中的计算比这复杂多了
RTO是超时重传时间,RTO设置应该比RTT略大,如果设置太小,可能数据报已经被ACK了,但是由于超时重发时间设置的太短,就又重发了,重发了本应该不用重发的数据报。如果设置的太大就会导致传输的效率太低,如果ACK数据报丢失,需要很长时间来进行重发。
5.4 滑动窗口
6. TCP 拥塞控制
6.1 慢启动
cwnd: 拥塞窗口大小
rwnd: 接收方窗口大小
慢启动的思想就是为发送方增加了一个拥塞窗口记为cwnd,拥塞窗口指的是接收到ACK后,发送端还能发送最大的MSS数,发送方的窗口大小 = Min {cwnd,rwnd}。
慢启动算法就是,每收到一个ACK (eg.也就是经过一个RTT) cwnd的大小*2 ,比如最开始cwnd的大小是 initcwnd,当接收到该报文的ACK ,cwnd的大小就变为initcwnd * 2 。下次发送报文段的数量就是 initcwnd *2 ,再接收到 initcwnd * 2 报文段的ACK后,那么接着发送窗口的大小就是 initcwnd * 4 ,这是一种指数关系
n 指的是RTT的次数
6.2 拥塞避免
慢启动阈值:ssthresh
当cwnd<ssthresh时,拥塞窗口使用慢启动算法,按指数级增长。 当cwnd="">ssthresh时,拥塞窗口使用拥塞避免算法,按线性增长。
拥塞避免算法每经过一个RTT,拥塞窗口增加initcwnd
。
当到达设置的阈值时发送窗口的大小就每收到一个ACK,cwnd+=initcwnd ,当出现丢包的情况是就立马将发送窗口的大小设置为 initcwnd.
当发生拥塞的时候(超时或者收到重复ack),RFC5681认为此时ssthresh需要置为没有被确认包的一半,但是不小于两个MSS。此外,如果是超时引起的拥塞,则cwnd被置为initcwnd。
超时重传对传输性能有严重影响。原因之一是在RTO阶段不能传数据,相当于浪费了一段时间;原因之二是拥塞窗口的急剧减小,相当于接下来传得慢多了。
6.3 快速重传
有时候拥塞比较轻微,只有少量包丢失,后续的包能够正常到达。当后续的包到达接收方时,接收方会发现其Seq号比期望的大,所以它每收到一个包就Ack一次期望的Seq号,以此提醒发送方重传。当发送方收到3个或以上重复确认(Dup Ack)时,就意识到相应的包已经丢了,从而立即重传它。这个过程称为快速重传。
为什么要规定凑满3个呢?这是因为网络包有时会乱序,乱序的包一样会触发重复的Ack,但是为了乱序而重传没有必要。由于一般乱序的距离不会相差太大,比如2号包也许会跑到4号包后面,但不太可能跑到6号包后面,所以限定成3个或以上可以在很大程度上避免因乱序而触发快速重传。
还有一个问题,如下图:
如果2号和3号包都丢失了,但是后面4,5,6,7号都正常收到了,并触发了三次ack = 2。在重传了2号包之后该传哪个包那,是全部需要重传还是只传2号包?
为了解决这种问题,TCP在发送重复的Ack包的时候,会告诉接收方收到的已经收到包的序号,如下图:
这样发送方就知道该重传哪个包了,这种方式被称为选择性确认(Selective Acknowledgement)。
6.4快速恢复
如果在拥塞阶段发生了快速重传就没有必要像超时重传那样处理拥塞窗口了,因为此时的拥塞并不是很严重。RFC5681建议此时的慢启动阈值ssthreh设置为没有被确认包的1/2,但是不小于2个MSS。拥塞窗口设置为慢启动阈值加3个MSS。这个过程被称为快速恢复。
7.1 TCP的四次挥手
TCP的四次挥手如下图所示
7.1 为什么TCP建立连接需要三次握手,而释放连接需要四次?
当处于Listen状态的服务器接收到一个SYN报文后,可以将ACK(应答作用)报文和SYN (请求建立连接) 报文放到一个报文中进行发送。
但是在关闭TCP连接时,对方给你发送一个FIN报文,这仅仅表示对方没有数据给你发送了,但是并不代表你没有数据给对方发送了。所以先给对方发送一个ACK报文,当发送完数据过后在发送一个FIN报文表示告诉对方你同意关闭连接了。所以在通常情况下ACK报文和FIN报文是分开发送的。
7.2 为什么TIME_WAIT 要等待2 * MSL秒后才释放连接?
-
可靠的实现全双工通信的终止
TCP协议在关闭连接的4次握手中,最终ACK必须由发起终止的主机(A端)发送,如果这个ACK丢失,那么被终止端会重新发送FIN包。因此A端必须维护TIME_WAIT 允许他最终发送FIN包。如果A端不维护TIME_WAIT 而是直接处于Closed状态,那么A端会给B相应一个RST,B端在收到会解析成一个错误(在Java中会抛出异常)
-
允许老的重复字节在网络中消失
TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。
</ssthresh时,拥塞窗口使用慢启动算法,按指数级增长。>