如何基于提供的Winsock C代码实现控制台与服务端数据收发
Winsock2 TCP客户端收发功能实现指引
现有代码存在的问题
你手上的基础框架已经完成了Winsock初始化、服务器地址解析、TCP连接建立的核心流程,但存在以下致命问题导致无法正常完成收发功能:
- 发送缓冲区
sendbuf声明后未做任何初始化就直接调用strlen计算长度,属于C语言未定义行为,大概率会发送随机内存垃圾数据,甚至直接触发程序崩溃 - 没有控制台输入读取逻辑,根本无法获取你在终端输入的待发送内容
- 仅单次调用
recv接收数据,既无法处理服务器返回数据长度超过4096字节的场景,也无法处理TCP流分段传输的情况 getaddrinfo调用成功返回的地址结构没有在连接建立后释放,存在内存泄漏- 日志提示语义错误:socket创建成功的分支打印内容为"Any error occured...",和实际状态完全相反
- 收发流程是一次性的,发送一次、接收一次就直接退出程序,无法支持持续交互
操作步骤
编译准备
- 该代码只能在Windows平台编译运行,可直接使用Visual Studio打开编译,代码中已经通过
#pragma comment自动链接了必须的Ws2_32.lib,无需额外配置 - 如果使用MinGW/GCC编译,需要在编译命令末尾追加参数
-lws2_32链接Winsock库,示例编译命令:gcc tcp_client.c -o tcp_client.exe -lws2_32
代码修正逻辑
- 连接成功后第一时间调用
freeaddrinfo(result)释放地址解析结果,避免资源泄漏 - 修正错误的日志提示内容,保证状态打印和实际运行结果一致
- 增加主循环:持续读取控制台用户输入,用户输入内容按回车后触发发送,输入
exit时主动断开连接退出程序 - 发送前清空发送缓冲区,将用户输入内容拷贝到缓冲区后,去掉fgets自带的末尾换行符,再计算实际长度调用
send - 接收数据时增加循环逻辑,持续读取服务器返回内容直到连接断开;每次读取到数据后手动在末尾追加字符串结束符
\0再打印,避免出现乱码 - 程序退出前统一执行关闭socket、调用
WSACleanup清理Winsock环境的操作
修正后可直接运行的完整代码
#include <stdio.h> #include <winsock2.h> #include <ws2tcpip.h> #include <conio.h> #include <string.h> #pragma comment (lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 4096 int main() { WSADATA wsaData; int iResult; // 初始化Winsock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed: %d\n", iResult); return 1; } struct addrinfo *result = NULL, *ptr = NULL; struct addrinfo hints; ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // 解析服务器地址,可根据需求修改目标IP和端口 iResult = getaddrinfo("147.175.115.34", "777", &hints, &result); if (iResult != 0) { printf("getaddrinfo failed: %d\n", iResult); WSACleanup(); return 1; } printf("Server address resolved successfully\n"); SOCKET ConnectSocket = INVALID_SOCKET; ptr = result; ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %d\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 1; } printf("Socket created successfully\n"); // 建立TCP连接 iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); freeaddrinfo(result); // 连接建立后立刻释放地址解析结果 if (iResult == SOCKET_ERROR) { printf("Failed to connect to server, error code: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Connected to server successfully, type content and press enter to send, type 'exit' to quit\n"); char sendbuf[DEFAULT_BUFLEN]; char recvbuf[DEFAULT_BUFLEN]; // 主收发循环 while (1) { printf("> "); ZeroMemory(sendbuf, DEFAULT_BUFLEN); // 读取控制台输入 if (fgets(sendbuf, DEFAULT_BUFLEN, stdin) == NULL) { break; } // 去掉输入末尾的换行符 sendbuf[strcspn(sendbuf, "\r\n")] = '\0'; // 判断是否退出 if (strcmp(sendbuf, "exit") == 0) { printf("Closing connection...\n"); break; } // 空输入不发送 if (strlen(sendbuf) == 0) { continue; } // 发送数据 iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("Send failed, error code: %d\n", WSAGetLastError()); break; } printf("Bytes sent: %d\n", iResult); // 接收服务器返回数据 printf("Received from server:\n"); while (1) { ZeroMemory(recvbuf, DEFAULT_BUFLEN); iResult = recv(ConnectSocket, recvbuf, DEFAULT_BUFLEN - 1, 0); if (iResult > 0) { printf("%s", recvbuf); } else if (iResult == 0) { printf("\nServer closed the connection\n"); goto CLEANUP; } else { printf("\nRecv failed, error code: %d\n", WSAGetLastError()); goto CLEANUP; } // 如果当前读到的数据长度小于缓冲区长度,说明本轮数据接收完成 if (iResult < DEFAULT_BUFLEN - 1) { printf("\n"); break; } } } CLEANUP: // 优雅关闭连接 shutdown(ConnectSocket, SD_SEND); closesocket(ConnectSocket); WSACleanup(); return 0; }
注意事项
- TCP是面向流的协议,没有固定的消息边界,如果你的业务场景需要按消息包处理数据,需要自行定义协议格式(比如固定长度头、特殊分隔符等)做拆包处理,上面的基础版本适合简单的行交互场景
- 如果连接服务器失败,优先检查目标IP和端口是否正确、本地网络是否连通、服务器端是否正常监听、系统防火墙是否拦截了出站连接
内容的提问来源于stack exchange,提问作者Denis
相关产品推荐
相关产品推荐

