前面我们所用的 unp/protocol/tools/winclient/echo_cli.cpp
程序的特别之处是它总会发送一个小分组(TCP 段,只有 41 字节)到服务器。这样的小分组在英文中称为 tinygram,在网络状态好的情况下,比如局域网中,通常不会引起什么麻烦。但是在广域网中,这样的小分组会增加网络拥塞的可能。
为了能够减少这样的 tinygram 在网络中的数量,在 TCP 协议栈中,默认使用了 Nagle 算法。
1. Nagle 算法
Nagle 算法要求:
- 一个 TCP 连接上最多只能有一个未被确认的未完成的小分组,在它到达目的地前,不能发送其它分组。
- 在上一个小分组未到达目的地前,即还未收到它的 ack 前,TCP 会收集后来的小分组。当上一个小分组的 ack 收到后,TCP 就将收集的小分组合并成一个大分组发送出去。
在广域网中,一般网络延时都比较大,小分组发送出去后,可能要等很久才会收到 ack,因此,在收到 ack 前,发送方可能会累积好多好多未发送的小分组。
图1 Nagle 算法如何处理小分组
在图 1 中,客户端首先发送了一个字符 l
给服务器,在收到服务器的回应前,客户端又发送了三个分组,根据 Nagle 算法规则,在未收到 ack 前,这些小分组都不能发出去。收到 ack 后,tcp 将这三个小分组合并成一个,一次性发出去。
2. 实验 1(开启 Nagle 算法)
默认情况下 Nagle 算法就是开启的。
在图 1 中,假设往返时间是 16ms,要想在这 16ms 里连续发送 4 个字符 l
, o
, v
, e
几乎是不可能的,这意味着我们每秒打字速度要超过 250 个左右,16 ms 里发送至少 2 个字符,打字速度也得要超过每秒 60 个。那么如何在实验中模拟快速发送字符呢?
客户端 echo_client.cpp 提供了一个选项,它能帮我们在键入一个字符时,连续将此字符发送多次。比如键入字符 x
,echo_client.cpp 会在极短时间内将 x
发送很多次。
- 客户端路径:
unp/protocol/tools/winclient/echo_client.cpp
,部署在 windows 上。 - 服务器路径:
unp/protocol/tools/tcpserver/echo_serv.c
,部署在 Linux 上。
2.1 实验步骤
- 在 Linux 上启动服务器 echo_serv
$ ./echo_serv 192.168.80.130 8000
- 在 Windows 上打开 OmniPeek 抓包
- Windows 上启动 echo_client.exe
// 注意 echo_client 后面又加了个参数 5,表示将输入的字符连续发送 5 次 echo_client.exe 192.168.80.130 8000 5
接下来,我在 client 中键入了一个字符 x
,然后按 q
键结束。
2.2 抓包结果
图2 OmniPeek 抓的数据
图 2 中,客户端首先发送了一个字符 x
过去,接下来等待 ack,在此期间,客户端又请求发送了四个 x
,TCP 将这些后来的小分组收集,当收到 ack 后,将这 4 个 x
合并成了一个分组一次发送出去。
3. 实验 2(关闭 Nagle 算法)
这一次我们关闭 Nagle 算法。
和实验 1 不同的地方在于,客户端启动的时候,再加一个参数,NONAGLE.
// 启动客户端 echo_client.exe 192.168.80.130 8000 5 NONAGLE
同样的在客户端中输入一个字符 x
,此时客户端会帮我们连续发送 5 次 x
,然后按 q
退出。抓包结果如下:
图3 没有 Nagle 算法的情况下,客户端发数据的结果
从图 3 中可以看到,红色框框包含的那 5 个数据包,在极短的时间内被发送出去,TCP 在发送第一个包后,并没有等待对方回送的确认。
4. 总结
- 掌握 Nagle 算法的规则
- 有 Nagle 算法和无 Nagle 有什么不同