在 TCP 套接字程序中,客户端发送一些数据,但服务器需要多次读取。为啥?
Posted
技术标签:
【中文标题】在 TCP 套接字程序中,客户端发送一些数据,但服务器需要多次读取。为啥?【英文标题】:In TCP socket program,client send some data, but server need read multiple times. why?在 TCP 套接字程序中,客户端发送一些数据,但服务器需要多次读取。为什么? 【发布时间】:2016-09-05 08:12:24 【问题描述】:我有一个关于套接字的问题。我从客户端向服务器发送 N 大小的数据,N 大小小于 100 字节。所以我认为我的数据不应该拆分为多个 tcp 数据包。在我看来,客户端发送数据应该一次做,服务器一次可以接收数据。但是结果并不理想。实际情况是服务器需要调用读取数据。我不明白。按照代码:
epoll_server.cpp(只接收数据)
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <netdb.h>
#define BUFSIZE 1024
#define INITSIZE 1024
#define MAXEVENTCOUNT 10240
// add non-blocking to sockfd
int make_socket_non_blocking(int fd)
// get initial flag
int src_flags;
src_flags= fcntl(fd, F_GETFL,0);
if(src_flags == -1)
perror("fcntl get error.");
return-1;
// add non-blocking
int new_flags = src_flags | O_NONBLOCK;
int ret_value;
ret_value = fcntl(fd, F_SETFL, new_flags);
if(ret_value == -1)
perror("fcntl set error.");
return-1;
return 0;
// main function
int main(int argc, char* argv[])
int server_sockfd, client_sockfd;
int server_len;
struct sockaddr_in server_address;
// create server socket fd
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
// init server address struct
bzero(&server_address, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(9567);
server_address.sin_addr.s_addr = INADDR_ANY;
server_len = sizeof(server_address);
// bind server address info for server fd
if((bind(server_sockfd, (struct sockaddr*)&server_address, server_len)) == -1)
perror("bind error");
exit(EXIT_FAILURE);
// let server is listened state
listen(server_sockfd, 5);
printf("server start waiting for connect...\r\n");
// only suggestion
int efd = epoll_create(INITSIZE);
if(-1 == efd)
printf("epoll_create error happen.\n");
return -1;
// set server_sockfd
struct epoll_event server_event, event;
server_event.data.fd = server_sockfd;
server_event.events = EPOLLIN | EPOLLET;
int ret_epollctl = epoll_ctl(efd, EPOLL_CTL_ADD, server_sockfd, &server_event);
if(-1 == ret_epollctl)
printf("epoll_ctl error happen when efd is adding server_sockfd.\n");
return -1;
/* event loop */
struct epoll_event* return_events;
// set timeout is 3000 ms
int timeout_msecond = 3000;
return_events = (struct epoll_event*)malloc(MAXEVENTCOUNT*sizeof(struct epoll_event));
int count = 0;
while(1)
int ret_epollwait = epoll_wait(efd, return_events, MAXEVENTCOUNT, timeout_msecond);
// part_1:epoll_wait error happen
if(-1 == ret_epollwait)
printf("logged epoll_wait error happen.\n");
continue;
// part_2:epoll_wait timeout
if(0 == ret_epollwait)
printf("logged epoll_wait timeout.\n");
continue;
// part_3:do some other event
int index = 0;
for(index = 0; index < MAXEVENTCOUNT; index++)
// part_3-1:hup ...
if((return_events[index].events & EPOLLERR)
|| (return_events[index].events & EPOLLHUP)
|| !(return_events[index].events & EPOLLIN) )
continue;
// part_3-2:is connection
if(return_events[index].data.fd == server_sockfd)
struct sockaddr_in client_address;
int client_len = sizeof(client_address);
// server accept connection from client
int client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_address, (socklen_t*)&client_len);
// part_3-2-1:connection error happen
if(-1 == client_sockfd)
if((EAGAIN == errno)
|| (EWOULDBLOCK == errno) )
continue;
else
printf("accept error occured.\n");
continue;
else // part_3-2-2:normal connection
// get clinet some information
char hostinfo_buf[BUFSIZE] = 0;
char servname_buf[BUFSIZE] = 0;
int tmp_ret = getnameinfo((struct sockaddr*)&client_address, client_len, hostinfo_buf, sizeof(hostinfo_buf), servname_buf, sizeof(servname_buf), NI_NUMERICHOST| NI_NUMERICSERV);
if(0 == tmp_ret)
printf("Accepted connection on descriptor %d:ip=%s, port=%s.\n", client_sockfd, hostinfo_buf, servname_buf);
// set client_sockfd to non-blocking
tmp_ret = make_socket_non_blocking(client_sockfd);
if(-1 == tmp_ret)
printf("set client_sockfd=%d to non-blocking error occured.\n", client_sockfd);
abort();
// set client_sockfd is EPOLLIN, EPOLLET
event.data.fd = client_sockfd;
event.events = EPOLLIN | EPOLLET;
tmp_ret = epoll_ctl(efd, EPOLL_CTL_ADD, client_sockfd, &event);
if(tmp_ret == -1)
printf("efd add %d has a error.\n", client_sockfd);
continue;
printf("add descriptor %d:ip=%s, port=%s successfully.\n", client_sockfd, hostinfo_buf, servname_buf);
continue;
// part_3-3:read data from client
printf("read data start++++\n");
int temp = 0;
// get recv_cache size start
int recvsize = 0;
socklen_t optlen = sizeof(recvsize);
int err = getsockopt(return_events[index].data.fd, SOL_SOCKET, SO_RCVBUF, &recvsize, &optlen);
printf("recv cache size :%d\n", recvsize);
// get recv_cache size end
while(1) // start while(1)
printf("%d times read data\n", ++temp);
char* recv_buffer = (char*)malloc(1024+1);
memset(recv_buffer, 0, 1025);
// int ret_read = read(return_events[index].data.fd, recv_buffer, sizeof(recv_buffer));
int ret_read = recv(return_events[index].data.fd, recv_buffer, sizeof(recv_buffer), 0);
// part_3-3-1:read return error
if(-1 == ret_read)
if(EAGAIN != errno)
printf("read data from %d error occured, errno=%d, %s.\n", return_events[index].data.fd, errno, strerror(errno));
break;
// part_3-3-2:no data
if(0 == ret_read)
continue;
// part_3-3-3:output data. If data is 'bye', connection will close.
if(ret_read > 0)
printf("%d client's data:size=%dbyte, content=%s\n", return_events[index].data.fd, ret_read, recv_buffer);
// part_3-3-3-1:close connection and remove client_sockfd
if((recv_buffer[0] == 'b')
&& (recv_buffer[1] == 'y')
&& (recv_buffer[2] == 'e') )
close(return_events[index].data.fd);
printf("close %d, ", return_events[index].data.fd);
int tmp_ret = epoll_ctl(efd, EPOLL_CTL_DEL, return_events[index].data.fd, NULL);
if(tmp_ret == -1)
printf("efd del %d has a error.\n", client_sockfd);
printf("remove descriptor %d successfully.\n", return_events[index].data.fd);
// end of while(1)
printf("read data finish------\n");
free(return_events);
// close server_sockfd
shutdown(server_sockfd, 2);
return 0;
epoll_client.cpp(只发送数据。)
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFSIZE 1024
int main(int argc, char* argv[])
int sock_clientfd, ret_recvsize, i;
struct sockaddr_in dest, mine;
char send_buffer[BUFSIZE + 1];
// create socket fd
if ((sock_clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
perror("Socket");
exit(EXIT_FAILURE);
// init server address that client will connetct to.
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(9567);
if(argc != 2)
printf("Usage: %s <dest ip>\n", argv[0]);
printf("Usage: %s 127.0.0.1\n", argv[0]);
return -1;
printf("-----\n");
if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0)
perror(argv[1]);
exit(1);
// connect to server
printf("will connect!\n");
if (connect(sock_clientfd, (struct sockaddr *) &dest, sizeof(dest)) != 0)
perror("Connect ");
exit(EXIT_FAILURE);
while(1)
bzero(send_buffer, BUFSIZE + 1);
printf("input message:");
fgets(send_buffer, BUFSIZE, stdin);
send_buffer[strlen(send_buffer) - 1] = '\0';
printf("%d\n", strlen(send_buffer));
int send_retsize = send(sock_clientfd, send_buffer, strlen(send_buffer), 0);
if(send_retsize == -1)
perror("send data to client error happen!");
exit(EXIT_FAILURE);
printf("send succ data:%s\n", send_buffer);
if((send_buffer[0] == 'b')
&& (send_buffer[1] == 'y')
&& (send_buffer[2] == 'e') )
printf("client active close connect.\n");
break;
// close sock_clientfd
close(sock_clientfd);
return 0;
以下图片是一些运行信息: epoll_server.png
epoll_client.png
服务端读取的数据只有8个字节,难道内核设计的epoll是这样的? 我猜原因如下:
【问题讨论】:
你会发现这个功能很有用:***.com/questions/27527395/… @GiorgiMoniava,我明白了。 您的意见无关紧要。事实上,TCP 没有提供这样的保证。 OT:recv()
和 send()
返回 ssize_t
而不是 int
。也无需在 C 中强制转换 void
-pointers。
如果recv()
返回0
对方关闭连接。不要再期待任何数据了。离开recv
-loop。
【参考方案1】:
您没有在一次读取中收到所有可用内容的原因是因为您一次只能读取 8 个字节。
char* recv_buffer = (char*)malloc(1024+1);
int ret_read = recv(return_events[index].data.fd, recv_buffer, sizeof(recv_buffer), 0);
// part_3-3-1:read return error
recv_buffer
是 char*
不是数组,因此 sizeof recv_buffer
等于指针的大小,在您的情况下为 8
。
请注意,您永远不应依赖包中的数据。如果您的消息协议规定您应该获得 10 个字节,则永远不要期望所有 10 个字节一次都可用。您应该始终以可以处理被拆分为多个读取的数据的方式进行编码。
如果线程处理单个套接字,那么一个简单的do read... while (total_bytes_received < expected_bytes);
就足够了。
如果线程处理多个连接,那么您需要保存已读取的字节,然后继续管理其他准备好的套接字,然后返回到将使用select/epoll
等待更多数据的处理循环。
【讨论】:
@Klas Lindbäck,哦,我明白了。我还有一个问题。除了我的错误,在什么情况下会出现我说的问题描述的?“粘包”还是“不完整阅读”? @study_20160808 我已经添加了有关如何从套接字读取消息以处理消息被拆分为多个 ip 数据包的情况的信息。 @ Klas Lindbäck,我查看了您更新的答案。但我不明白以下情况:我的数据内容:N 大小。最终的 Ip 包大小为 N + M 且 N+M 大小小于 1500 字节。对于这种情况,我的问题会发生吗? @study_20160808 想一想如果服务器很忙并且在服务器开始读取时从客户端收到了几条消息会发生什么。或者如果客户端发送了太多以至于发送缓冲区已满。读取和写入调用都需要假设并非所有数据都可以在一次读取/写入中读取/发送。当您阅读时,可能会获得多于(或少于)一条完整的信息。 @Klas Lindbäck,是的,我明白了,谢谢!以上是关于在 TCP 套接字程序中,客户端发送一些数据,但服务器需要多次读取。为啥?的主要内容,如果未能解决你的问题,请参考以下文章
《asyncio 系列》8. 在 asyncio 中通过流(StreamReaderStreamWriter)来实现 TCP 请求的发送与接收