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

Winsock自定义字符串收发函数反向调用报10014错误排查

问题根因

你遇到的10014错误以及代码中的潜在问题共有5个核心点:

  • 触发10014错误的直接原因:sendStrBuffer函数硬编码使用全局变量ClientSocket作为发送句柄,函数定义根本没有接收socket参数。服务端调用时传入的连接句柄s被直接忽略,函数使用的ClientSocket在服务端侧是无效值,传入send系统调用直接触发WSAEFAULT(10014)地址错误。之前客户端发、服务端收能跑通,仅因为客户端侧全局ClientSocket刚好是有效值,属于巧合。
  • 严重内存错误:你修改后的recvStrBuffer返回栈上局部数组buffer的地址,函数返回后栈内存自动回收,该指针是悬空指针,访问属于未定义行为,随时会崩溃。
  • 逻辑漏洞:收发4字节长度头时没有做循环读写,TCP是流协议,不保证一次send/recv就能处理完指定长度的数据,极端场景下会拿到错误的长度值,后续读写直接越界。
  • 越界风险:recvStrBuffer没有校验对端发来的sizeBuffer长度是否超过本地缓冲区实际大小,如果对端发送超大长度值,会直接写爆内存。
  • 参数错误:你调用sendStrBuffer传字符串时,传入的长度大于字符串实际长度,send会读取字符串后方的栈上随机垃圾值,甚至触发读越界。
修复后的正确实现

统一两个函数的参数规则,显式传入socket句柄,修复所有上述问题:

#include <winsock2.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

// 发送函数:第一个参数显式传入目标socket,不再硬编码全局句柄
int sendStrBuffer(SOCKET sock, const char* buffer, uint32_t dataLen)
{
    // 循环发送4字节网络字节序长度头,保证完整发送
    uint32_t netLen = htonl(dataLen);
    const char* lenPtr = (const char*)&netLen;
    int totalSent = 0;
    int ret = 0;
    while (totalSent < sizeof(uint32_t))
    {
        ret = send(sock, lenPtr + totalSent, sizeof(uint32_t) - totalSent, 0);
        if (ret == SOCKET_ERROR)
        {
            printf("send length header failed, err: %d\n", WSAGetLastError());
            return SOCKET_ERROR;
        }
        totalSent += ret;
    }

    // 循环发送实际业务数据,保证完整发送
    totalSent = 0;
    while (totalSent < dataLen)
    {
        ret = send(sock, buffer + totalSent, dataLen - totalSent, 0);
        if (ret == SOCKET_ERROR)
        {
            printf("send payload failed, err: %d\n", WSAGetLastError());
            return SOCKET_ERROR;
        }
        totalSent += ret;
    }
    return 0; // 发送成功返回0
}

// 接收函数:由调用方传入输出缓冲区和缓冲区最大长度,禁止返回栈内存
int recvStrBuffer(SOCKET sock, char* outBuffer, uint32_t bufferMaxSize)
{
    // 循环接收4字节长度头,保证完整接收
    uint32_t netLen = 0;
    char* lenPtr = (char*)&netLen;
    int totalRecv = 0;
    int ret = 0;
    while (totalRecv < sizeof(uint32_t))
    {
        ret = recv(sock, lenPtr + totalRecv, sizeof(uint32_t) - totalRecv, 0);
        if (ret == SOCKET_ERROR)
        {
            printf("recv length header failed, err: %d\n", WSAGetLastError());
            return SOCKET_ERROR;
        }
        if (ret == 0) // 对端关闭连接
        {
            printf("peer closed connection\n");
            return -1;
        }
        totalRecv += ret;
    }

    uint32_t dataLen = ntohl(netLen);
    // 校验对端发来的长度不超过本地缓冲区大小,防止溢出
    if (dataLen > bufferMaxSize)
    {
        printf("recv data len %u exceed buffer size %u\n", dataLen, bufferMaxSize);
        return SOCKET_ERROR;
    }
    memset(outBuffer, 0, bufferMaxSize);

    // 循环接收实际业务数据,保证完整接收
    totalRecv = 0;
    while (totalRecv < dataLen)
    {
        ret = recv(sock, outBuffer + totalRecv, dataLen - totalRecv, 0);
        if (ret == SOCKET_ERROR)
        {
            printf("recv payload failed, err: %d\n", WSAGetLastError());
            return SOCKET_ERROR;
        }
        if (ret == 0)
        {
            printf("peer closed connection when recv payload\n");
            return -1;
        }
        totalRecv += ret;
    }
    return (int)dataLen; // 接收成功返回实际数据长度
}
正确调用示例
  • 服务端侧代码:
char x[20];
printf("Give command: ");
scanf_s("%s", x, 20);

// LINE1 替换为自定义函数,传入实际字符串长度+1(包含末尾'\0')
int r = sendStrBuffer(s, x, (uint32_t)strlen(x) + 1);
if (r == SOCKET_ERROR)
{
    printf("send command failed, err: %d\n", WSAGetLastError());
    goto jump;
}
if(strncmp(x, "test", 4) == 0)
{
    const char* reply = "Hello friend!";
    // 传入字符串实际长度+1,不要硬编码20
    sendStrBuffer(s, reply, (uint32_t)strlen(reply) + 1);
}
  • 客户端侧代码:
char buff[20];
// LINE2 替换为自定义函数,传入缓冲区最大长度
int ress = recvStrBuffer(ClientSocket, buff, sizeof(buff));
if (ress == SOCKET_ERROR || ress == -1)
{
    printf("Server disconnected or recv failed, err: %d\n", WSAGetLastError());
    break;
}
if(strncmp(buff, "test", 4) == 0)
{
   char buffer[20];
   int ret = recvStrBuffer(ClientSocket, buffer, sizeof(buffer));
   if (ret > 0)
   {
       printf("%s\n", buffer);
   }
}
额外注意事项
  • 禁止在函数中返回栈上局部变量的地址,栈内存会在函数退出时自动释放,后续访问会触发不可预期的错误。如果需要返回动态分配的内存,可以用malloc在堆上申请,调用方使用完后手动free;工程上更安全的做法是让调用方自己传入缓冲区,避免内存泄漏和悬空指针问题。
  • 所有socket操作不要硬编码全局句柄,通过函数参数显式传入要操作的socket,保证服务端、客户端调用时都能传入正确的有效句柄。
  • 无论收发长度头还是实际业务数据,都必须做循环处理,不能假设一次send/recv就能完成全部数据传输。
  • 接收数据时必须校验对端传来的长度值,不能直接按照对端给的长度写内存,否则会留下缓冲区溢出漏洞。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:21:27