分析这行代码:key.interestOps(SelectionKey.OP_READ);
java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) {
try {
// 打开一个 SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // 设置为非阻塞模式
// 连接到服务器
socketChannel.connect(new InetSocketAddress("localhost", 8080));
// 打开 Selector
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
while (true) {
// 选择已准备好进行 I/O 操作的通道
selector.select();
for (SelectionKey key : selector.selectedKeys()) {
if (key.isConnectable()) {
// 完成连接
SocketChannel client = (SocketChannel) key.channel();
if (client.isConnectionPending()) {
client.finishConnect();
System.out.println("Connected to the server.");
}
}
if (key.isWritable()) {
// 发送数据
SocketChannel client = (SocketChannel) key.channel();
String message = "Hello from the client!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
client.write(buffer);
System.out.println("Message sent to server: " + message);
// 发送完后取消可写事件
key.interestOps(SelectionKey.OP_READ); // 只关注读事件
}
if (key.isReadable()) {
// 读取服务器响应
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = client.read(buffer);
if (bytesRead > 0) {
buffer.flip(); // 切换为读模式
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("Received from server: " + new String(data));
}
}
}
selector.selectedKeys().clear(); // 清理已处理的键
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这属于 Java NIO 中的多路复用(Selector/Channel)机制。我会逐步解释:
1️⃣ 背景
在 Java NIO 中,你可以使用 Selector 来监听多个通道(Channel)的事件,比如:
- OP_READ:通道可以读数据
- OP_WRITE:通道可以写数据
- OP_CONNECT:通道完成连接
- OP_ACCEPT:服务器通道有新连接
这些事件被称为 "兴趣事件(interestOps)"。
SelectionKey 代表通道注册到 Selector 后返回的一个 key,它记录了这个通道的兴趣事件、就绪状态以及附加对象。
2️⃣ interestOps 方法
java
key.interestOps(int ops)
- 用来 设置通道的兴趣事件,告诉 Selector 你关心哪些事件。
- 参数
ops是一个位掩码(bit mask),可以是:
java
SelectionKey.OP_READ // 读
SelectionKey.OP_WRITE // 写
SelectionKey.OP_CONNECT // 连接完成
SelectionKey.OP_ACCEPT // 接受新连接
- 可以组合多个事件,例如:
java
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
表示你同时关心读和写事件。
3️⃣ 你的代码:key.interestOps(SelectionKey.OP_READ);
意思就是:
给这个 SelectionKey 注册 只关心读事件 。
当通道 有数据可读 时,Selector 会通知你。
具体效果:
- 当你调用
selector.select()时,如果该通道有数据可读,它会被加入 就绪集合(selected set)。 - 你就可以安全地调用
channel.read(buffer)来读取数据。
作用:将兴趣集合设置为 仅 OP_READ。
结果:
- 原来的兴趣集合(比如 OP_WRITE)会被替换掉。
- Selector 不再监听通道的原先事件,只有读事件仍然被监听。
保留原有事件的方法
如果我想在保留原有事件的基础上添加一个事件 ,不要直接覆盖,而要用位操作:
java
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
解释:
key.interestOps():获取原来的兴趣集合| SelectionKey.OP_READ:使用按位或,添加读事件- 结果:保留原有事件,同时添加 OP_READ
同理,如果想移除某个事件:
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
~SelectionKey.OP_WRITE:按位与取反,移除写事件。
总结
- 直接调用
key.interestOps(OP_X)->替换原有兴趣事件 - 保留原有事件 ->用
key.interestOps(key.interestOps() | OP_X) - 移除事件 ->用
key.interestOps(key.interestOps() & ~OP_X)
4️⃣ 举个例子
假设你写一个聊天服务器,处理客户端消息:
java
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
- 客户端一旦发送消息,
Selector就会检测到通道可读。 - 服务器就可以安全读取客户端发送的数据。
- 如果你还想写数据,可以之后:
java
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
这样同时关心读和写。
✅ 总结
key.interestOps(SelectionKey.OP_READ);- 设置该通道 只关心读事件
- 当通道有数据可读时,Selector 会通知你
- 是 NIO 非阻塞 I/O 的核心机制之一