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

Winsock2 TCP客户端recv()无法接收自身发送消息问题

Winsock2 TCP客户端多线程recv无法接收回传消息的根因及修复

问题根因

代码存在4处明确逻辑错误,其中线程传参错误是导致多线程场景下recv无法收包的核心原因:

  • 线程传参类型不匹配:调用CreateThread创建接收线程时,传入的参数是主线程栈上socket变量的地址&s(类型为SOCKET*),但线程函数内直接将该指针值强转为SOCKET类型的句柄调用recv,相当于用无效的随机句柄执行网络操作,自然无法收到数据。在主线程内顺序调用recv时直接使用正确的socket句柄值,因此功能表现正常。
  • send发送长度错误:发送消息时固定传入sizeof(buff)作为发送长度,每次发送4096字节,但实际有效消息仅为拼接后的字符串长度,多余字节为栈上未初始化的垃圾内存,既浪费带宽也可能导致服务端解析异常。
  • recv未做字符串终止和错误处理:recv函数不会自动在接收数据末尾添加C字符串要求的\0终止符,直接调用printf("%s", output)会读取缓冲区残留数据甚至越界访问栈内存,导致输出乱码;同时代码未判断recv返回值,连接断开、调用出错时死循环会持续无效运行。
  • scanf传参存在未定义行为:读取用户名、控制台输入时,对char数组名额外取地址传入scanf,参数类型不匹配,可能造成栈内存写入异常。

修复后代码

#include <stdio.h>
#include <windows.h>
#include <winsock2.h>
#include <time.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")

DWORD WINAPI receive(LPVOID s) {
    SOCKET sock = (SOCKET)s;
    char output[4096];
    int recv_len;
    for(;;) {
        recv_len = recv(sock, output, sizeof(output) - 1, 0); // 预留1字节存字符串终止符
        if (recv_len <= 0) {
            printf("disconnected from server, error code: %d\n", WSAGetLastError());
            break;
        }
        output[recv_len] = '\0'; // 手动补全字符串终止符
        printf("%s\n", output);
    }
    return 0;
}

int main() {
    WSADATA wsa;
    SOCKET s;
    struct sockaddr_in server;
    clock_t start;
    double finished;

    start = clock();
    printf("initializing...\n");
    if(WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        printf("failed: %d", WSAGetLastError());
        return 1;
    }
    
    finished = (double) (clock() - start) / CLOCKS_PER_SEC;
    printf("initialized %fs\n", finished);
    
    start = clock();
    printf("creating socket...\n");
    if((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        printf("socket creation failed: %d", WSAGetLastError());
        return 1;
    }

    finished = (double) (clock() - start) / CLOCKS_PER_SEC;
    printf("socket created %fs\n", finished);
    
    printf("what ip would you like to connect to: ");
    char ip[64];
    scanf("%s", ip);
    server.sin_addr.s_addr = inet_addr(ip);
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);

    if(connect(s, (struct sockaddr*) &server, sizeof(server)) < 0) {
        printf("connect error: %d\n", WSAGetLastError());
        return 1;
    }
    
    printf("connection to %s was successful\n", ip);

    char username[256];
    printf("what username would you like to use: ");
    scanf(" %[^\n]s", username); // 数组名本身为首地址,无需额外取地址

    HANDLE handle = CreateThread(NULL, 0, receive, (LPVOID)s, 0, NULL); // 直接传socket句柄值,不传栈地址
    CloseHandle(handle); // 关闭线程句柄避免资源泄漏
    char input[4096];
    char buff[4096];
    for(;;) {
        printf(">: ");
        scanf(" %[^\n]s", input); // 数组名本身为首地址,无需额外取地址
        sprintf(buff, "%s> %s", username, input);
        int send_len = strlen(buff);
        int send_ret = send(s, buff, send_len, 0); // 仅发送实际有效消息长度
        if (send_ret == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            break;
        }
    }

    closesocket(s);
    WSACleanup();
    return 0;
}

补充说明:如果服务端逻辑为“仅转发消息给除发送者外的其他连接”,客户端自然无法收到自己发送的消息,可根据需求调整服务端转发规则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.22 16:12:25