哈喽大家好,我是咸鱼。

今天分享一篇大佬的文章,作者:卡瓦邦噶!

文章链接:https://www.kawabangga.com/posts/5845

教科书介绍的 TCP 内容通常比较基础:包括三次握手,四次挥手,数据发送通过收到 ACK 来保证可靠传输等等。当时我以为已经学会了 TCP,但是后来在工作中,随着接触 TCP 越来越多,我发现很多内容和书上的不一样——现实世界的 TCP 要复杂一些。

我们从一个简单的 HTTP 请求开始。发送一个简单的 HTTP 请求,tcpdump 抓包如下:

04:13:49.438293 IP foobarhost.53422 > 104.244.42.65.http: Flags [S], seq 637381086, win 64240, options [mss 1460,sackOK,TS val 2468293419 ecr 0,nop,wscale 7], length 0
04:13:49.577825 IP 104.244.42.65.http > foobarhost.53422: Flags [S.], seq 1622592001, ack 637381087, win 65535, options [mss 1460], length 0
04:13:49.578004 IP foobarhost.53422 > 104.244.42.65.http: Flags [.], ack 1, win 64240, length 0
04:13:49.578644 IP foobarhost.53422 > 104.244.42.65.http: Flags [P.], seq 1:38, ack 1, win 64240, length 37: HTTP: POST /apikey=1&command=2 HTTP/1.0
04:13:49.579110 IP 104.244.42.65.http > foobarhost.53422: Flags [.], ack 38, win 65535, length 0
04:13:49.702633 IP 104.244.42.65.http > foobarhost.53422: Flags [P.], seq 1:204, ack 38, win 65535, length 203: HTTP: HTTP/1.0 400 Bad Request
04:13:49.702662 IP foobarhost.53422 > 104.244.42.65.http: Flags [.], ack 204, win 64037, length 0
04:13:50.702170 IP 104.244.42.65.http > foobarhost.53422: Flags [F.], seq 204, ack 38, win 65535, length 0
04:13:50.702783 IP foobarhost.53422 > 104.244.42.65.http: Flags [F.], seq 38, ack 205, win 64037, length 0
04:13:50.703525 IP 104.244.42.65.http > foobarhost.53422: Flags [.], ack 39, win 65535, length 0

第一个和书上不一样的地方是,TCP 结束连接不是要 4 次挥手吗?为什么这里只出现了 3 次?

04:13:50.702170 IP 104.244.42.65.http > foobarhost.53422: Flags [F.], seq 204, ack 38, win 65535, length 0
04:13:50.702783 IP foobarhost.53422 > 104.244.42.65.http: Flags [F.], seq 38, ack 205, win 64037, length 0
04:13:50.703525 IP 104.244.42.65.http > foobarhost.53422: Flags [.], ack 39, win 65535, length 0

回顾 TCP 的包结构,FIN 和 ACK 其实是不同的 flags,也就是说,理论上我可以在同一个 Segment 中,即可以设置 FIN 也可以同时设置 ACK。

所以如果在结束连接的时候,客户端发送 FIN,这时候服务端一看:“正好我也没有东西要发送了。”于是,除了要 ACK 自己收到的 FIN 之外,也要发送一个 FIN 回去。那不如我一石二鸟,直接用一个包好了。

既然 FIN 可以附带去 ACK 自己收到的 FIN,那么数据是否也可以附带 ACK?也是可以的。

Delay ACK

TCP 是全双工的,意味着两端都可以同时向对方发送数据,而两端又需要分别去 ACK 自己收到的数据。

TCP 的一端在收到数据之后,反正马上也要发送数据回去,与其发送两个包:一个 ACK 和一个数据包,不如不立即发送 ACK 回去,而是等待一段时间——我反正一会要发送数据给你,等到那时候,我再带上 ACK 就好啦。这就是 Delay ACK。



Delay ACK 可以显著降低网络中纯 ACK 包的数量,大概 1/3. 纯 ACK 包(即 payload length 是 0 ),有 20 bytes IP header 和 20 bytes TCP header。

Delay ACK 的假设是:如果我收到一个包,那么应用层会需要对这个包做出回应,所以我等到应用的回应之后再发出去 ACK。这个假设是有问题的。而且现实是,Delay ACK 所造成的问题比它要解决的问题要多。(下文详解)

Nagle 算法

现在再考虑这样一个问题:像 nc 和 ssh 这样的交互式程序,你按下一个字符,就发出去一个字符给 Server 端。每通过 TCP 发送一个字符都需要额外包装 20 bytes IP header 和 20 bytes TCP header,发送 1 bytes 带来额外的 40 bytes 的流量,不是很不划算吗?

除了像这种程序,还有一种情况是应用代码写的不好。TCP 实际上是由 Kernel 封装然后通过网卡发送出去的,用户程序通过调用 write syscall 将要发送的内容送给 Kernel。有些程序的代码写的不好,每次调用 write 都只写一个字符(发送端糊涂窗口综合症)。如果 Kernel 每收到一个字符就发送出去,那么有用数据和 overhead 占比就是 1/41.

为了解决这个问题,Nagle 设计了一个巧妙的算法 (Nagle’s Algorithm),其本质就是:发送端不要立即发送数据,攒多了再发。但是也不能一直攒,否则就会造成程序的延迟上升。

算法的伪代码如下:

if there is new data to send then
if the window size ≥ MSS and available data is ≥ MSS then
send complete MSS segment now
else
if there is unconfirmed data still in the pipe then
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if

简单来说,就是如果要发送的内容足够一个 MSS 了,就立即发送。否则,每次收到对方的 ACK 才发送下一次数据。

Delay ACK 和 Nagle 算法

这两个方法看似都能解决一些问题。但是如果一起用就很糟糕了。

假设客户端打开了 Nagle’s Algorithm,服务端打开了 Delay ACK。这时候客户端要发送一个 HTTP 请求给服务端,这个 HTTP 请求大于 1 MSS,要用 2 个 IP 包发送。于是情况就变成了:

  • Client: 这是第一个包
  • Server:… (不会发送 ACK,直到 Server 想发送数据给 Client,但是这里因为 Server 没有收到整个 HTTP 请求内容,所以 Server 不会发送数据给 Client)
  • Client: … (因为 Nagle 算法,Client 在等待对方的 ACK,然后再发送第二个包的数据)
  • Server: 好吧,我等够了,这是 ACK
  • Client: 这是第二个包

这里有一个类似死锁的情况发生。会导致某些情况下,HTTP 请求有不合理的延迟。

再多说一点有关的历史,我曾经多次在 hackernews 上看到 Nagle 的评论(Nagle 亲自解释 Nagle 算法!1,2)。大约 1980s,Nagle 和 Berkeley 为了解决几乎相同的问题,发明了二者。Berkeley 的问题是,很多用户通过终端共享主机,网络会被 ssh 或者 telnet 这样的字符拥塞。于是用 Delay ACK,确实可以解决 Berkeley 的问题。但是 Nagle 觉得,他们根本不懂问题的根源。如果他当时还在网络领域的话,就不会让这种情况发生。可惜,他当时改行去了一家创业公司,叫 Autodesk。

解决方法是关闭 Delay ACK 或者 Nagle’s Algorithm。

配置方法

关闭 Nagle’s Algorithm 的方法:可以给 socket 设置 TCP_NODELAY. 这样程序在 write 的时候就可以 bypass Nagle’s Algorithm,直接发送。

关闭 Delay ACK 的方法:可以给 socket 设置 TCP_QUICKACK,这样自己(作为 server 端)在收到 TCP segment 的时候会立即 ACK。实际上,在现在的 Linux 系统默认就是关闭的。

前面文章提到过:如果在收到对方的第二次包SYN+ACK之后很快要发送数据,那么第三次包ACK可以带着数据一起发回去。这在Windows和Linux中都是比较流行的一种实现。但是数据的大小在不同实现中有区别。

如果我们关闭 TCP_QUICKACK ,就可以看到几乎每一次 TCP 握手,第三个 ACK 都是携带了数据的。

int off = 0;
setsockopt(sockfd, IPPROTO_TCP, TCP_QUICKACK, &off, sizeof(off));
04:13:32.240213 IP foobarhost.57010 > 104.244.42.65.http: Flags [S], seq 1515096107, win 64240, options [mss 1460,sackOK,TS val 2468276221 ecr 0,nop,wscale 7], length 0
04:13:32.383742 IP 104.244.42.65.http > foobarhost.57010: Flags [S.], seq 1620480001, ack 1515096108, win 65535, options [mss 1460], length 0
04:13:32.384536 IP foobarhost.57010 > 104.244.42.65.http: Flags [P.], seq 1:38, ack 1, win 64240, length 37: HTTP: POST /apikey=1&command=2 HTTP/1.0

TCP 中的 Delay ACK 和 Nagle 算法的更多相关文章

  1. TCP Nagle算法&&延迟确认机制

    TCP Nagle算法&&延迟确认机制 收藏 秋风醉了 发表于 3年前 阅读 1367 收藏 0 点赞 0 评论 0 [腾讯云]买域名送云解析+SSL证书+建站!>>> ...

  2. TCP系列29—窗口管理&流控—3、Nagle算法

    一.Nagle算法概述 之前我们介绍过,有一些交互式应用会传递大量的小包(称呼为tinygrams),这些小包的负载可能只有几个bytes,但是TCP和IP的基本头就有40bytes,如果大量传递这种 ...

  3. TCP-IP详解:Nagle算法

    在使用一些协议通讯的时候,比如Telnet,会有一个字节字节的发送的情景,每次发送一个字节的有用数据,就会产生41个字节长的分组,20个字节的IP Header 和 20个字节的TCP Header, ...

  4. Nagle算法&&延时确认

    数据流分类 成块数据 交互数据   Rlogin需要远程系统(服务器)回显我们(客户)键入的字符 数据字节和数据字节的回显都需要对方确认 rlogin 每次只发送一个字节到服务器,而Telnet 可以 ...

  5. 即使关闭了nagle算法,粘包依旧存在

    JAVA高级架构 https://mp.weixin.qq.com/s?src=11&timestamp=1542107581&ver=1242&signature=OoktA ...

  6. TCP Nagle算法以及延迟确认(即延迟回复ACK)的学习

    TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认.为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据. (一个连TCP接会 ...

  7. nagle 算法 tcp nodelay 以及 quick ack分析

    后面详细分析 先上传 之前总结查看源码后的总结 Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段.所谓"小段",指的是小于MSS尺寸的数据块,所谓"未被确 ...

  8. TCP之Nagle算法与延迟ACK

    (一)Nagle算法 为了减少网络中小分组的数目,减少网络拥塞的情况.Nagle算法要求在一条TCP连接上最多只能有一个未被确认的未完成小分组,在该分组ACK到达之前不能够发送其他的小分组,发送端需要 ...

  9. TCP之Nagle算法&&延迟ACK

    1. Nagle算法: 是为了减少广域网的小分组数目,从而减小网络拥塞的出现: 该算法要求一个tcp连接上最多只能有一个未被确认的未完成的小分组,在该分组ack到达之前不能发送其他的小分组,tcp需要 ...

  10. delayed ack与nagle's算法

    delayed ack和nagles算法都能减少tcp传输过程中的小数据包的问题 tcpip卷二25章中提到tcp为每个连接建立7个定时器: 1.connection established 2.re ...

随机推荐

  1. 面试谈薪4点博弈策略,将20k谈到28k

    薪资谈判本质上是一种博弈,无论是表面谈得好还是实质上谈得好,都需要掌握一些策略 面试薪资怎么谈,您目前的薪资是20k,如果您想要提高到28k,那么请花两分钟看完以下内容.薪资谈判本质上是一种博弈,无论 ...

  2. 分享四个实用的vue自定义指令

    v-drag 需求:鼠标拖动元素 思路: 元素偏移量 = 鼠标滑动后的坐标 - 鼠标初始点击元素时的坐标 + 初始点击时元素距离可视区域的top.left 将可视区域作为边界,限制在可视区域里面拖拽 ...

  3. CH59X/CH58X/CH57X sleep模式下串口唤醒收发数据

    整体程序逻辑: 下方的具体程序及使用是基于CH592进行的 SLEEP模式睡眠唤醒是由协议栈管理的,还在睡眠时,无法接收到数据. 已经通过使能HAL_SLEEP开启睡眠.如果需要在睡眠时实时接收串口传 ...

  4. ch57x/ch58x开启仿真

    本次使用的平台是MounRiver Studio,使用的是WCH的CH582m与WCH-LinkE 仿真之前确保LinkE处在Risc-V模式下  空闲时蓝灯常灭 Step1:首先通过ISP工具开启两 ...

  5. Windows、MacOs上 gif 录像软件 LICEcap

    LICEcap 官网:https://www.cockos.com/licecap/ 适用于 windows macos 我自己保持的一版,下载地址:https://files-cdn.cnblogs ...

  6. Linux-编译源码时所需提前安装的常用依赖包列表

    编译源码时所需提前安装的常用依赖包列表: yum -y install gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel f ...

  7. ABC 332

    ABCDF 都赛时做出来了. E \(\displaystyle\dfrac{1}{D}\sum_{i=1}^D (x_i-\overline{x})^2=\dfrac{1}{D}(\sum_{i=1 ...

  8. 看似简单的input框输入竟然异常卡顿,记一个日常性能问题的排查思路

    壹 ❀ 引 我们公司产品主要提供企业项目管理服务,那么自然有配套的desk工单管理系统,用于搜集客户bug以及相关问题反馈.有一天我在测试功能时碰巧发现了一个bug,所以就想着提一个工单记录下方便日后 ...

  9. NC22600 Rinne Loves Dynamic Graph

    题目链接 题目 题目描述 Rinne 学到了一个新的奇妙的东西叫做动态图,这里的动态图的定义是边权可以随着操作而变动的图. 当我们在这个图上经过一条边的时候,这个图上所有边的边权都会发生变动. 定义变 ...

  10. RFID EPC Class1 Gen2电子标签笔记

    RFID EPC Class1 Gen2 符合EPC Class1 Gen2(简称G2)协议V109版的电子标签(Tag)和读写器(Reader)应该具有下述的特性 标签存储器分区 Tag memor ...