TCP服务器仅读取首次消息问题:原因排查求助
问题原因分析与解决方案
嘿,我一眼就揪出问题所在了——你的服务器现在的逻辑是每接受一个新连接,只读取一次客户端发来的数据,然后就彻底把这个客户端连接丢在一边,转头去等下一个新连接了!这就是为什么你只能收到每条新连接的第一条消息,后续消息没人处理的核心原因。
具体问题拆解
- 你把监听套接字
fd_serv设置为非阻塞模式,accept会立即返回,但你完全没处理accept返回错误的情况(比如没有新连接时,accept会返回-1,errno为EAGAIN或EWOULDBLOCK),不过这不是最关键的。 - 核心逻辑缺陷:当
accept成功拿到客户端套接字fd_client_serv后,你只执行了一次read,然后直接回到循环顶部再次调用accept。这个客户端套接字之后就被程序彻底遗忘了,没有任何后续逻辑去读取它发来的新数据,也没有关闭它,时间长了还会造成套接字资源泄漏。
解决方案
方案1:单客户端持续处理(简单但无法同时处理多客户端)
如果你暂时只需要处理单个客户端的连续消息,可以在拿到客户端套接字后,进入一个子循环,持续读取数据直到客户端断开连接:
void start_server(void) { int fd_serv, fd_client_serv, bytes_read; struct sockaddr_in client_serv_addr; char buf[2048]; socklen_t sin_len_serv = sizeof(client_serv_addr); fd_serv = initialize_server(get_serving_port()); // 监听套接字非阻塞,但客户端套接字默认是阻塞的,这里可以保持默认 // fd_set_blocking(fd_serv, 0); while(1) { fd_client_serv = accept(fd_serv, (struct sockaddr *) &client_serv_addr, &sin_len_serv); // 处理accept错误:没有新连接时跳过 if (fd_client_serv == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("accept error"); } usleep(1000); // 避免空循环占用CPU continue; } puts("New client connected!!!"); write(fd_client_serv, "thank you for connecting", 24); // 子循环:持续读取该客户端的消息 while(1) { memset(buf, 0, sizeof(buf)); // 留一个位置给'\0',避免字符串越界 bytes_read = read(fd_client_serv, buf, sizeof(buf)-1); if (bytes_read == -1) { perror("read error"); break; } else if (bytes_read == 0) { puts("Client disconnected"); break; } printf("Received %d bytes: %s\n", bytes_read, buf); fflush(stdout); } close(fd_client_serv); // 关闭客户端套接字,释放资源 } }
方案2:IO多路复用(推荐,支持多客户端同时连接)
如果需要同时处理多个客户端的消息,推荐使用select实现IO多路复用,这样可以同时监听监听套接字(等待新连接)和所有已连接的客户端套接字(等待消息):
#include <sys/select.h> void start_server(void) { int fd_serv, fd_client_serv, bytes_read; struct sockaddr_in client_serv_addr; char buf[2048]; socklen_t sin_len_serv = sizeof(client_serv_addr); fd_set read_fds; int max_fd; fd_serv = initialize_server(get_serving_port()); fd_set_blocking(fd_serv, 0); // 监听套接字设为非阻塞 FD_ZERO(&read_fds); FD_SET(fd_serv, &read_fds); max_fd = fd_serv; while(1) { fd_set temp_fds = read_fds; // 每次select都要重新设置,因为select会修改集合 int ret = select(max_fd + 1, &temp_fds, NULL, NULL, NULL); if (ret == -1) { perror("select error"); exit(1); } // 检查监听套接字是否有新连接 if (FD_ISSET(fd_serv, &temp_fds)) { fd_client_serv = accept(fd_serv, (struct sockaddr *) &client_serv_addr, &sin_len_serv); if (fd_client_serv == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("accept error"); } continue; } puts("New client connected!!!"); write(fd_client_serv, "thank you for connecting", 24); fd_set_blocking(fd_client_serv, 0); // 客户端套接字也设为非阻塞 FD_SET(fd_client_serv, &read_fds); if (fd_client_serv > max_fd) { max_fd = fd_client_serv; } } // 检查已连接的客户端是否有消息 for (int i = fd_serv + 1; i <= max_fd; i++) { if (FD_ISSET(i, &temp_fds)) { memset(buf, 0, sizeof(buf)); bytes_read = read(i, buf, sizeof(buf)-1); if (bytes_read == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read error"); close(i); FD_CLR(i, &read_fds); } continue; } else if (bytes_read == 0) { printf("Client %d disconnected\n", i); close(i); FD_CLR(i, &read_fds); // 更新max_fd,避免无效遍历 if (i == max_fd) { while (max_fd >= 0 && !FD_ISSET(max_fd, &read_fds)) { max_fd--; } } continue; } printf("Client %d sent %d bytes: %s\n", i, bytes_read, buf); fflush(stdout); } } } }
额外注意点
- 处理非阻塞套接字的
read/write时,一定要判断返回值和errno:如果返回-1且errno是EAGAIN或EWOULDBLOCK,这不是错误,只是当前没有数据可读写,稍后再试即可。 - 无论哪种方案,客户端断开连接后一定要调用
close()关闭套接字,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Darren Col
相关产品推荐
相关产品推荐

