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

