最可靠方式是POSIX系统用fcntl(fd, F_SETFL, O_NONBLOCK),Windows用ioctlsocket;需对每个新fd单独设置,connect前完成,并立即保存errno/WSAGetLastError()。

Linux/macOS 下用 fcntl 设置 socket 非阻塞最可靠
直接调用 fcntl(fd, F_SETFL, O_NONBLOCK) 是 POSIX 系统上最通用、最稳妥的方式。它不依赖 socket 类型(TCP/UDP)、创建方式(socket() 或 accept() 返回),也不和 connect() 的超时逻辑冲突。
常见错误是只设一次就以为万事大吉——其实 accept() 返回的新 socket 默认仍是阻塞的,必须对每个新 fd 单独调用一次 fcntl。
- 务必检查返回值:
fcntl失败会返回 -1,常见原因是传入了非法 fd 或权限不足 - 获取当前 flag 时别漏掉
O_NONBLOCK:先fcntl(fd, F_GETFL),再用|= O_NONBLOCK,而不是直接覆盖 - 不要在
connect()之后才设非阻塞——连接发起前就要设好,否则可能卡在三次握手阶段
Windows 上必须用 ioctlsocket,fcntl 不可用
Windows 没有 fcntl,得用 Winsock 特有的 ioctlsocket。参数固定为 FIONBIO,且第二个参数是 u_long* 类型的指针,传入 &nonblocking(值为 0 或 1)。
容易踩的坑是传错类型或忽略字节序无关性——u_long 在 Windows 上就是 32 位整数,不需要 htonl;但若误传 int* 可能在某些编译器下触发警告或未定义行为。
立即学习“C++免费学习笔记(深入)”;
- 调用前确认 socket 已初始化(
WSAStartup已执行) -
ioctlsocket返回 0 表示成功,-1 表示失败,错误码用WSAGetLastError() - UDP socket 同样需要设置,否则
recvfrom仍会阻塞
connect() 在非阻塞 socket 上的行为要手动轮询
设成非阻塞后,connect() 立即返回 -1,且 errno(Linux/macOS)或 WSAGetLastError()(Windows)为 EINPROGRESS(或 Windows 的 WSAEWOULDBLOCK)。此时连接仍在后台进行,不能直接读写。
真正完成连接的判断方式不是看 connect() 是否返回 0,而是用 select()、poll() 或 epoll_wait() 监听该 socket 的可写事件(write-ready)——可写意味着 TCP 三次握手完成或失败。
- 不要在
connect()返回 -1 后立刻调用send(),大概率失败并置errno = EAGAIN - 监听可写事件时,需额外调用
getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len)获取真实连接结果(0 表示成功,非零表示失败如ECONNREFUSED) - Windows 下对应的是
SO_ERROR+WSAGetLastError(),语义一致
跨平台封装要注意 errno / WSAGetLastError() 的读取时机
非阻塞 I/O 的错误判断高度依赖系统 API 的返回值和错误码。一旦在中间插入了其他系统调用(比如日志打印、内存分配),errno 或 WSAGetLastError() 就可能被覆盖。
例如:在 connect() 返回 -1 后,如果先调用 printf() 再检查 errno,Linux 下很可能得到 0(实际已被 printf 修改);Windows 下同理。
- 每次系统调用后立即保存错误码:
int saved_errno = errno;或int saved_err = WSAGetLastError(); - 避免在关键路径中混用标准库 I/O(
printf,fopen)或 C++ 流(std::cout) - 封装
send()/recv()时,统一约定:返回 -1 且errno == EAGAIN || errno == EWOULDBLOCK表示“暂时不可用”,不是错误
select 轮询或少一次 errno 保存,都会导致连接卡死或数据丢失。


















