Windows 平台下的 TCP 客户端
本次实操验证核心目标:Windows 平台 TCP 客户端,能够正常连通 Linux TCP 服务端。
我们已经完整讲解过 TCP 套接字基础写法,配套实验也完成过 Linux 机器与 Linux 机器之间同平台 TCP 通信。
socket 套接字 - TCPhttps://rosetea.blog.csdn.net/article/details/147653994此前我们实践过 Windows UDP 客户端连通 Linux 服务端,同理,TCP 通信也必须实现 Windows 客户端跨平台连通 Linux 服务端;
Windows 平台下的 UDP 客户端https://rosetea.blog.csdn.net/article/details/163220666如果无法完成跨平台互通,这套套接字通信方案就不具备实用价值,所以本次专门带大家完整落地这套跨平台 TCP 通信流程。
一、Windows TCP 客户端代码通用固定框架说明
Windows 下 Winsock 套接字基础流程和之前 UDP 完全一致,核心固定步骤没有任何区别:
引入头文件<winsock2.h>
通过
#pragma comment(lib, "ws2_32.lib")链接 Winsock 库调用WSAStartup()初始化 Winsock 版本环境,接收
WSADATA版本信息程序结束时调用WSACleanup()释放网络资源
头文件内置核心类型与函数
| 名称 | 作用说明 |
|---|---|
| WSADATA | 存储 Winsock 库初始化返回的系统信息 |
| SOCKET | 套接字句柄,唯一标识一个网络套接字 |
| sockaddr_in | IPv4 专用地址结构体,存放 IP、端口等地址信息 |
| socket() | 创建套接字对象 |
| bind() | 将套接字绑定本地 IP 与端口 |
| listen() | 把套接字切换为监听模式,等待客户端连接 |
| accept() | 接收客户端连接,返回专用通信套接字 |
WSAStartup
函数定位:Windows 套接字库初始化函数,所有 Socket 相关 API 调用前必须优先执行,是 Windows 网络程序入口初始化步骤。
参数说明
wVersionRequested:指定程序需要使用的 Winsock 版本,固定搭配宏MAKEWORD(主版本, 次版本),示例代码使用MAKEWORD(2,2)代表 Winsock 2.2 版本;lpWSAData:WSADATA 结构体指针,用于接收系统返回的初始化详情。
返回值规则
返回
0:初始化成功,可正常使用 Socket 接口;非 0 数字:初始化失败,数字为对应错误码。
核心功能:告知操作系统当前程序要使用指定版本的 Winsock 网络库,完成库资源初始化;初始化完成后,程序才能调用地址解析、连接、收发数据等全套网络功能,实现应用层与系统 TCP/IP 协议栈交互。
配套释放接口:程序不再使用 Socket 功能时,必须调用WSACleanup():解除程序与 Winsock 库绑定,释放系统占用的网络资源,避免内存 / 句柄泄漏。
本次重点对比:Windows 平台 TCP Client 代码 和 Linux 平台原生 TCP Client 代码的区别。
结论:二者差异极小,99% 代码逻辑、流程完全一致。
1. 套接字创建流程
前置初始化完成后,直接固定写法创建套接字:
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);地址族参数:
AF_INET套接字类型:
SOCK_STREAM(面向连接 TCP 流)协议参数:可填
0自动适配,也可显式填写IPPROTO_TCP,两种写法均可
Linux 平台套接字句柄为普通int类型;Windows 平台专用类型SOCKET,创建失败时会返回内置宏INVALID_SOCKET,程序中需要做失败判断处理。
2. 填充服务端地址与连接逻辑
客户端需要手动填充服务端端口、IP 地址信息,使用结构体sockaddr_in; 调用connect()发起连接,传入强制转换后的套接字地址指针:
connect(clientSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr));Windows 平台相关类型、宏名称大多为大写(如SOCKADDR、SOCKET),底层对应 Linux 里小写的sockaddr、文件描述符,仅语法标识区分,逻辑完全相同。
连接返回SOCKET_ERROR代表连接失败,需要关闭套接字、清理 Winsock 资源后退出程序。
3. 循环收发数据逻辑
建立连接后进入业务主循环,完整流程:
获取控制台用户输入,空输入直接跳过本次循环
调用send()向服务端发送数据:参数依次为套接字句柄、消息字符串、消息字节长度、发送标识0
定义buffer缓冲区,调用recv()读取服务端回传数据:参数为套接字、缓冲区、缓冲区长度减 1、读取标识0
判断recv()返回值:
返回值 > 0:读取到有效数据,手动给缓冲区末尾追加
\0,转为标准字符串打印服务端回显内容返回值≤0:读取失败、连接断开,打印错误提示
整体收发逻辑,和之前 UDP 客户端、Linux 原生 TCP 客户端完全统一,没有额外复杂逻辑,此处不重复展开。
完整客户端代码
#include <winsock2.h> #include <ws2tcpip.h> // 新增,用于inet_pton #include <iostream> #include <string> #pragma warning(disable : 4996) #pragma comment(lib, "ws2_32.lib") std::string serverip = "113.132.212.215"; // 替换为你的云服务器ip uint16_t serverport = 8888; // 填写你的云服务开放的端口号 int main() { WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { std::cerr << "WSAStartup failed: " << result << std::endl; return 1; } SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { std::cerr << "socket failed" << std::endl; WSACleanup(); return 1; } sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(serverport); // 替换废弃的inet_addr,使用安全的inet_pton if (inet_pton(AF_INET, serverip.c_str(), &serverAddr.sin_addr) <= 0) { std::cerr << "IP地址格式错误" << std::endl; closesocket(clientSocket); WSACleanup(); return 1; } result = connect(clientSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr)); if (result == SOCKET_ERROR) { std::cerr << "connect failed" << std::endl; closesocket(clientSocket); WSACleanup(); return 1; } bool runFlag = true; while (runFlag) { std::string message; std::cout << "Please Enter@ "; std::getline(std::cin, message); if(message.empty()) continue; // 增加send发送结果校验 int sendRet = send(clientSocket, message.c_str(), message.size(), 0); if (sendRet == SOCKET_ERROR) { std::cerr << "send 发送失败,连接断开" << std::endl; break; } char buffer[1024] = {0}; int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); if (bytesReceived > 0) { buffer[bytesReceived] = '\0'; std::cout << "Received from server: " << buffer << std::endl; } else { // recv返回0=服务端关闭连接;返回负数=网络异常 std::cerr << "recv failed,服务端已断开连接" << std::endl; runFlag = false; } } // 循环退出后正常释放资源 closesocket(clientSocket); WSACleanup(); std::cout << "客户端正常退出" << std::endl; return 0; }二、实操验证完整流程
阶段 1:提前启动 Linux TCP Echo 服务端
我们提前准备好配套 TCP 服务端源码,端口统一使用8888。 服务端本质是标准 Echo 回显服务器,核心逻辑:
创建套接字 → bind 绑定端口 → listen 开启监听
通过accept()阻塞等待客户端新连接,获取专属通信套接字、客户端 IP 与端口信息
循环读取客户端发送的数据,读完后原样写回客户端(echo 回显)
服务端代码基础流程之前文章已经完整,本次不再细读源码,直接启动 Linux 服务端程序,保持后台监听状态。
阶段 2:VS20XX 创建 Windows TCP 客户端工程实操
打开 Visual Studio,新建空 C++ 项目,自定义项目名称(示例:test)
右键项目添加新建源文件,命名为tcp_client.cpp/tcp_client.cc均可
复制课件内完整 Windows TCP Client 代码粘贴至源文件,注意区分 TCP、UDP 代码,避免粘贴错误
修改代码内两处关键配置:
serverip:填写你自己的云服务器公网 IP(演示使用讲师云服务器 IP,学员需替换为自身机器地址)serverport:固定 8888,与 Linux 服务端监听端口保持一致
阶段 3:编译运行、双向通信测试
代码配置完成后,按下F5启动客户端程序:
程序启动自动执行WSAStartup、创建套接字、调用connect连接 Linux 服务端
Linux 服务端日志会立刻打印新接入客户端的公网 IP、随机临时端口,代表跨平台 TCP 连接建立成功
在 Windows 客户端控制台输入任意文本消息,例如hello、你好、在吗等:
Windows 客户端通过 TCP 将数据发送至 Linux 服务端
Linux 服务端读取数据后原样回传
Windows 客户端
recv()读取回显内容并打印,双向通信验证完成
IP 地址、端口开放、防火墙放行等注意事项,UDP 相关中已经重点强调,本次不再重复说明。
三、核心断线检测逻辑:客户端退出,服务端自动感知
TCP 协议具备连接状态感知能力,这是本篇重点补充的关键特性: Windows 客户端主动关闭程序 / 断开套接字时,Linux 服务端执行recv()读取操作会直接返回0,服务端可以精准识别客户端下线。
服务端断线处理逻辑说明
服务端处理新连接的循环中,对recv()返回值做分支判断:
返回值 > 0:正常读取到客户端消息,执行 echo 回显
返回值≤0:判定客户端主动断开连接,打印日志格式:
[客户端IP:端口] client quit
演示效果:关闭 Windows 客户端程序后,Linux 服务端控制台立刻输出对应客户端退出日志,证明跨平台 TCP 连接断开事件可被两端正常识别。
四、实操总结
本次完整实操验证了:基于 Winsock 实现的 Windows TCP 客户端,能够和 POSIX 标准 Linux TCP 服务端完成稳定跨平台 TCP 双向通信,连接建立、数据收发、断线检测全流程均可正常运行。
Windows 与 Linux 套接字仅初始化、资源释放、类型标识存在少量平台专属差异,通信核心逻辑完全通用,大家后续开发跨平台 TCP 程序可直接复用这套代码框架。