非阻塞 connect 如何判断连接成功?超时怎么实现?
简化版
非阻塞 socket 调用 connect 通常会立即返回 EINPROGRESS,表示连接正在建立。之后用 select/poll/epoll 监听可写事件,事件到来后通过 getsockopt(SO_ERROR) 判断是否真正连接成功。连接超时由应用自己计时实现。
详细版
阻塞 connect 会等 TCP 握手完成或失败;非阻塞 connect 不等待,立即返回进行中。可写事件出现不等于一定成功,可能是连接成功,也可能是连接失败。标准做法是:
- 设置 socket 非阻塞;
- 调用
connect; - 若返回
EINPROGRESS,注册可写事件; - 可写后调用
getsockopt(fd, SOL_SOCKET, SO_ERROR); SO_ERROR=0成功,否则失败;- 应用层维护 deadline 实现超时。
完整版教学
一、为什么需要非阻塞 connect
客户端连接远端服务时,网络延迟、丢包、防火墙丢弃都可能让阻塞 connect 卡很久。如果一个线程要同时管理大量连接,不能让它阻塞在某一个连接上。
connect A: 20ms
connect B: timeout 3s
connect C: 30ms
非阻塞 connect 允许应用发起连接后继续处理其他连接,用事件机制等待结果。
二、EINPROGRESS 表示什么
非阻塞 socket 调用 connect 时,如果连接不能立即完成,通常返回 -1,并设置 errno 为 EINPROGRESS。这不是失败,而是“正在进行”。
connect() -> -1, errno=EINPROGRESS
register writable event
如果目标在本机或连接非常快,也可能直接返回 0。代码要同时处理立即成功和进行中两种情况。
三、为什么可写不等于成功
连接完成或连接失败,都可能让 socket 变为可写。可写事件只表示“connect 这个异步动作有结果了”,不是结果一定成功。
writable event -> check SO_ERROR
SO_ERROR = 0 -> connected
SO_ERROR = ECONNREFUSED -> failed
所以必须调用 getsockopt(SO_ERROR)。这是面试里判断是否真懂非阻塞 connect 的关键点。
四、超时由应用自己维护
非阻塞 connect 不会帮你按业务期望超时。应用要记录连接开始时间和 deadline,把 fd 放到定时器结构里,超过时间仍未成功就关闭连接。
start = now
timeout = 3000ms
if now - start > timeout: close(fd)
事件循环一般会同时管理 IO 事件和定时器。常见结构有最小堆、时间轮或框架内置 timer。
五、DNS 解析也可能阻塞
很多人把 socket 设成非阻塞,却在连接前调用阻塞 DNS 解析。域名解析慢时,线程仍会卡住。
getaddrinfo("api.example.com") may block
connect nonblocking comes after DNS
高并发客户端要使用异步 DNS、线程池 DNS、缓存解析结果,或让基础网络库统一处理。
六、错误码如何处理
常见失败包括 ECONNREFUSED、ETIMEDOUT、ENETUNREACH、EHOSTUNREACH。不同错误对应不同排查方向。
| 错误 | 含义 | 常见原因 |
|---|---|---|
| ECONNREFUSED | 对端拒绝 | 端口没监听 |
| ETIMEDOUT | 连接超时 | 防火墙丢包/网络不通 |
| ENETUNREACH | 网络不可达 | 路由问题 |
| EHOSTUNREACH | 主机不可达 | ARP/路由/防火墙 |
日志里记录具体错误码,比只写“connect failed”更有排查价值。
七、连接成功后的状态切换
连接成功后,要把这个 fd 从“连接中”状态切到“已连接”状态。通常取消连接超时定时器,按业务注册读事件,并根据是否有待发送数据决定是否监听写事件。
CONNECTING -> CONNECTED
remove connect timer
enable read
enable write if output buffer not empty
如果忘记取消超时定时器,连接成功后仍可能被旧 timer 关闭,形成难查的偶发 bug。
记忆钩子:非阻塞 connect 的可写事件只是“结果通知”,真正成功与否要问
SO_ERROR。
八、常见误区与追问
- 误区:connect 返回 EINPROGRESS 是失败。 它表示连接正在建立,后续等可写事件。
- 误区:socket 可写就说明连接成功。 失败也会触发可写,必须检查
SO_ERROR。 - 误区:非阻塞 connect 自动有超时。 业务超时要应用层定时器实现。
- 追问:如何处理立即 connect 成功? 直接进入 connected 状态,不必等事件。
- 追问:DNS 解析会不会阻塞? 普通
getaddrinfo可能阻塞,需要异步化或缓存。 - 追问:连接失败后能复用 fd 吗? 通常关闭旧 fd,重新创建连接。
- 追问:为什么要记录错误码? 错误码能区分拒绝、超时、路由不可达等问题。
九、加强记忆
非阻塞 connect 的套路是“发起、等待、确认、计时”。EINPROGRESS 不是坏消息,而是连接正在路上;可写事件不是成功证明,只是结果到了;getsockopt(SO_ERROR) 才能判定成败。再加上应用层超时和异步 DNS,才是可用于高并发客户端的完整方案。