Linux C/C++网络编程实战-陈硕-笔记16-TCP使用的基本事项

SIGPIPE 信号

  • 如果向一个已经关掉的管道写数据,write系统调用会返回一个 SIGPIPE 信号
    在这里插入图片描述
例1
  • 于读写IO,当我们使用管道连接一行命令时,如果管道末端的命令执行失败,那么整个管道的程序将会依次收到SIGPIPE。避免了无效的计算。
    gunzip -c message.log.gz | grep -a Succeeded. | head -10
    
    # gunzip -c或--stdout或--to-stdout  把解压后的文件输出到标准输出设备
    # grep -a grep如果碰到\000 NUL字符,就会认为文件是二进制文件,而 grep 匹配 默认忽略二进制数据。-a 将 binary 文件以 text 文件的方式搜寻数据
    
  • 此命令的只取压缩文件中含有 “Succeeded.” 的前10函数数据,当成功匹配到10行数据后,gunzip也会停止执行,这样就可以避免将整个大文件都解压缩
例2
  • 对于网络IO,当我们关闭了一个连接时,如果尝试向他写入数据,也会收到一个 SIGPIPE 信号
  • 对于这种情况,如果在服务端没有做特殊处理,有一个客户端意外退出,造成连接关闭,而服务端如果此时发送消息的话,就会收到 SIGPIPE 信号,导致服务端被迫退出
  • 因此,网络程序一般在启动时,都会忽略掉 SIGPIPE 信号
    signal(SIGPIPE, SIG_IGN);
    
  • 另外,需要注意的是,忽略SIGPIPE后,如果对方关闭了连接,我们的程序可能不会退出,因此,我们需要额外关注一些函数的返回值,例如我们向一个连接请求数据后将它输出到标准输出时,我们需要额外关注printf 函数的返回值,如果它的返回值是负值,则表示对端连接已关闭。那么我们的程序应该退出

Nagle 算法

  • Nagle算法主要是避免发送小的数据包,要求TCP连接上最多只能有一个未被确认的小分组,在该分组的确认到达之前不能发送其他的小分组
  • 目的:避免发送大量的小包,网络上每次只能一个小包存在,在小包被确认之前,只能积累发送大包,如果包长度达到MSS,则允许发送;如果该包含有FIN,则允许发送;但发生了超时(一般为200ms),则立即发送, 启动TCP_NODELAY,就意味着禁用了Nagle算法
    在这里插入图片描述
Nagle 算法缺点
  • 如果我们的程序设计的不够合理,Nagle算法可能会增加程序的延迟。
  • 如果你的程序是 write-write-read 模式,在使用了Nagle算法后,第二个 write 就会被推后一个RRT发送而造成一个很长的ack等待,从而产生一个延迟。为了避免这种情况,一般建议在应用层做缓冲,将两个write合在一起,成为 write-read。
  • 但是还有一种情况是,在一个连接上并发的有多个请求时,我们很难将数据整合在一起,它们来自程序中不同的位置。而这种情况Nagle算法会大大增加程序的延迟。
  • 因此,如果你没有十足的把握驾驭Nagle算法的话,我们建议使用 TCP_NODELY 关闭Nagle算法。
Nagle 算法延迟示例

服务器端代码:recipes/tpc/nodelay_server.cc
客户端代码:recipes/tpc/nodelay.cc

  • 客户端是一个 write-write-read 程序,通过制定参数 对比 采取不同TCP选项下程序的延迟情况
  • 客户端
      double start = now();
      for (int n = 0; n < num; ++n)
      {
        printf("Request no. %d, sending %d bytes\n", n, len);
        if (buffering)	// 如果设置缓冲,我们将header与数据合在一起发送
        {
          std::vector<char> message(len + sizeof len, 'S');
          memcpy(message.data(), &len, sizeof len);
          int nw = stream->sendAll(message.data(), message.size());
          printf("%.6f sent %d bytes\n", now(), nw);
        }
        else	// 如果没有设置缓冲,分两次发送header和数据
        {
          stream->sendAll(&len, sizeof len);
          printf("%.6f sent header\n", now());
          usleep(1000); // prevent kernel merging TCP segments
          std::string payload(len, 'S');
          int nw = stream->sendAll(payload.data(), payload.size());
          printf("%.6f sent %d bytes\n", now(), nw);
        }
      }
    
  • 服务端
    // nodelay_server.cc 服务端代码部分
    // 处理逻辑:收header ——》收数据 ——》回响应
    //...
    int main(int argc, char* argv[])
    {
      //...
      bool nodelay = argc > 1 && strcmp(argv[1], "-D") == 0;
      while (true)
      {
        TcpStreamPtr tcpStream = acceptor.accept();
        printf("accepted no. %d client\n", ++count);
        if (nodelay)
          tcpStream->setTcpNoDelay(true);
    
        while (true)
        {
          int len = 0;	// 收header
          int nr = tcpStream->receiveAll(&len, sizeof len);
          if (nr <= 0)
            break;
          printf("%f received header %d bytes, len = %d\n", now(), nr, len);
          assert(nr == sizeof len);
    
          std::vector<char> payload(len);	// 收数据
          nr = tcpStream->receiveAll(payload.data(), len);
          printf("%f received payload %d bytes\n", now(), nr);
          assert(nr == len);		// 回响应
          int nw = tcpStream->sendAll(&len, sizeof len);
          assert(nw == sizeof len);
        }
    
        printf("no. %d client ended.\n", count);
      }
    }
    
  • 编译

    编译服务端代码:

    g++ -o nodelay_server nodelay_server.cc Acceptor.cc InetAddress.cc TcpStream.cc Socket.cc -lpthread -std=c++11
    

    编译客户端代码:

    g++ -o nodelay nodelay.cc Acceptor.cc InetAddress.cc TcpStream.cc Socket.cc -lpthread -std=c++11
    
  • 测试

使用ping命令,可以看到正常的RTT约31ms延迟。然后执行程序,大概有61ms的延迟,是RTT的两倍
在这里插入图片描述
使用 buffering 选项,可以看到延迟减少到35ms左右
在这里插入图片描述
通过tcpdump我们可以看到,客户端一次发送了 1005 个字节的数据。客户端 write-read
在这里插入图片描述

使用SO_REUSEADDR选项

  • SO_REUSEADDR ——复用地址 。

  • 一般服务器的监听socket都应该打开它。它允许服务器bind一个地址,即使这个地址当前已经存在已建立的连接。

  • 对于以下两种情况,SO_REUSEADDR会很有作用:
    在这里插入图片描述

  • 服务器启动后,与客户端建立连接,如果服务器主动关闭,那么和客户端的连接会处于TIME_WAIT状态,此将无法启动服务器进程。

  • 服务器父进程监听客户端,建立连接后,fork一个子进程专门处理客户端的请求,如果父进程停止,因为子进程还和客户端有连接,所以此时重启父进程会失败。

对于以上两张情况,重启服务器都会出现bind: : Address already in use错误。而当我们使用了 SO_REUSEADDR 选项后,服务器退出后,仍然允许我们马上重启进程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值