You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 05:12:02