非阻塞UDP套接字调用recvfrom始终返回EWOULDBLOCK无法接收消息的问题求助
非阻塞UDP套接字调用recvfrom始终返回EWOULDBLOCK无法接收消息的问题求助
看起来你遇到的问题主要是错误处理逻辑的漏洞,再加上一些代码细节的小问题,共同导致了非阻塞模式下recvfrom一直返回EWOULDBLOCK的假象。让我一步步帮你分析和修复:
核心问题分析
你的代码最关键的错误是错误处理逻辑完全颠倒了:你现在是调用recvfrom后直接检查errno == EWOULDBLOCK,但正确的逻辑应该是先判断recvfrom的返回值是否为-1,再去检查errno的值。
原因很简单:如果recvfrom成功接收到数据,返回值是正数(接收到的字节数),此时errno的值是未定义的(可能保留了之前的错误状态,或者是随机值),这会导致你误判以为又出现了EWOULDBLOCK错误。
除此之外还有两个次要问题:
- 复用了同一个
addr结构体,既用来设置服务器绑定地址,又用来存储客户端地址,容易造成逻辑混淆; - 每次调用
recvfrom前没有重置客户端地址长度变量,若之前的调用修改了这个值,会导致后续recvfrom行为异常。
修复后的服务器代码
// 修正后的 server.c #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <time.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUFSIZE 1024 #define PORT 12345 int main(int argc, char** argv) { char buffer[BUFSIZE]; int rv; int sock; // 分开服务器地址和客户端地址,避免复用混淆 struct sockaddr_in server_addr, client_addr; socklen_t client_addr_len = sizeof(client_addr); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 添加socket创建的错误检查 sock = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); if (sock == -1) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 添加bind的错误检查 if (bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("bind failed"); close(sock); exit(EXIT_FAILURE); } while (1) { memset(buffer, 0, sizeof(buffer)); // 每次调用recvfrom前,重置客户端地址长度为正确值 client_addr_len = sizeof(client_addr); rv = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &client_addr_len); if (rv == -1) { // 只有recvfrom返回-1时,才需要检查errno if (errno == EWOULDBLOCK || errno == EAGAIN) { // 无数据待接收,属于正常情况 puts("."); } else { // 真正的系统错误,打印并退出 perror("recvfrom error"); close(sock); exit(EXIT_FAILURE); } } else { // 成功接收到客户端消息,处理并打印 char ipstr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, sizeof(ipstr)); buffer[rv] = '\0'; // 注意:客户端端口是网络字节序,要用ntohs转为主机字节序 printf("new request from [%s:%d]:\n%s\n", ipstr, ntohs(client_addr.sin_port), buffer); } sleep(1); } close(sock); return 0; }
客户端代码的可选优化
你的客户端代码基本没问题,这里只是添加了系统调用的错误检查,让代码更健壮:
// 优化后的 client.c #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUFSIZE 1024 #define PORT 12345 #define IPADDR "127.0.0.1" const char* msgtosend = "can you hear me?"; int main(int argc, char** argv) { char buffer[BUFSIZE]; int sock; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(PORT); if (inet_pton(AF_INET, IPADDR, &addr.sin_addr.s_addr) <= 0) { perror("inet_pton error"); exit(EXIT_FAILURE); } sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { perror("socket creation failed"); exit(EXIT_FAILURE); } // UDP的connect只是设置默认发送地址,失败概率低但仍建议检查 if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("connect error"); close(sock); exit(EXIT_FAILURE); } while (1) { ssize_t send_rv = send(sock, msgtosend, strlen(msgtosend), 0); if (send_rv == -1) { perror("send error"); close(sock); exit(EXIT_FAILURE); } puts("."); sleep(1); } close(sock); return 0; }
修复效果说明
现在运行修改后的服务器和客户端,你应该能正常接收到客户端发送的消息了。核心的修复点是:
- 修正了
recvfrom的错误处理逻辑,只有当返回-1时才检查errno; - 每次调用
recvfrom前重置客户端地址长度,确保参数正确; - 拆分了服务器地址和客户端地址的结构体,避免逻辑混淆;
- 添加了系统调用的错误检查,方便快速定位异常。
备注:内容来源于stack exchange,提问作者ugo_capeto




