网络编程:发送0字节数据的效果
通过一个例子来看看发送一个长度为0的数据,send函数返回值是什么,对端是否会接收到0字节数据。
server端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
int main(void)
{
// 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[64] = {0};
// 5.从客户端接收数据,客户端没u有数据过来时,会在recv函数处阻塞
int ret = recv(clientfd, recvBuf, 64, 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;
}
}
}
// 6.关闭客户端socket
close(clientfd);
// 7.关闭监听socket
close(listenfd);
return 0;
}
client端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 3000
#define SEND_DATA ""
int main(void)
{
// 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 singal." << 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(1);
}
// 5.关闭socket
close(clientfd);
return 0;
}
执行流程
先启动server端,再使用tcpdump抓取经过3000端口的数据包。
在启动client端,输出结果如下:每隔一秒发送一次数据。
此时使用lsof -i Pn命令查看连接状态:发现连接正常
在tcpdump抓包结果中,除了建立连接的三次握手数据包,再无其它数据,也就是说,send函数发送0字节数据,此时send函数返回0,但client端的操作系统协议栈并不会把这些数据发送出去:
因此,server端也会一直没有输出:
使用gdb调试,此时中断会发现,server端没有数据,一直阻塞在recv函数调用处:
结论
通过测试,可以知道存在以下两种情况让send函数的返回值为0:
- 对端关闭连接时,正好尝试调用send函数发送数据;
- 本端尝试调用send函数发送0字节数据。
而recv函数只有在对端关闭连接时才会返回0,对端发送0字节数据,本端的recv函数是不会收到0字节数据的。