Linux下C语言UDP Socket中recv超时的正确处理方法
UDP客户端超时处理与完善错误实现方案
我写了一段通过UDP从服务器获取值的C语言代码,当前版本能正常运行,但错误处理不完善。已经尝试捕获socket创建、send操作的错误返回值,但当目标IP不存在时,socket仍能创建并发送UDP包(这可能是正常情况),但程序会卡在recvfrom处无限等待。希望实现接收超时机制,让程序在无服务器回复时返回并提示用户服务器未响应,请教最佳实现方式,目前多数资料未覆盖该场景。现有代码如下:
int getcount () { /*TODO: Add proper error handling: error when socket is opened error when sending error when receiving and add receiver timeout */ char *ip = "192.168.1.183"; int port = 27015; int sockfd; int err; struct sockaddr_in addr; char buffer[1024]; //Store the server reply here char firstreq[1024] = "ÿÿÿÿUÿÿÿÿ"; //This is what we need to ask to get a challengekey char challenge[1024]; //Something to store the challenge in char concatted[1024] = "ÿÿÿÿU"; //Something to add the challenge to and then request the actual data socklen_t addr_size; memset(&addr, '\0', sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(ip); sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) //Try to catch error when opening the fd for the socket. { printf ("Socket open failed"); return(-1); } addr_size = sizeof(addr); //Send this message to get a reply with a challenge value: err = sendto(sockfd, firstreq, 9, 0, (struct sockaddr*)&addr, sizeof(addr)); if (err < 0) //Try to catch error when sending the request. { printf("Send failed"); return (err); } //Recieve the challenge value: err = recvfrom(sockfd, challenge, 9, 0, (struct sockaddr*)&addr, &addr_size); //Concat the challenge value and message together: memcpy(concatted + 4 + 1, challenge + 5, 10 - 5 + 1); //Reply with this to server sendto(sockfd, concatted, 9, 0, (struct sockaddr*)&addr, sizeof(addr)); bzero(buffer, 1024); //Recieve the data from server: recvfrom(sockfd, buffer, 1024, 0, (struct sockaddr*)&addr, &addr_size); return(buffer[5]); //Return the 6th value that is the value we want. }
解决方案
一、核心问题解决:接收超时实现
UDP是无连接协议,recvfrom默认处于阻塞模式,要避免无限等待,最简洁可靠的方式是通过套接字选项SO_RCVTIMEO设置超时,当超过指定时间未收到数据时,recvfrom会返回错误并设置errno为EAGAIN或EWOULDBLOCK。
二、完善错误处理与超时的最终代码
以下是修改后的完整代码,包含所有错误处理与超时逻辑:
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> #define RECV_TIMEOUT_SEC 3 // 接收超时时间(秒) #define RECV_TIMEOUT_USEC 0 // 接收超时时间(微秒) int getcount() { char *ip = "192.168.1.183"; int port = 27015; int sockfd; ssize_t err; // 适配sendto/recvfrom的返回值类型 struct sockaddr_in addr; char buffer[1024]; const char firstreq[] = "ÿÿÿÿUÿÿÿÿ"; // 用const固定请求内容 char challenge[1024]; char concatted[1024] = "ÿÿÿÿU"; socklen_t addr_size; struct timeval timeout; // 初始化地址结构 memset(&addr, '\0', sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); // 检查IP地址有效性 addr.sin_addr.s_addr = inet_addr(ip); if (addr.sin_addr.s_addr == INADDR_NONE) { fprintf(stderr, "无效的IP地址\n"); return -1; } // 创建UDP套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("套接字创建失败"); return -1; } // 设置接收超时 timeout.tv_sec = RECV_TIMEOUT_SEC; timeout.tv_usec = RECV_TIMEOUT_USEC; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { perror("设置接收超时失败"); close(sockfd); return -1; } addr_size = sizeof(addr); // 发送获取挑战值的请求 err = sendto(sockfd, firstreq, sizeof(firstreq)-1, 0, (struct sockaddr*)&addr, sizeof(addr)); if (err < 0) { perror("发送初始请求失败"); close(sockfd); return -1; } // 接收挑战值(带超时) err = recvfrom(sockfd, challenge, sizeof(challenge)-1, 0, (struct sockaddr*)&addr, &addr_size); if (err < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { fprintf(stderr, "服务器未响应挑战请求\n"); } else { perror("接收挑战值失败"); } close(sockfd); return -1; } challenge[err] = '\0'; // 确保字符串终止,避免越界 // 拼接请求(添加边界检查) size_t challenge_len = strlen(challenge); if (challenge_len < 5 || (sizeof(concatted)-1) < (5 + (challenge_len -5))) { fprintf(stderr, "挑战数据无效或缓冲区过小\n"); close(sockfd); return -1; } memcpy(concatted + 5, challenge + 5, challenge_len -5); // 发送拼接后的请求 err = sendto(sockfd, concatted, strlen(concatted), 0, (struct sockaddr*)&addr, sizeof(addr)); if (err < 0) { perror("发送拼接请求失败"); close(sockfd); return -1; } memset(buffer, 0, sizeof(buffer)); // 接收最终数据(带超时) err = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&addr, &addr_size); if (err < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { fprintf(stderr, "服务器未响应数据请求\n"); } else { perror("接收数据失败"); } close(sockfd); return -1; } buffer[err] = '\0'; // 确保字符串终止 close(sockfd); // 关闭套接字,避免资源泄漏 return buffer[5]; }
三、关键修改说明
- 超时机制:通过
SO_RCVTIMEO设置接收超时,超时后精准判断并提示用户服务器未响应。 - 错误处理强化:用
perror输出系统调用的具体错误信息;所有失败路径都关闭套接字,避免资源泄漏;添加IP地址有效性检查。 - 内存安全:对
memcpy添加边界检查,防止缓冲区溢出;接收数据后添加字符串终止符,避免后续操作出错。 - 类型修正:用
ssize_t接收sendto/recvfrom的返回值,匹配函数实际返回类型,避免类型不匹配问题。
内容的提问来源于stack exchange,提问作者ObligedTester
相关产品推荐
相关产品推荐

