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

如何基于提供的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

代码修正逻辑

  1. 连接成功后第一时间调用freeaddrinfo(result)释放地址解析结果,避免资源泄漏
  2. 修正错误的日志提示内容,保证状态打印和实际运行结果一致
  3. 增加主循环:持续读取控制台用户输入,用户输入内容按回车后触发发送,输入exit时主动断开连接退出程序
  4. 发送前清空发送缓冲区,将用户输入内容拷贝到缓冲区后,去掉fgets自带的末尾换行符,再计算实际长度调用send
  5. 接收数据时增加循环逻辑,持续读取服务器返回内容直到连接断开;每次读取到数据后手动在末尾追加字符串结束符\0再打印,避免出现乱码
  6. 程序退出前统一执行关闭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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.04 16:15:40