先来了解下Nagle算法。
Nagle算法作用是尽可能发送大块数据,避免网络中拥有大量小块数据而降低了网络利用率。
Nagle算法基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
Nagle算法的规则:
(1)如果包长度达到MSS,则允许发送;
(2)如果该包含有FIN,则允许发送;
(3)设置了TCP_NODELAY选项,则允许发送;
(4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
(5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
结合代码来说,对于程序中“写-写-读”的情况,第二次写会延时一个RTT。
来看代码。
server.c
- int main(void)
- {
- signal(SIGPIPE, SIG_IGN);
- int listenfd;
- if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
- ERR_EXIT("socket error");
- struct sockaddr_in servaddr;
- memset(&servaddr, 0, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(5188);
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- int on = 1;
- if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
- ERR_EXIT("setsockopt error");
- if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
- ERR_EXIT("bind error");
- if (listen(listenfd, SOMAXCONN) < 0)
- ERR_EXIT("listen error");
- while (1) {
- int conn;
- struct sockaddr_in peeraddr;
- socklen_t peerlen = sizeof(peeraddr);
- if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
- ERR_EXIT("accept error");
- printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
- while (1) {
- char recvbuf[1024];
- int readlen;
- readlen = recv(conn, recvbuf, sizeof(recvbuf), 0);
- if (readlen <= 0)
- break;
- send(conn, recvbuf, readlen, 0);
- }
- printf("close\n");
- close(conn);
- }
- close(listenfd);
- return 0;
- }
- int main(int argc, char *argv[])
- {
- int sock;
- int size = 1024;
- char *buffer;
- int option = get_args(argc, argv);
- buffer = malloc(size);
- if (buffer == NULL)
- ERR_EXIT("malloc error");
- if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
- ERR_EXIT("socket error");
- struct sockaddr_in servaddr;
- memset(&servaddr, 0, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(5188);
- servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
- if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
- ERR_EXIT("connect error");
- int writelen, readlen;
- int on = 1;
- printf("write...\n");
- writelen = send(sock, buffer, size/2, 0);
- usleep(100);
- printf("write...\n");
- writelen = send(sock, buffer+size/2, size/2, 0);
- printf("read...\n");
- readlen = recv(sock, buffer, size, 0);
- printf("close\n");
- close(sock);
- return 0;
- }
可以看到,客户端程序先后执行了两次发送数据,发送的大小都没达到MSS,符合Nagle算法规则。我们来观察两次发送之间的延时时间。
为了实验效果,把服务端程序放到远程机子上执行,客户端在本地执行。
使用tcpdump抓包,tcpdump的使用命令可以参考这个教程。
$ sudo tcpdump -i wlan0 tcp port 5188
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlan0, link-type EN10MB (Ethernet), capture size 65535 bytes
10:23:31.274688 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [S], seq 2552470467, win 29200, options [mss 1460,sackOK,TS val 98772 ecr 0,nop,wscale 7], length 0
10:23:31.446624 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [S.], seq 2653946229, ack 2552470468, win 28960, options [mss 1400,sackOK,TS val 2278931709 ecr 98772,nop,wscale 7], length 0
10:23:31.446682 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [.], ack 1, win 229, options [nop,nop,TS val 98815 ecr 2278931709], length 0
10:23:31.446779 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [P.], seq 1:513, ack 1, win 229, options [nop,nop,TS val 98815 ecr 2278931709], length 512
10:23:31.618338 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [.], ack 513, win 235, options [nop,nop,TS val 2278931752 ecr 98815], length 0
10:23:31.618374 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [P.], seq 513:1025, ack 1, win 229, options [nop,nop,TS val 98858 ecr 2278931752], length 512
10:23:31.618675 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [P.], seq 1:513, ack 513, win 235, options [nop,nop,TS val 2278931752 ecr 98815], length 512
10:23:31.618702 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [.], ack 513, win 237, options [nop,nop,TS val 98858 ecr 2278931752], length 0
10:23:31.618768 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [F.], seq 1025, ack 513, win 237, options [nop,nop,TS val 98858 ecr 2278931752], length 0
10:23:31.790670 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [P.], seq 513:1025, ack 1026, win 243, options [nop,nop,TS val 2278931795 ecr 98858], length 512
10:23:31.790709 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [R], seq 2552471493, win 0, length 0
10:23:31.790899 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [F.], seq 1025, ack 1026, win 243, options [nop,nop,TS val 2278931795 ecr 98858], length 0
10:23:31.790939 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [R], seq 2552471493, win 0, length 0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlan0, link-type EN10MB (Ethernet), capture size 65535 bytes
10:23:31.274688 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [S], seq 2552470467, win 29200, options [mss 1460,sackOK,TS val 98772 ecr 0,nop,wscale 7], length 0
10:23:31.446624 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [S.], seq 2653946229, ack 2552470468, win 28960, options [mss 1400,sackOK,TS val 2278931709 ecr 98772,nop,wscale 7], length 0
10:23:31.446682 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [.], ack 1, win 229, options [nop,nop,TS val 98815 ecr 2278931709], length 0
10:23:31.446779 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [P.], seq 1:513, ack 1, win 229, options [nop,nop,TS val 98815 ecr 2278931709], length 512
10:23:31.618338 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [.], ack 513, win 235, options [nop,nop,TS val 2278931752 ecr 98815], length 0
10:23:31.618374 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [P.], seq 513:1025, ack 1, win 229, options [nop,nop,TS val 98858 ecr 2278931752], length 512
10:23:31.618675 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [P.], seq 1:513, ack 513, win 235, options [nop,nop,TS val 2278931752 ecr 98815], length 512
10:23:31.618702 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [.], ack 513, win 237, options [nop,nop,TS val 98858 ecr 2278931752], length 0
10:23:31.618768 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [F.], seq 1025, ack 513, win 237, options [nop,nop,TS val 98858 ecr 2278931752], length 0
10:23:31.790670 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [P.], seq 513:1025, ack 1026, win 243, options [nop,nop,TS val 2278931795 ecr 98858], length 512
10:23:31.790709 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [R], seq 2552471493, win 0, length 0
10:23:31.790899 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55509: Flags [F.], seq 1025, ack 1026, win 243, options [nop,nop,TS val 2278931795 ecr 98858], length 0
10:23:31.790939 IP vv-Inspiron-5323.lan.55509 > 47.88.22.84.5188: Flags [R], seq 2552471493, win 0, length 0
来看一下,服务器客户端主机之间的RTT时间
$ ping 47.88.22.84
PING 47.88.22.84 (47.88.22.84) 56(84) bytes of data.
64 bytes from 47.88.22.84: icmp_seq=1 ttl=52 time=174 ms
64 bytes from 47.88.22.84: icmp_seq=2 ttl=52 time=174 ms
64 bytes from 47.88.22.84: icmp_seq=3 ttl=52 time=173 ms
RTT正好是170ms左右。
这个发送延时的解决方法有两个:
1. 把客户端的“写-写-读”修改为“写-读”
这种方法需要应用层实现,但有时环境并不允许我们这么做。
2. 禁止Nagle算法
在C语言中,可以使用setsockopt函数来禁止Nagle算法,具体使用如下:
- int on = 1;
- setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
我们再来抓一下包:
$ sudo tcpdump -i wlan0 tcp port 5188
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlan0, link-type EN10MB (Ethernet), capture size 65535 bytes
10:31:09.982217 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [S], seq 2900548376, win 29200, options [mss 1460,sackOK,TS val 213449 ecr 0,nop,wscale 7], length 0
10:31:10.156928 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [S.], seq 2411382823, ack 2900548377, win 28960, options [mss 1400,sackOK,TS val 2279046387 ecr 213449,nop,wscale 7], length 0
10:31:10.156982 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [.], ack 1, win 229, options [nop,nop,TS val 213492 ecr 2279046387], length 0
10:31:10.157062 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [P.], seq 1:513, ack 1, win 229, options [nop,nop,TS val 213492 ecr 2279046387], length 512
10:31:10.159812 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [P.], seq 513:1025, ack 1, win 229, options [nop,nop,TS val 213493 ecr 2279046387], length 512
10:31:10.328542 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [.], ack 513, win 235, options [nop,nop,TS val 2279046430 ecr 213492], length 0
10:31:10.328858 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [P.], seq 1:513, ack 513, win 235, options [nop,nop,TS val 2279046430 ecr 213492], length 512
10:31:10.328935 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [.], ack 513, win 237, options [nop,nop,TS val 213535 ecr 2279046430], length 0
10:31:10.329007 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [F.], seq 1025, ack 513, win 237, options [nop,nop,TS val 213535 ecr 2279046430], length 0
10:31:10.371897 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [.], ack 1025, win 243, options [nop,nop,TS val 2279046441 ecr 213493], length 0
10:31:10.500831 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [P.], seq 513:1025, ack 1025, win 243, options [nop,nop,TS val 2279046473 ecr 213535], length 512
10:31:10.500883 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [R], seq 2900549401, win 0, length 0
10:31:10.501164 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [F.], seq 1025, ack 1026, win 243, options [nop,nop,TS val 2279046473 ecr 213535], length 0
10:31:10.501207 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [R], seq 2900549402, win 0, length 0
170ms延时已经没了。
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlan0, link-type EN10MB (Ethernet), capture size 65535 bytes
10:31:09.982217 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [S], seq 2900548376, win 29200, options [mss 1460,sackOK,TS val 213449 ecr 0,nop,wscale 7], length 0
10:31:10.156928 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [S.], seq 2411382823, ack 2900548377, win 28960, options [mss 1400,sackOK,TS val 2279046387 ecr 213449,nop,wscale 7], length 0
10:31:10.156982 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [.], ack 1, win 229, options [nop,nop,TS val 213492 ecr 2279046387], length 0
10:31:10.157062 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [P.], seq 1:513, ack 1, win 229, options [nop,nop,TS val 213492 ecr 2279046387], length 512
10:31:10.159812 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [P.], seq 513:1025, ack 1, win 229, options [nop,nop,TS val 213493 ecr 2279046387], length 512
10:31:10.328542 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [.], ack 513, win 235, options [nop,nop,TS val 2279046430 ecr 213492], length 0
10:31:10.328858 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [P.], seq 1:513, ack 513, win 235, options [nop,nop,TS val 2279046430 ecr 213492], length 512
10:31:10.328935 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [.], ack 513, win 237, options [nop,nop,TS val 213535 ecr 2279046430], length 0
10:31:10.329007 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [F.], seq 1025, ack 513, win 237, options [nop,nop,TS val 213535 ecr 2279046430], length 0
10:31:10.371897 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [.], ack 1025, win 243, options [nop,nop,TS val 2279046441 ecr 213493], length 0
10:31:10.500831 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [P.], seq 513:1025, ack 1025, win 243, options [nop,nop,TS val 2279046473 ecr 213535], length 512
10:31:10.500883 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [R], seq 2900549401, win 0, length 0
10:31:10.501164 IP 47.88.22.84.5188 > vv-Inspiron-5323.lan.55548: Flags [F.], seq 1025, ack 1026, win 243, options [nop,nop,TS val 2279046473 ecr 213535], length 0
10:31:10.501207 IP vv-Inspiron-5323.lan.55548 > 47.88.22.84.5188: Flags [R], seq 2900549402, win 0, length 0
170ms延时已经没了。
在高互动的网络环境下,Nagle算法往往是不需要的。go语言默认情况下就是禁止Nagle算法的。
本文中提到的所有代码都能在这里看到。