创建TCP连接需先用AF_UNSPEC和getaddrinfo()自动适配地址族,避免硬写AF_INET导致跨平台失败;socket()第三参数应显式传IPPROTO_TCP;send()/recv()必须检查返回值,其值可能小于请求长度、为0(对端关闭)或-1(错误)。

用 socket() 创建 TCP 连接前,必须设对地址族和类型
很多人一上来就调 socket(AF_INET, SOCK_STREAM, 0),结果在 macOS 或某些嵌入式环境跑不通——不是函数报错,而是后续 connect() 直接超时。根本原因是没确认系统是否真支持 IPv4(AF_INET)或该协议栈是否启用。更稳妥的做法是优先用 AF_UNSPEC 配合 getaddrinfo(),让系统自己选可用的地址族。
-
AF_INET只强制走 IPv4,AF_INET6强制 IPv6,AF_UNSPEC允许双栈自动降级 - 硬写
SOCK_STREAM没问题,但别漏掉第三个参数:TCP 要传IPPROTO_TCP(虽然传 0 通常也行,但 Linux man page 明确说“不推荐”) - Windows 上如果没调
WSAStartup()就用socket(),会直接返回INVALID_SOCKET,错误码是WSANOTINITIALISED
send() 和 recv() 不保证一次传完所有数据
新手常把 send(buf, len, 0) 当成“发完了”,然后立刻 close();或者以为 recv() 一定会填满缓冲区。实际上这两个函数只承诺“尽力而为”,返回值才是真实传输字节数——可能小于请求长度,甚至为 0(对端关闭)或 -1(出错)。
- 必须检查返回值:
if (n == 0)表示对方已关闭连接;n 且 <code>errno == EAGAIN或EWOULDBLOCK是非阻塞模式下的正常现象 - 发送大数据要循环调用:
while (sent -
recv()返回-1且errno == ECONNRESET,说明对方异常断连,不是网络抖动
bind() 失败常见于端口被占或权限不足
写服务端时 bind() 返回 -1,errno 是 EADDRINUSE 最常见,但别急着杀进程——Linux 下 TIME_WAIT 状态会占着端口 2MSL(通常 60 秒),此时即使进程退出,端口也不能立即重用。
- 加
SO_REUSEADDR选项能绕过 TIME_WAIT 占用:setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) - 绑定
INADDR_ANY(即0.0.0.0)才能监听所有网卡,绑定127.0.0.1只响应本地回环 - 绑定 1024 以下端口(如 80、443)需要 root 权限,普通用户运行会报
EACCES
客户端 connect() 超时不能靠 sleep 控制
默认 connect() 是阻塞的,卡住几秒甚至几十秒才返回失败,这在交互程序里不可接受。想设超时,不能在 connect() 前 sleep(),得用非阻塞 socket + select() 或 poll() 等待可写事件。
立即学习“C++免费学习笔记(深入)”;
- 先
fcntl(sock, F_SETFL, O_NONBLOCK)把 socket 设为非阻塞 - 调
connect(),若返回 -1 且errno == EINPROGRESS,说明正在连接中 - 用
select()等待 socket 可写(注意:可写意味着连接完成,无论成功或失败) - 再调
getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len)拿真实错误码
真正的难点不在 API 调用顺序,而在于每一步的 errno 判断逻辑不同——比如 connect() 在非阻塞下成功时返回 0,失败时也返回 -1,但错误藏在 socket 选项里;而 send() 成功时返回正数,失败才返回 -1。漏查一个 errno,程序就静默挂死。









