TCP nagle算法是说,一个TCP连接只允许有一个未被确认的小数据包,如果有小数据包未被确认,其他要发送的小数据包先被缓存起来,等收到确认后, 把这些数据包再一块发送出去。注意算法中说的是小数据包,也就是nagle算法是针对发送许多小数据包时的算法。为什么会有这个算法?因为我们知道TCP头部一般就有20字节的长度,如果每次我只发送1字节的数据,那一个数据包的有效载荷只有1/21,所以,为了更高效发送数据,TCP设计了nagle算法,把小包收集起来一块发送。这个算法的精妙之处在于,如果我ack收到的越快,发送数据越快,如果ack收到的慢,说明网络环境不好,TCP发送数据也就越慢,这样就避免了给网络带来负担。
示例
首先我在本机windows电脑用netassist开启了1234端口,作为服务端。
然后编写如下代码,在一台centos虚拟机中执行,在执行之前,开启wireshark抓包。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <string.h>
// nagle算法示例
int main(){
// 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
//向服务器(特定的IP和端口)发起请求
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("192.168.52.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
int res = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(res != 0) {
printf("connect fail %d\n", res);
return 0;
}
// 向服务端发送多个小数据包
char s[] = "abc";
for(int i=0; i<100; i++) {
write(sock, s, strlen(s));
}
sleep(1);
close(sock);
return 0;
}
wireshark抓包内容如下:
可以看到,第四行,tcp先发送了"abc"三个字节,然后就没有继续再发送,而是等到服务端回复了ack之后,又发送了另外的99个"abc"(共297字节)。说明nagle算法起作用了。
tcp提供了一种方式来关闭nagle算法,我们把nagle算法关闭,再来看效果。
修改上面的代码,改成如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <string.h>
// nagle算法示例
int main(){
// 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 设置关闭nagle算法
int setFlags = 1;
socklen_t setFlagsLen = sizeof(setFlags);
int setRes = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &setFlags, setFlagsLen);
printf("set nagle no delay flags result: %d\n", setRes);
//向服务器(特定的IP和端口)发起请求
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("192.168.52.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
int res = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(res != 0) {
printf("connect fail %d\n", res);
return 0;
}
// 向服务端发送多个小数据包
char s[] = "abc";
for(int i=0; i<100; i++) {
write(sock, s, strlen(s));
}
sleep(1);
close(sock);
return 0;
}
此时我们再抓包看,如下,这里只截取了一部分,因为数据包太多了。可以看到tcp把我们在for循环中发送的"abc"一个个发送的,不再一块发送,也不再等待服务端ack之后再发送。