Linux/macOS下最通用方式是用fcntl()设置O_NONBLOCK标志:先调用fcntl(sockfd, F_GETFL)获取当前标志,再通过fcntl(sockfd, F_SETFL, flags | O_NONBLOCK)设置非阻塞;需检查返回值,失败时errno可能为EBADF或EACCES。

Linux/macOS 下用 fcntl() 设置非阻塞
Unix-like 系统上最通用、最推荐的方式是调用 fcntl() 修改 socket 文件描述符的标志。阻塞/非阻塞本质是文件描述符属性,不是 socket 协议层的开关。
常见错误是只在 socket() 后立刻设置,却忘了后续 connect() 或 accept() 也可能被阻塞——这些调用本身也要配合非阻塞逻辑处理。
- 先获取当前标志:
int flags = fcntl(sockfd, F_GETFL, 0); - 再添加
O_NONBLOCK:fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - 必须检查返回值:失败时
fcntl()返回 -1,errno可能是EBADF(无效 fd)或EACCES(权限问题)
Windows 下用 ioctlsocket() 设置非阻塞
Winsock 不支持 fcntl(),必须用 ioctlsocket()。传入的参数和语义与 Unix 完全不同,容易混淆。
典型坑点:传入的第三个参数是指向 u_long 的指针,不是值;且 u_long 值为 0 表示阻塞,非 0(如 1)才表示非阻塞——这点和 O_NONBLOCK 的“置位”逻辑相反。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 声明变量:
u_long mode = 1; - 调用:
ioctlsocket(sockfd, FIONBIO, &mode); - 检查返回值:
SOCKET_ERROR表示失败,用WSAGetLastError()查错 - 注意:该调用会立即生效,但不会影响已发起的阻塞操作(比如正在
connect()的 socket)
非阻塞 socket 的 connect() 怎么判断成功
调用 connect() 后不能等它返回 0——非阻塞下它几乎立刻返回 -1,并把 errno 设为 EINPROGRESS(Linux/macOS)或 WSAEWOULDBLOCK(Windows)。
真正判断连接是否建立,得靠 select()、poll() 或 epoll_wait() 监听 socket 是否可写(write-ready),再用 getsockopt(sockfd, SOL_SOCKET, SO_ERROR, ...) 检查错误码是否为 0。
-
SO_ERROR获取的是连接过程的最终结果,不是 socket 当前状态 - 如果
getsockopt()返回 0,说明连接成功;否则返回对应错误(如ETIMEDOUT、ECONNREFUSED) - 别跳过这一步:仅靠
select()返回可写,不等于连接成功,可能是出错后就绪
跨平台封装时要注意的兼容性细节
想写一份同时跑在 Linux 和 Windows 上的非阻塞 socket 代码,光做条件编译不够,关键行为差异必须对齐。
比如 send() 在非阻塞模式下返回 -1 时,Linux 看 errno == EAGAIN || errno == EWOULDBLOCK,而 Windows 只看 WSAGetLastError() == WSAEWOULDBLOCK——这两个宏在 Windows 上是同一个值,但 Linux 没有 WSAEWOULDBLOCK。
- 建议统一用宏抽象:
#ifdef _WIN32分支处理 Winsock 错误,其他走 POSIX 错误码 -
accept()在非阻塞下失败时,Linux 返回 -1 +EAGAIN/EWOULDBLOCK,Windows 是WSAEWOULDBLOCK,语义一致,但错误码值不同 - 不要假设
recv()返回 0 就一定是对方关闭连接——非阻塞下也可能是暂时没数据,得结合errno判断
非阻塞不是加个 flag 就完事,它把同步等待逻辑拆成了状态轮询和事件驱动,所有 I/O 调用都要重审错误分支和返回含义。

















