梁越

网络编程学习笔记7-TCP使用的注意事项(附代码)

0 人看过

Nagle和NODELAY,视频p18

本笔记参考的视频链接:https://www.bilibili.com/video/BV1Ht411p7wx?p=18
库链接:https://github.com/chenshuo/muduo

SIGPIPE

当服务器close一个连接时,若client端接着发数据

根据TCP 协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了

根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出,server也会退出

为了不影响服务端和其他客户端,可以忽略SIGPIPE

Nagle

Nagle算法主要是避免发送小的数据包,要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的小分组,并在确认到来时以一个分组的方式发出去。

//MSS最大段大小

if there is new data to send  //如果还有数据发送
  if the window size >= MSS and available data is >= MSS  //如果发送大小大于MSS,则发送MSS大小的数据,多出MSS的数据下一个RTT发送
    send complete MSS segment now
  else
    if there is unconfirmed data still in the pipe  //如果还有未收到前一个数据ACK的数据,等待ACK
      enqueue data in the buffer until an acknowledge is received  
    else
      send data immediately  //小于MSS而且都收到了前一个数据的ACK,则直接发送
    end if
  end if
end if

Nagle算法只允许一个未被ACK的包存在,它并不管包的大小,所以只要有一个没收到ACK,则会一直等下去

因为Nagle要等待确认后才能发送,所以会有一定延迟

而且这里面有着ACK延迟机制,当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去

所以一般发送延迟出现40ms以上的原因是Nagle算法在捣乱

TCP_NODELAY

TCP_NODELAY就是禁用Nagle算法

验证Nagle和TCP_NODELAY的延迟

因为找不到muduo对应的代码,但是我又想自己看看两者的延迟,所以我自己写了一个验证
这里参考muduo的例子,一个小文件分两次发送

//客户端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include <arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/tcp.h>

#define MAXLINE 20

int main(int argc, char** argv)
{
    int    sockfd, n;
    char    recvline[4096], sendhead[20], senddata[20];
    struct sockaddr_in    servaddr;

    if( argc < 2){
    printf("usage: ./client <ipaddress>\n");
    exit(0);
    }

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
        printf("inet_pton error for %s\n",argv[1]);
        exit(0);
    }
    //设置tcp_nodelay
    if(argc==3 && argv[2]=="nodelay")
    {
        int on = 1;
        setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
        setsockopt( sockfd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
    }

    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
        exit(0);
    }

    printf("send msg to server: \n");
    //fgets(sendline, 4096, stdin);
    for(int i=0;i<MAXLINE;i++)
    {
        sendhead[i]='s';
        senddata[i]='o';
    }
  //发送两次
    if( send(sockfd, sendhead, strlen(sendhead), 0) < 0)
    {
        printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
    }
    if( send(sockfd, senddata, strlen(senddata), 0) < 0)
    {
        printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
    }

    close(sockfd);
    exit(0);
}
//服务端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    listenfd, connfd;
    struct sockaddr_in     servaddr;
    char    buff[4096];
    int     n;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
    printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666);

    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    if( listen(listenfd, 10) == -1){
    printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    printf("======waiting for client's request======\n");
    while(1){
    if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
        continue;
    }
    n = recv(connfd, buff, MAXLINE, 0);
    buff[n] = '\0';
    printf("recv msg from client: %s\n", buff);
    close(connfd);
    }

    close(listenfd);
}

验证结果

在服务端tcpdump

sudo tcpdump -i ens33 -n tcp port 6666 -ttt

下面是结果

 //没加nodelay
 00:00:00.000000 IP 192.168.200.132.46442 > 192.168.200.134.ircu-2: Flags [S], seq 922712728, win 29200, options [mss 1460,sackOK,TS val 41496494 ecr 0,nop,wscale 7], length 0
 00:00:00.000051 IP 192.168.200.134.ircu-2 > 192.168.200.132.46442: Flags [S.], seq 536372574, ack 922712729, win 28960, options [mss 1460,sackOK,TS val 41501953 ecr 41496494,nop,wscale 7], length 0
 00:00:00.000296 IP 192.168.200.132.46442 > 192.168.200.134.ircu-2: Flags [.], ack 1, win 229, options [nop,nop,TS val 41496494 ecr 41501953], length 0
 * 00:00:00.000247 IP 192.168.200.132.46442 > 192.168.200.134.ircu-2: Flags [P.], seq 1:21, ack 1, win 229, options [nop,nop,TS val 41496494 ecr 41501953], length 20
 00:00:00.000031 IP 192.168.200.134.ircu-2 > 192.168.200.132.46442: Flags [.], ack 21, win 227, options [nop,nop,TS val 41501953 ecr 41496494], length 0
 * 00:00:00.000068 IP 192.168.200.132.46442 > 192.168.200.134.ircu-2: Flags [FP.], seq 21:41, ack 1, win 229, options [nop,nop,TS val 41496494 ecr 41501953], length 20
 00:00:00.000253 IP 192.168.200.134.ircu-2 > 192.168.200.132.46442: Flags [F.], seq 1, ack 42, win 227, options [nop,nop,TS val 41501953 ecr 41496494], length 0
 00:00:00.000303 IP 192.168.200.132.46442 > 192.168.200.134.ircu-2: Flags [.], ack 2, win 229, options [nop,nop,TS val 41496495 ecr 41501953], length 0

//加了nodelay
 00:00:28.461646 IP 192.168.200.132.46444 > 192.168.200.134.ircu-2: Flags [S], seq 2190537608, win 29200, options [mss 1460,sackOK,TS val 41524957 ecr 0,nop,wscale 7], length 0
 00:00:00.000200 IP 192.168.200.134.ircu-2 > 192.168.200.132.46444: Flags [S.], seq 2615353636, ack 2190537609, win 28960, options [mss 1460,sackOK,TS val 41530416 ecr 41524957,nop,wscale 7], length 0
 00:00:00.000249 IP 192.168.200.132.46444 > 192.168.200.134.ircu-2: Flags [.], ack 1, win 229, options [nop,nop,TS val 41524957 ecr 41530416], length 0
 * 00:00:00.000153 IP 192.168.200.132.46444 > 192.168.200.134.ircu-2: Flags [P.], seq 1:21, ack 1, win 229, options [nop,nop,TS val 41524957 ecr 41530416], length 20
 00:00:00.000084 IP 192.168.200.134.ircu-2 > 192.168.200.132.46444: Flags [.], ack 21, win 227, options [nop,nop,TS val 41530416 ecr 41524957], length 0
 * 00:00:00.000031 IP 192.168.200.132.46444 > 192.168.200.134.ircu-2: Flags [FP.], seq 21:41, ack 1, win 229, options [nop,nop,TS val 41524957 ecr 41530416], length 20
 00:00:00.000543 IP 192.168.200.134.ircu-2 > 192.168.200.132.46444: Flags [F.], seq 1, ack 42, win 227, options [nop,nop,TS val 41530417 ecr 41524957], length 0
 00:00:00.000135 IP 192.168.200.132.46444 > 192.168.200.134.ircu-2: Flags [.], ack 2, win 229, options [nop,nop,TS val 41524958 ecr 41530417], length 0

可以看到,打开tcp_nodelay之后,延迟时间差不多为不打开的一半,说明tcp_nodelay生效了

参考链接:

https://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html

https://www.cnblogs.com/chenpingzhao/p/9108570.html

https://blog.xuite.net/jyoutw/xtech/23669726-tcpdump+%E7%9A%84%E7%94%A8%E6%B3%95