大数跨境
0
0

07.发送0字节的数据是什么效果?

07.发送0字节的数据是什么效果? CppGuide
2023-12-12
2

关注我,更多的优质内容第一时间阅读


上一节,我提到 sendrecv 函数返回 0 表示对端关闭了连接。有的读者可能认为如果发送 0 字节数据,那么  send 函数也会返回 0,对端会 recv0 字节的数据,事实是这样的吗?

我们来通过一个例子,来看下 send 一个长度0 的数据,send 函数的返回值是什么,对端是否会 recv0 字节的数据。

server 端代码:

/**
 * 验证recv函数接受0字节的行为,server端,server_recv_zero_bytes.cpp
 * zhangyl 2018.12.17
 */
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <vector>

int main(int argc, char* argv[])
{
    //1.创建一个侦听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
    {
        std::cout << "create listen socket error." << std::endl;
        return -1;
    }

    //2.初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(3000);
    if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)
    {
        std::cout << "bind listen socket error." << std::endl;
     close(listenfd);
        return -1;
    }
    
    //3.启动侦听
    if (listen(listenfd, SOMAXCONN) == -1)
    {
        std::cout << "listen error." << std::endl;
     close(listenfd);
        return -1;
    }
    
    int clientfd;
     
    struct sockaddr_in clientaddr;
    socklen_t clientaddrlen = sizeof(clientaddr);
    //4. 接受客户端连接
    clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
    if (clientfd != -1)
    {          
     while (true)
     {
      char recvBuf[32] = {0};
      //5. 从客户端接受数据,客户端没有数据来的时候会在 recv 函数处阻塞
      int ret = recv(clientfd, recvBuf, 32, 0);
      if (ret > 0) 
      {
       std::cout << "recv data from client, data: " << recvBuf << std::endl;    
      } 
      else if (ret == 0)
      {
       //“假设recv返回值为0时是收到了0个字节”
       std::cout << "recv 0 byte data." << std::endl;
       continue;
      } 
      else
      {
       //出错
       std::cout << "recv data error." << std::endl;
       break;
      }
     }    
    }

 //关闭客户端socket
 close(clientfd);
 //7.关闭侦听socket
 close(listenfd);
 
 return 0;
}

上述代码侦听端口号是 3000,代码 55 行调用了 recv 函数,如果客户端一直没有数据,程序会阻塞在这里。

client 端代码:

/**
 * 验证非阻塞模式下send函数发送0字节的行为,client端,nonblocking_client_send_zero_bytes.cpp
 * zhangyl 2018.12.17
 */
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT     3000
#define SEND_DATA       ""

int main(int argc, char* argv[])
{
    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        std::cout << "create client socket error." << std::endl;
        return -1;
    }

    //2.连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)
    {
        std::cout << "connect socket error." << std::endl;
     close(clientfd);
        return -1;
    }
    
    //连接成功以后,我们再将 clientfd 设置成非阻塞模式,
    //不能在创建时就设置,这样会影响到 connect 函数的行为
    int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
    int newSocketFlag = oldSocketFlag | O_NONBLOCK;
    if (fcntl(clientfd, F_SETFL,  newSocketFlag) == -1)
    {
     close(clientfd);
     std::cout << "set socket to nonblock error." << std::endl;
     return -1;
    }
    
    //3. 不断向服务器发送数据,或者出错退出
    int count = 0;
    while (true)
    {
     //发送 0 字节的数据
     int ret = send(clientfd, SEND_DATA, 0, 0);
     if (ret == -1) 
     {
      //非阻塞模式下send函数由于TCP窗口太小发不出去数据,错误码是EWOULDBLOCK
      if (errno == EWOULDBLOCK)
      {
       std::cout << "send data error as TCP Window size is too small." << std::endl;
       continue;
      } 
      else if (errno == EINTR)
      {
       //如果被信号中断,我们继续重试
       std::cout << "sending data interrupted by signal." << std::endl;
       continue;
      } 
      else 
      {
       std::cout << "send data error." << std::endl;
       break;
      }
     }
     else if (ret == 0)
     {
      //发送了0字节
      std::cout << "send 0 byte data." << std::endl;
     } 
     else
     {
      count ++;
      std::cout << "send data successfully, count = " << count << std::endl;
     }
     
     //每三秒发一次
     sleep(3);
    }
    
    //5. 关闭socket
    close(clientfd);
    
    return 0;
}

client 端连接服务器成功以后,每隔 3 秒调用 send 一次发送一个 0 字节的数据。除了先启动 server 以外,我们使用 tcpdump 抓一下经过端口 3000 上的数据包,使用如下命令:

tcpdump -i any 'tcp port 3000'

然后启动 client ,我们看下结果:


客户端确实是每隔 3 秒 send 一次数据。此时我们使用 lsof -i -Pn 命令查看连接状态,也是正常的:


然后,tcpdump 抓包结果输出中,除了连接时的三次握手数据包,再也无其他数据包,也就是说,send 函数发送 0 字节数据,此时 send 函数返回 0,但 client 的协议栈并不会把这些数据发出去。

[root@localhost ~]# tcpdump -i any 'tcp port 3000'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:37:03.028449 IP localhost.48820 > localhost.hbci: Flags [S], seq 1632283330, win 43690, options [mss 65495,sackOK,TS val 201295556 ecr 0,nop,wscale 7], length 0
17:37:03.028479 IP localhost.hbci > localhost.48820: Flags [S.], seq 3669336158, ack 1632283331, win 43690, options [mss 65495,sackOK,TS val 201295556 ecr 201295556,nop,wscale 7], length 0
17:37:03.028488 IP localhost.48820 > localhost.hbci: Flags [.], ack 1, win 342, options [nop,nop,TS val 201295556 ecr 201295556], length 0

因此,server 端也会一直没有输出,如果你用的是 gdb 启动 server,此时中断下来会发现,server 端由于没有数据会一直阻塞在 recv 函数调用处(55 行)。


通过上面的测试我们得到如下结论:

存在以下两种情形会让 send 函数的返回值为 0 :

  • 对端关闭了连接时,我们正好尝试调用 send 函数发送数据;
  • 本端尝试调用 send 函数发送 0 字节数据。

而 recv 函数只有在对端关闭连接时才会返回 0,对端发送 0 字节,本端的 recv 函数是不会收到 0 字节数据的。

然而,这里的代码示例仅仅用于实验性讨论,send 一个 0 字节数据是没有任何意义的,希望读者在实际开发时,避免写出可能调用 send 函数发送 0 字节的代码。

关注我,更多的优质内容第一时间阅读


更多的专题参见:cppguide.cn


技术交流群:加微信 cppxiaofang,备注加微信群。


付费课程

C/C++ 网络编程实战训练营 一期录像

C/C++ 网络编程二期录像 限时特惠

【声明】内容源于网络
0
0
CppGuide
专注于高质量高性能C++开发,站点:cppguide.cn
内容 1260
粉丝 0
CppGuide 专注于高质量高性能C++开发,站点:cppguide.cn
总阅读582
粉丝0
内容1.3k