掌握TCP的头部详细构成对了解整个网络体系结构至关重要。
1.MSS:Max Segment Size
- 设计目的:防止 IP 层报文分段。即尽量每个使Segment报文段携带更多的数据,以减少头部空间占用比率,同时防止 Segment 被某个设备的 IP 层基于 MTU 拆分,因为 MTU 拆分效率低下。
- 默认MSS:536 字节(默认 MTU576 字节,20 字节 IP 头部,20 字节 TCP 头部)
- 注意:握手阶段会协商 MSS 大小,MSS又更具体的分为发送方最大报文段 SMSS与接收方最大报文段 RMSS,下图为Wireshark抓包得到的示例:
如上图所示展示了一个详细32bytes的TCP报文头部,20标准头部+12options,其中可以清楚的看到MSS此处设置为了1440 bytes。上图仅为三次握手中的发起请求的SYN报文,为了更加清楚的认识对报文头部的利用,下面的图片为服务器发送过来的SYN-ACK报文:
2.重传和确认
简单了解其实tcp协议设计实现也是一步步迭代而来的,就拿确认机制来说初始的包确认是串行的,即只有等待到上一个报文的确认到来之后才会继续发下一个报文段。而后期为了提升并发又采取接受缓冲区管理的方法,可以实现同时发送多个包的要求(也就是说不必再等待包确认的到来)。
(1)认识序列号
需要清楚的知道设计Sequence 序列号/Ack 序列号的根本原因是为了解决应用层字节流的可靠发送,跟踪应用层的发送端数据是否送达以及确保接收端有序的接收到字节流。最重要的是要注意序列号的值针对的是字节而不是报文,就像上图所示一样,清楚的展示了序列号的计算方法。
(2)什么是序列号回绕
序列号回绕即PAWS (Protect Against Wrapped Sequence numbers)。由seq=32bits可知最多只能确认4G的字节流,再发送4G以后的数据时就需要重用最开始使用的那一部分序列号。这种情况会造成一个问题就是若要复用的部分对应的确认包若还没有到来,贸然使用就会造成来了确认序列号之后无法确认是前面的还是现在包的确认包,造成歧义,为了解决之后问题,来瞅一瞅TCP报文是怎样解决的?
很简单,TCP设计者通过为报文加上时间戳( timestamps),来区别报文,从而解决了序列号环绕的问题,该位在头部选项处,具体示例如下图:
(3)超时重传定时器RTO的计算
首先要辨析RTT(Round-trip time) 的计算方式以及其具体计算优劣,还是看下图:
有上面两种计算RTT的方法,由于总是存在以上两种情况,所以RTT的取舍就至关重要了,否则就是要么太小,要么太大,比较幸运的是我们可以借助上面解决了序列号回绕问题的timestamp 选项正确得到RTT。而对于RTO 应当略大于 RTT,若太小则可能会造成资源浪费(RTT快要结束时来也有可能),若太大的话会导致不能及时重传。注意RTO 应当更平滑,具体计算方法过于复杂,目前没必要掌握,知道OS在具体计算RTO时并不是仅仅似上面这样,相对要复杂的多即可。
3.滑动窗口
(1)发送窗口
其具体划分如下图所示:
(2)接收窗口
显而易见的是接收窗口约等于对端发送窗口。这两种模型的建立基于通信时MSS 不产生影响,窗口不变,从而确定出的滑动窗口的大小。
(3)操作系统缓冲区与滑动窗口的关系
- 当应用层没有及时读取缓存时,会逐渐使得滑动窗口大小变为0,即造成窗口关闭现象
- 收缩窗口导致的丢包,即当系统资源紧张时,内核会调整缓冲区的大小降低资源开销,若操作不当会导致发送方无法及时更新滑动窗口的大小,进而造成丢包,为解决这种现象常建议的措施如下:
一、先收缩窗口,再减少缓存;
二、窗口关闭后,定时探测窗口大小
(4)Linux中对TCP缓冲区的调整方式
- net.ipv4.tcp_rmem = 4096
读缓存最小值(4096)、默认值(87380)、最大值(6291456) (单位:字节),覆盖 net.core.rmem_max - net.ipv4.tcp_wmem = 4096
写缓存最小值(4096)、默认值(16384)、最大值(4194304) (单位:字节),覆盖 net.core.wmem_max - net.ipv4.tcp_moderate_rcvbuf = 1
开启自动调整缓存模式
4.如何减少小报文提高网络效率?
首先了解SWS(Silly Window syndrome)糊涂窗口综合症,指服务器来不及处理TCP接收缓冲区的报文,进而使得一次比一次滑动窗口小,而每一个TCP报文都有20bytes的固定头部,当body太小时就会得不偿失,极其影响效率。故需要想办法尽可能的减少小报文提高网络效率。
(1) SWS 避免算法
- 接收方
窗口边界移动值小于 min(MSS, 缓存/2)时, 通知窗口为 0 - 发送方
Nagle 算法,即没有已发送未确认报文段时,立刻发送数据;存在未确认报文段时,直到:1-没有已发送未确认报文段,或者 2-数据 长度达到 MSS 时再发送
TCP_NODELAY 用于关闭 Nagle 算法
(2) TCP delayed acknowledgment (TCP延迟确认)
- 当有响应数据要发送时,ack 会随着响应数据立即发送给对方.
- 如果没有响应数据,ack的发送将会有一个延迟,以等待看是否有响应数据可以一起发送。
- 如果在等待发送 ack 期间,对方的第二个数据段又到达了,这时要立即发送 ack
(3) Linux 上更为激进的”Nagle”:TCP_CORK
粗略了解就好,其实也挺简单,只要了解 sendfile 零拷贝技术就明白了,此处更为激进的”Nagle”就是指Nagle+sendfile零拷贝技术。
sendfile零拷贝技术: 若知道操作系统要将文件的内容从磁盘上读取到用户空间时需要基于内核空间的临时映射区来一把临时映射,然后通过一个转接将数据传到用户空间对应的内存。此处的sendfile零拷贝技术就是期望不要将数据在映射到用户空间了而是直接拷贝到TCP的发送缓冲区,这样就会减少一次拷贝,很大程度就提高了传输效率。