C语言UDP Socket问题:recvfrom()无法按预期读取数据
UDP服务器recvfrom阻塞问题分析与解决
问题描述
我专门注册账号求助,目前陷入困境:我的目标是让UDP服务器通过recvfrom()每次读取1字节,从消息"Hello from client"中读取恰好2字节。但程序运行时第一个recvfrom()会无限阻塞(我未设置超时)。我使用Oracle VM VirtualBox中的Ubuntu虚拟机,且刚接触C语言编程。
服务器代码(udp_server.c)
int main(void) { /* Socket */ int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { perror("socket(2)"); return 1; } /* Set socket option SO_REUSEADDR */ int opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt(2)"); close(sock); return 1; } /* server sockaddr_in struct */ struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_addr.s_addr = INADDR_ANY; server.sin_family = AF_INET; server.sin_port = htons(5061); /* Bind */ if (bind(sock, (struct sockaddr*)&server, sizeof(server)) < 0) { perror("bind(2)"); close(sock); return 1; } /* client sockaddr_in struct (for recvfrom()) */ struct sockaddr_in client; socklen_t client_len = sizeof(client); memset(&client, 0, sizeof(client)); /* Receive data from socket */ fprintf(stdout, "Listening on port %d...\n", 5061); char buffer[1024] = { 0 }; int bytes_received = recvfrom(sock, buffer, 1, 0, (struct sockaddr*)&client, &client_len); // PROGRAM FREEZES HERE ^^^ if (bytes_received < 0) { printf("bytes_received %d, errno %d", bytes_received, errno); perror("recvfrom"); close(sock); return 1; } printf("SUCCESS(1) bytes_received %d", bytes_received); bytes_received = recvfrom(sock, buffer, 1, 0, (struct sockaddr*)&client, &client_len); if (bytes_received < 0) { printf("bytes_received %d, errno %d", bytes_received, errno); perror("recvfrom"); close(sock); return 1; } printf("SUCCESS(2) bytes_received %d", bytes_received); /* Shutdown */ shutdown(sock, SHUT_RDWR); close(sock); return 0; }
客户端代码(udp_client.c)
int main(void) { /* Socket */ int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { // In case socket creation failed. fprintf(stdout, "ERROR: Socket creation failed.\n"); return -1; } /* Server sockaddr_in struct (for sendto()) */ struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(5061); if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) <= 0) { perror("inet_pton(3)"); close(sock); return 1; } /* Send data to socket */ char* message = "Hello from client"; int bytes_sent = sendto(sock, message, strlen(message), 0, (struct sockaddr*)&server, sizeof(server)); if (bytes_sent <= 0) { perror("sendto(2)"); close(sock); return 1; } fprintf(stdout, "Sent %d bytes to %s:%d - %s\n", bytes_sent, inet_ntoa(server.sin_addr), ntohs(server.sin_port), message); usleep(75000); /* Shutdown */ shutdown(sock, SHUT_RDWR); close(sock); return 0; }
问题分析与解决方案
1. 最可能的阻塞原因:运行顺序错误
UDP是无连接协议,客户端发送的数据包如果没有对应的服务器在监听端口,会直接被系统丢弃。如果你先启动客户端,再启动服务器,服务器启动后根本收不到之前发送的数据包,导致第一个recvfrom()一直阻塞等待数据。
解决:先启动服务器,待服务器打印"Listening on port 5061..."后,再启动客户端发送数据。
2. UDP协议特性导致的逻辑问题
UDP是面向数据包的协议,和TCP的字节流模型完全不同:
- 客户端一次
sendto()发送的是一个完整的UDP数据包,所有字节会被打包成一个单元发送。 - 服务器的
recvfrom()每次只能读取一个完整的数据包:- 如果你的缓冲区大小(这里是1字节)小于数据包总长度,
recvfrom()会读取前1字节,剩余的字节直接被丢弃,不会留在系统缓冲区中。 - 第二次调用
recvfrom()会再次阻塞,因为客户端只发送了一个数据包,没有新的数据包到达。
- 如果你的缓冲区大小(这里是1字节)小于数据包总长度,
3. 实现"读取2字节"目标的正确方式
根据你的需求,有两种可行方案:
方案一:一次性读取足够字节
修改服务器的recvfrom(),将缓冲区长度设为2,一次读取整个数据包的前2字节,代码如下:int bytes_received = recvfrom(sock, buffer, 2, 0, (struct sockaddr*)&client, &client_len); if (bytes_received >=2) { printf("读取到前2字节:%c%c\n", buffer[0], buffer[1]); }方案二:客户端分两次发送
修改客户端代码,分两次sendto()各发送1字节,这样服务器的两次recvfrom()都能收到数据:// 替换原sendto代码 char* message = "Hello from client"; // 发送第一个字节 int bytes_sent = sendto(sock, message, 1, 0, (struct sockaddr*)&server, sizeof(server)); if (bytes_sent <=0) { /* 错误处理 */ } usleep(1000); // 可选,确保数据包顺序 // 发送第二个字节 bytes_sent = sendto(sock, message+1, 1, 0, (struct sockaddr*)&server, sizeof(server)); if (bytes_sent <=0) { /* 错误处理 */ }
内容的提问来源于stack exchange,提问作者Jacob
相关产品推荐
相关产品推荐

