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

