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

使用Winsock开发C语言TCP客户端:如何保持连接?

问题

我是一名初学者,首次尝试使用Winsock搭建TCP客户端。参考示例代码编写了一个最小化客户端(代码如下),该客户端基本可正常工作,能接收服务器消息,发送消息也能得到预期结果。但除非添加持续执行收发(甚至仅接收)操作的循环,否则连接建立后会立即断开。请问如何在按需收发时保持连接?服务器为闭源软件,不清楚其配置情况。

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

int startWinsock(void) {
    WSADATA wsa;
    return WSAStartup(MAKEWORD(2, 2), &wsa);
}

int main(void) {
    long rc;
    SOCKET s;
    SOCKADDR_IN addr;

    printf("Starting Winsock... ");

    rc = startWinsock();

    if (rc != 0) {
        printf("Error: unable to start Winsock, error code: %d\n", rc);
    }
    else {
        printf("done.\n");
    }

    printf("Creating socket... ");

    s = socket(AF_INET, SOCK_STREAM, 0);

    if (s == INVALID_SOCKET) {
        printf("Error: Unable to create socket, error code: %d\n", WSAGetLastError());
    }
    else {
        printf("done.\n");
    }

    memset(&addr, 0, sizeof(SOCKADDR_IN));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(10134);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    rc = connect(s, (SOCKADDR*)&addr, sizeof(SOCKADDR));

    if (rc == SOCKET_ERROR) {
        printf("Connection failed, error code: %d\n", WSAGetLastError());
    }
    else {
        printf("Connected to 127.0.0.1.\n");
    }
    
    // 若在此添加持续收发的循环,连接就能保持
    return 0;
}
解决方案

核心原因

你的程序连接建立后直接走到return 0,进程退出时操作系统会自动关闭所有打开的句柄(包括这个TCP socket),因此连接会断开。而添加循环时进程持续运行,socket句柄保持打开状态,连接就能维持。

实现按需收发并保持连接的方法

1. 阻塞式监听输入与socket(适合简单场景)

连接成功后进入循环,用select()同时监听控制台输入和服务器消息,既可以按需发送,又不会阻塞在单一操作上:

修改后的main函数核心逻辑示例:

// 连接成功后
printf("Connected to 127.0.0.1.\n");

fd_set read_fds;
struct timeval tv;
char send_buf[1024];
char recv_buf[1024];
int ret;

while (1) {
    FD_ZERO(&read_fds);
    FD_SET(s, &read_fds);
    FD_SET(STDIN_FILENO, &read_fds); // 监听控制台输入

    tv.tv_sec = 1; // 1秒超时,可根据需求调整
    tv.tv_usec = 0;

    ret = select(0, &read_fds, NULL, NULL, &tv);
    if (ret == SOCKET_ERROR) {
        printf("Select error: %d\n", WSAGetLastError());
        break;
    } else if (ret == 0) {
        // 超时,可在此添加心跳包逻辑(若服务器需要)
        continue;
    }

    // 处理服务器发来的消息
    if (FD_ISSET(s, &read_fds)) {
        int recv_len = recv(s, recv_buf, sizeof(recv_buf)-1, 0);
        if (recv_len <= 0) {
            printf("Connection closed by server or error: %d\n", WSAGetLastError());
            break;
        }
        recv_buf[recv_len] = '\0';
        printf("Received from server: %s\n", recv_buf);
    }

    // 处理用户输入(按需发送消息)
    if (FD_ISSET(STDIN_FILENO, &read_fds)) {
        fgets(send_buf, sizeof(send_buf), stdin);
        // 去掉输入末尾的换行符
        send_buf[strcspn(send_buf, "\n")] = '\0';
        if (strcmp(send_buf, "exit") == 0) {
            printf("Exiting...\n");
            break;
        }
        int send_len = send(s, send_buf, strlen(send_buf), 0);
        if (send_len == SOCKET_ERROR) {
            printf("Send error: %d\n", WSAGetLastError());
            break;
        }
        printf("Sent message: %s\n", send_buf);
    }
}

// 退出前释放资源
closesocket(s);
WSACleanup();

2. 多线程分离收发逻辑

如果需要更复杂的业务处理,可以用子线程专门监听服务器消息,主线程处理用户输入和发送操作,避免收发互相阻塞:

  • 主线程:连接成功后等待用户输入,按需发送消息,直到用户触发退出指令。
  • 子线程:循环调用recv()监听服务器消息,直到连接断开或收到退出信号。
    注意要处理线程同步,比如用全局变量或事件通知子线程退出。

3. 适配服务器心跳机制

部分闭源服务器会定期检查客户端活跃度,若长时间无数据交互会主动断开连接。这种情况下,即使保持socket打开也会被踢掉,需要在循环的超时逻辑中定期发送约定的心跳包(比如空消息或特定指令),维持连接活跃。

关键注意事项

  • 进程退出前显式调用closesocket(s)和WSACleanup()释放资源,虽然操作系统会自动清理,但显式调用是良好编程习惯。
  • 当recv()返回0或错误时,说明连接已断开,需退出循环并清理资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:05:37