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

Windows平台UDP多线程收发实现问题及错误排查咨询

Windows UDP多线程收发错误排查与实现方案

一、错误码及通用问题原因分析

1. 错误码10047(WSAEAFNOSUPPORT)

  • 核心问题:地址族不匹配。比如创建UDP Socket时用了AF_INET(IPv4),但绑定/发送时传入了struct sockaddr_in6(IPv6)结构体,或是初始化sockaddr时sin_family字段设置错误。常见场景是直接复用TCP的地址结构体但未修改地址族参数。

2. 错误码10038(WSAENOTSOCK)

  • 核心问题:操作无效Socket句柄。比如Socket创建失败未检查返回值就继续操作;多线程环境下Socket被某一线程关闭后,其他线程仍调用recvfrom/sendto;变量被意外覆盖导致Socket句柄失效。

3. "Error while recv frame" 通用排查点

  • 误用TCP的recv函数:UDP必须用recvfrom获取发送方地址,直接用recv会触发错误。
  • 服务器未绑定端口:UDP服务器必须调用bind,否则无法接收数据;客户端可省略绑定,但发送时必须指定目标地址。
  • 多线程资源冲突:若多个线程共享缓冲区、状态变量等非Socket资源,未加同步锁会导致逻辑错误,表现为接收失败。

二、正确的Windows UDP多线程收发实现示例

1. 服务器端完整代码

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024
#define PORT 8888

SOCKET g_udpSocket;
BOOL g_exitFlag = FALSE;

// 接收线程
DWORD WINAPI RecvThread(LPVOID lpParam) {
    char buf[BUF_SIZE];
    struct sockaddr_in clientAddr;
    int addrLen = sizeof(clientAddr);
    int recvLen;

    while (!g_exitFlag) {
        recvLen = recvfrom(g_udpSocket, buf, BUF_SIZE, 0, (struct sockaddr*)&clientAddr, &addrLen);
        if (recvLen == SOCKET_ERROR) {
            if (WSAGetLastError() == WSAEWOULDBLOCK) {
                Sleep(10);
                continue;
            }
            printf("recvfrom failed, error: %d\n", WSAGetLastError());
            break;
        }
        buf[recvLen] = '\0';
        printf("Received from %s:%d: %s\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), buf);
        
        // 可选:回复客户端
        sendto(g_udpSocket, "Received", 8, 0, (struct sockaddr*)&clientAddr, addrLen);
    }
    return 0;
}

// 发送线程
DWORD WINAPI SendThread(LPVOID lpParam) {
    char buf[BUF_SIZE];
    struct sockaddr_in targetAddr;
    targetAddr.sin_family = AF_INET;
    targetAddr.sin_port = htons(PORT);
    targetAddr.sin_addr.s_addr = INADDR_ANY;

    while (!g_exitFlag) {
        printf("Input message to send: ");
        fgets(buf, BUF_SIZE, stdin);
        buf[strcspn(buf, "\n")] = '\0';

        int sendLen = sendto(g_udpSocket, buf, strlen(buf), 0, (struct sockaddr*)&targetAddr, sizeof(targetAddr));
        if (sendLen == SOCKET_ERROR) {
            printf("sendto failed, error: %d\n", WSAGetLastError());
            break;
        }
    }
    return 0;
}

int main() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed\n");
        return 1;
    }

    g_udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (g_udpSocket == INVALID_SOCKET) {
        printf("socket failed, error: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // 可选:设置非阻塞模式,避免recvfrom阻塞线程
    u_long mode = 1;
    if (ioctlsocket(g_udpSocket, FIONBIO, &mode) == SOCKET_ERROR) {
        printf("ioctlsocket failed, error: %d\n", WSAGetLastError());
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    if (bind(g_udpSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("bind failed, error: %d\n", WSAGetLastError());
        closesocket(g_udpSocket);
        WSACleanup();
        return 1;
    }

    HANDLE hRecvThread = CreateThread(NULL, 0, RecvThread, NULL, 0, NULL);
    HANDLE hSendThread = CreateThread(NULL, 0, SendThread, NULL, 0, NULL);

    printf("UDP server running, press any key to exit...\n");
    getchar();

    g_exitFlag = TRUE;
    WaitForSingleObject(hRecvThread, INFINITE);
    WaitForSingleObject(hSendThread, INFINITE);

    closesocket(g_udpSocket);
    CloseHandle(hRecvThread);
    CloseHandle(hSendThread);
    WSACleanup();

    return 0;
}

2. 核心注意事项

  • 地址族统一:所有sockaddr_in的sin_family必须设为AF_INET(IPv4),若用IPv6则统一用AF_INET6和sockaddr_in6。
  • API正确使用:UDP接收用recvfrom,发送用sendto,禁用TCP专属的listen/accept/recv/send函数。
  • 句柄有效性检查:Socket创建、绑定、收发操作后必须检查返回值,避免无效句柄操作。
  • 多线程同步:UDP Socket本身支持并发读写,但共享缓冲区、配置变量等资源时,需用CRITICAL_SECTION或互斥量保护。
  • 资源清理:退出时先通知线程结束,等待线程终止后再关闭Socket、清理WSA环境。

三、常见坑点规避

  • 客户端发送时必须指定目标地址(sendto的最后两个参数),否则会返回错误。
  • 非阻塞模式下需处理WSAEWOULDBLOCK错误,避免循环占用CPU。
  • 端口和IP地址要做字节序转换:发送用htons/htonl转为网络字节序,接收用ntohs/ntohl转回主机字节序。

内容的提问来源于stack exchange,提问作者João Carlos Dantas Lopes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:15:21