最小可行跨平台tcp客户端需先调用wsastartup(windows)或忽略初始化(linux),再用getaddrinfo解析地址,socket创建套接字,connect连接,循环send/recv处理部分收发,最后close/closesocket并wsacleanup(windows)。

怎么用 C++ 写一个能跑通的 TCP 客户端
Windows 和 Linux 下都能编译运行的最小可行客户端,核心就是 socket、connect、send、recv 四个调用。别急着封装类,先确保底层调用链不崩。
常见错误现象:connect: Connection refused(服务端没起来或端口不对)、recv returned 0(对方已关闭连接)、WSAStartup failed(Windows 下忘了初始化 Winsock)。
- Windows 必须在
main()开头调用WSAStartup(MAKEWORD(2,2), &wsaData),结束前调用WSACleanup() - Linux 不需要初始化,但记得链接
-lsocket -lnsl(部分旧系统)或直接-lstdc++即可 -
connect()前要先用getaddrinfo()解析地址,别手写struct sockaddr_in—— IPv6 兼容性和端序问题会悄悄咬你 -
send()可能只发一部分数据,需循环调用;recv()同理,返回值必须检查,不能假设一次收完
怎么写一个不卡死的 TCP 服务端(单线程阻塞版)
初学者最容易写的“假服务端”:accept 后直接 recv,结果一连上来就卡住,第二个连接永远进不来。真正的起点是“一次只处理一个连接”,但得保证它真能收发完再等下一个。
关键不是多线程,而是把连接生命周期管清楚:
立即学习“C++免费学习笔记(深入)”;
- 监听 socket 设为
SOMAXCONN队列长度,避免连接被内核丢弃 -
accept()返回新 socket 后,立刻用setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt))开启保活(防客户端断电不通知) - 每次
recv()前检查返回值:-1 表示出错(查errno或WSAGetLastError()),0 表示对端关闭,此时应close()并跳出循环 - 别用
std::string直接接recv()结果——它不处理二进制零字节,用std::vector<char></char>或裸char buf[1024]更可控
为什么 send() 发了 100 字节,recv() 却只收到 32 字节
TCP 是流式协议,不是消息队列。send() 成功只代表数据进了系统发送缓冲区,不代表发到了对端;recv() 也只承诺“从接收缓冲区拿最多 n 字节”,不保证按发送边界切分。
这不是 bug,是设计如此。想实现“一条消息一收一发”,你必须自己加协议:
- 定长包头 + 变长内容(比如前 4 字节存 body 长度,后续按长度读)
- 特殊分隔符(如
\r\n),但注意二进制数据里可能含该序列,需转义 - 绝对不要依赖
send()和recv()的调用次数一致 - 调试时用
tcpdump或 Wireshark 看真实报文段,比猜更可靠
跨平台编译时 errno 和 WSAGetLastError() 怎么统一处理
Linux 用 errno,Windows 用 WSAGetLastError(),二者数值不兼容。硬写宏判断太丑,建议封装一个轻量错误码转换函数。
例如定义枚举 NetErr,映射常见错误:
enum class NetErr {
Success = 0,
ConnRefused,
Timeout,
BrokenPipe,
// ...
};
再写个 last_net_error() 函数,在 Windows 返回 WSAGetLastError() 映射值,Linux 返回 errno 映射值。这样上层逻辑不用反复 #ifdef _WIN32。
容易被忽略的一点:close() / closesocket() 失败时,错误码同样来自不同来源,且某些平台(如 Windows)关掉 socket 后再调用 WSAGetLastError() 会返回无关值——务必在出错调用后立即捕获错误码。











