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

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()会再次阻塞,因为客户端只发送了一个数据包,没有新的数据包到达。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:15:54