socketchannel非阻塞客户端必须配合selector使用,connect()立即返回false属正常,需监听op_connect事件并调finishconnect();读写须按op_read/op_write分阶段处理,bytebuffer须正确flip()/clear()。

SocketChannel 实现非阻塞客户端,核心不是“能不能连上”,而是“连不上时别卡住、读不到数据时不干等、写不完时不以为失败”。直接上结论:必须配合 Selector 使用,且 connect() 和 read()/write() 都要放在事件驱动循环里处理,不能裸调用。
configureBlocking(false) 之后 connect() 立即返回 false 是正常现象
-
connect()在非阻塞模式下不等待 TCP 三次握手完成,它只发起连接请求并立刻返回false(表示未就绪),此时连接处于“in-progress”状态。 - 必须后续轮询
finishConnect(),直到它返回true才算真正连通。 - 常见错误:调用
connect()后没检查返回值,直接去read(),结果抛NotYetConnectedException。 - 更稳妥的做法是把
SocketChannel注册到Selector,监听SelectionKey.OP_CONNECT事件,等事件就绪再调finishConnect()。
Selector.select() 循环中必须区分 OP_CONNECT / OP_READ / OP_WRITE 三种状态
- 一个
SocketChannel在生命周期中会经历连接 → 可写 → 可读等多个阶段,每个阶段对应不同事件。 - 错误写法:注册时只写
OP_READ,但连接刚建立时还没发数据,read()会返回 0,误判为断连。 - 正确流程:
- 连接阶段:注册
OP_CONNECT,收到事件后调channel.finishConnect(),成功后取消该兴趣集,改注册OP_WRITE或OP_READ- 写入阶段:注册
OP_WRITE,但注意——只要 socket 发送缓冲区有空位,OP_WRITE就几乎一直就绪,容易 busy-loop;实际应只在write()返回值小于待写字节数时才关注它- 读取阶段:注册
OP_READ,read()返回 -1 表示对端关闭,0 表示暂时无数据(正常,非错误),>0 才是真实收到字节
ByteBuffer 的 flip()/clear() 调用时机极易出错
-
write()前必须确保ByteBuffer处于“读模式”(即position指向首字节,limit指向末字节),否则什么也写不出。 -
read()后必须调flip()才能从 buffer 中正确读出刚收到的数据;处理完后要用clear()(不是compact())重置 buffer,为下次读做准备。 - 容易踩的坑:
-
read()后忘了flip(),array()拿到的是全零或脏数据-
clear()前没消费完 buffer 内容,导致数据被覆盖- 多次
write()用同一个 buffer 却没重置 position/limit,第二次写实际从中间开始
非阻塞客户端最难缠的点不在连接或发送,而在连接建立后、首条响应未到达前的状态管理:你不知道该等读、还是该主动写、还是该重试。这时候没有“默认行为”,每一步都要根据 SelectionKey 的就绪集和 ByteBuffer 的剩余容量来判断。漏掉任一环节,程序就卡死或反复空转。










