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
相关产品推荐
相关产品推荐

