C语言客户端recv调用阻塞,服务端已确认收数,仅MacOS存在该问题
macOS下C语言TCP客户端recv阻塞问题排查
我用C语言编写了一个简单的TCP客户端-服务端程序,服务端接收客户端消息后返回响应。服务端能正常接收消息并打印Received: Hello Server后退出,但客户端发送消息后卡在recv调用处,尽管Wireshark显示服务端的响应已发送并得到确认。该问题仅在macOS上出现,其他系统可正常运行。
client.c
#include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("Could not create socket"); exit(EXIT_FAILURE); } struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8080); if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { perror("Could not generate IP-address"); exit(EXIT_FAILURE); } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Could not connect to server"); exit(EXIT_FAILURE); } if (send(sock, "Hello Server", strlen("Hello Server"), 0) < 0) { perror("Could not send message"); exit(EXIT_FAILURE); } char buffer[1024] = {0}; if (recv(sock, buffer, 1023, 0) < 0) { perror("Could not receive message from server"); exit(EXIT_FAILURE); } printf("%s\n", buffer); close(sock); return 0; }
server.c
#include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("Could not create socket"); exit(EXIT_FAILURE); } struct sockaddr_in address; socklen_t addrlen = sizeof(address); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(8080); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("Could not bind to host address"); exit(EXIT_FAILURE); } if (listen(server_fd, 1) < 0) { perror("Could not start listening"); exit(EXIT_FAILURE); } int client_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen); if (client_socket < 0) { perror("Could not accept client"); exit(EXIT_FAILURE); } char buffer[1024] = {0}; if (recv(client_socket, buffer, 1023, 0) < 0) { perror("Could not receive message"); exit(EXIT_FAILURE); } printf("Received: %s\n", buffer); if (send(client_socket, "Hello World", strlen("Hello World"), 0) < 0) { perror("Could not send message"); exit(EXIT_FAILURE); } shutdown(client_socket, SHUT_RDWR); // wait for message to be received close(server_fd); return 0; }
服务端运行输出:
Received: Hello Server
问题原因分析
这是代码bug,并非macOS的网络特性导致。问题出在服务端的资源清理步骤:
服务端在发送响应后,仅调用shutdown(client_socket, SHUT_RDWR)并关闭了监听socket(server_fd),但没有显式关闭与客户端通信的client_socket文件描述符。虽然进程退出时内核会自动回收打开的文件描述符,但在macOS的TCP栈实现中,这种未显式关闭客户端socket的操作会导致客户端无法正确感知连接状态的变化:
- 服务端发送的"Hello World"确实已到达客户端接收缓冲区,但客户端的
recv是阻塞调用,在没有收到连接关闭的FIN包时,会一直等待更多数据; - 服务端的
shutdown(SHUT_RDWR)虽然关闭了socket的读写流,但未关闭文件描述符,macOS内核可能不会立即向客户端发送FIN包,导致客户端不知道这是最后一批数据,从而卡在recv中。
修复方法
修改服务端代码,在发送响应后显式关闭client_socket,再关闭监听socket:
// 替换原代码中的shutdown和close部分 close(client_socket); // 先关闭客户端连接socket close(server_fd); // 再关闭监听socket
或者保留shutdown但补充close(client_socket):
shutdown(client_socket, SHUT_RDWR); close(client_socket); // 显式关闭客户端socket close(server_fd);
这样能确保macOS内核正确发送FIN包,客户端的recv在读取完响应数据后会正常返回,不会再阻塞。
内容的提问来源于stack exchange,提问作者Viliam Holly
相关产品推荐
相关产品推荐

