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

C语言中向已关闭UDP套接字发送数据未触发错误的解决方法

UDP套接字检测对方关闭状态的问题解决

为什么sendto一直返回正值?

UDP是无连接、不可靠的传输协议,sendto的返回值仅表示数据成功写入了本地内核的发送缓冲区,完全不代表数据已经被对方接收,更无法感知对方的socket是否关闭。即使对方已经关闭了UDP套接字,发送端的sendto只要能把数据放进本地缓冲区,就会返回成功。

可行的解决方案

因为UDP本身没有连接状态维护,必须通过应用层协议来实现对端状态的检测,以下是几种可靠的方案:

方案1:接收端主动发送关闭通知

让接收端在关闭socket之前,主动给发送端发送一个约定好的“关闭信号”数据包,发送端收到该数据包后直接终止程序。这是最直接可靠的方式。

接收端修改代码:

printf("Closing the socket\n");
// 发送关闭信号(需要提前保存发送端的地址si_sender)
char shutdown_msg[] = "SHUTDOWN_SIGNAL";
sendto(s, shutdown_msg, sizeof(shutdown_msg), 0, (struct sockaddr *)&si_sender, sizeof(si_sender));
close(s);
return;

发送端修改代码:

int flags = fcntl(s, F_GETFL, 0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);
int x = recv(s, buffer, sizeof(buffer), 0);
if (x > 0) {
    buffer[x] = '\0'; // 确保字符串终止
    // 检查是否是关闭信号
    if (strcmp(buffer, "SHUTDOWN_SIGNAL") == 0) {
        printf("Received shutdown signal, exiting...\n");
        return;
    }
    // 处理正常业务数据
} else if (x == -1) {
    // 非阻塞下无数据,可选择发送心跳包(可选)
    char heartbeat[] = "HEARTBEAT";
    int send_result = sendto(s, heartbeat, sizeof(heartbeat), 0, (struct sockaddr *)&si_other, sizeof(si_other));
    if (send_result == -1) {
        perror("sendto failed");
        return;
    }
    continue;
}

方案2:心跳+超时检测

如果接收端可能异常崩溃(无法主动发关闭信号),可以用心跳机制:

  • 发送端定期发送心跳包
  • 接收端收到心跳包后必须回应一个确认包
  • 发送端如果在指定时间内没收到回应,就判定对方已断开,终止程序

示例代码(发送端):

// 初始化非阻塞socket
int flags = fcntl(s, F_GETFL, 0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);

int heartbeat_interval = 3; // 每3秒发一次心跳
time_t last_heartbeat = time(NULL);
time_t last_response = time(NULL);
const int timeout = 10; // 10秒没回应就判定断开

while (1) {
    int x = recv(s, buffer, sizeof(buffer), 0);
    if (x > 0) {
        buffer[x] = '\0';
        if (strcmp(buffer, "SHUTDOWN_SIGNAL") == 0) {
            return;
        } else if (strcmp(buffer, "HEARTBEAT_ACK") == 0) {
            last_response = time(NULL); // 更新最后回应时间
        }
        // 处理其他数据
    } else if (x == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
        perror("recv failed");
        return;
    }

    // 到时间发送心跳包
    time_t now = time(NULL);
    if (now - last_heartbeat >= heartbeat_interval) {
        char heartbeat[] = "HEARTBEAT";
        sendto(s, heartbeat, sizeof(heartbeat), 0, (struct sockaddr *)&si_other, sizeof(si_other));
        last_heartbeat = now;
    }

    // 检查是否超时
    if (now - last_response >= timeout) {
        printf("No response from peer, exiting...\n");
        return;
    }

    usleep(100000); // 避免循环太频繁
}

方案3:利用ICMP错误(不可靠)

部分场景下,当接收端关闭socket且主机在线时,发送端发送数据后,对方主机会返回ICMP端口不可达错误。但这个方法依赖网络环境(比如防火墙可能拦截ICMP),且需要特殊处理socket才能捕获该错误:

  • 先对UDP socket调用connect指定目标地址(这样socket会关联到固定目标)
  • 后续用send代替sendto,当收到ICMP错误时,recv会返回-1,errno为ECONNREFUSED

但注意:这个方法不能保证100%生效,仅作为补充方案。

总结

UDP本身没有连接状态,无法通过sendto或recv的返回值直接检测对方socket是否关闭,必须通过应用层协议来实现状态同步,方案1和方案2是生产环境中常用的可靠方式。

内容的提问来源于stack exchange,提问作者SarthakSin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 07:25:07