开启本选项,将禁止 Nagle 算法。有关 Nagle 算法的细节,请参考《TCP 协议(Nagle) 》。
1. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/options/opt
。
2. 回忆 Nagle
Nagle 算法的目的是为了减少网络中小分组的数目。“小”分组(tiny gram) 指是是小于 MSS 的分组。它利用收到 ack 前这一小段时间“攒”数据,收到 ack 后,再将“攒”起来的小分组合并成一个大分组一次性发出去。
在局域网上,通常我们察觉不到 Nagle 算法对客户进程的影响。但是在广域网上,小分组所需要的确认时间可能长达 1 秒,这样客户端就会有非常明显的延时。如果受延时的 ack 影响,这种延时会更加明显(参考 TCP 协议(迟到的 ACK —— Windows ) 和 TCP 协议(迟到的 ACK—— Linux) )。
3. 如何关闭 Nagle 算法
TCP_NODELAY 选项可以用来关闭 Nagle 算法,具体代码如下:
int onoff = 1; ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &onoff, sizeof(onoff)); if (ret < 0) perror("setsockopt");
4. Nagle 算法与 write-write-read
看下面一小段客户端的程序:
// client while(1) { write(sockfd, buf, 4); write(sockfd, buf, 396); read(sockfd, buf, 1024); }
第 2 次 write 的数据会一直驻留在发送缓冲区中,直到第一个 4 字节的小分组被确认。而服务器并没有数据要发送给客户端,因此会发送延时的 ack,比如已经过了 40ms,客户端才会收到这 4 字节的小分组的确认。接下来 TCP 发送 396 字节大小的分组,等待服务器处理完后发送数据给客户端,客户端从 read 返回。
图1 write-write-read
如果 RTT 非常小,比如只有 1 ms,那么上面的调用过程,将会在 read 上阻塞约 42 ms 左右(
在这种情况下,Nagle 算法并没有什么好处,反而会降低数据传输效率。有三种方法可以修正这种问题:
- 使用 writev 函数,聚集写。
- 在应用层将前 4 个字节和后 396 字节复制到单个缓冲区,然后调用一次 write.
- 使用 TCP_NODELAY 关闭 Nagle 算法。unp 中提到说这是最不可取的方法,而且有损于网络,通常不应该考虑。(很多博客和文章都推荐关闭 Nagle?Nginx 默认也是关闭了 Nagle 算法的。陈硕老师也推荐关闭 Nagle 算法……所以,大家以后该怎么做?)
5. 实验
5.1 关闭 Nagle 算法
- flower 上启动服务器
$ ./opt -s -h flower --slowread 4096
- sun 上启动客户端,注意参数
// 选项 --writesize 1,表示客户端每次只 write 一个字节 // --nodelay 表示关闭 Nagle 算法 $ ./opt -h flower --nodelay --writesize 1
- 在客户端输入
'helloworld'
,然后回车。一共 11 个字符包含了字符'\n'
。
图1 关闭 Nagle 算法的情况
可以看到,关闭 Nagle 时,只要有小分组就立即发送出去。
5.2 打开 Nagle 算法
- flower 上启动服务器
$ ./opt -s -h flower --slowread 4096
- sun 上启动客户端,注意参数
// 选项 --writesize 1,表示客户端每次只 write 一个字节 // 默认情况下 Nagle 算法是开启的 $ ./opt -h flower --writesize 1
- 在客户端输入
'helloworld'
,然后回车。一共 11 个字符包含了字符'\n'
。
图2 开启 Nagle 算法的情况
可以看到,客户端会将小分组收集起来,合并成一个大分组一次发送出去。
6. 总结
- 知道 Nagle 算法干了什么事
- 知道如何关闭 Nagle