NIO学习笔记(5) – Selector
Selector 允许一个单一的线程来操作多个 Channel. 如果我们的应用程序中使用了多个 Channel, 那么使用 Selector 很方便的实现这样的目的, 但是因为在一个线程中使用了多个 Channel, 因此也会造成了每个 Channel 传输效率的降低.为了使用 Selector, 我们首先需要将 Channel 注册到 Selector 中, 随后调用 Selector 的 select()方法, 这个方法会阻塞, 直到注册在 Selector 中的 Channel 发送可读写事件. 当这个方法返回后, 当前的这个线程就可以处理 Channel 的事件了.
图解:
实例:
public class SelectorTest {
public static void main(String[] args) throws IOException {
// 开启端口列表
int[] ports = new int[5];
ports[0] = 5000;
ports[1] = 5001;
ports[2] = 5002;
ports[3] = 5003;
ports[4] = 5004;
// 创建Selector
Selector selector = Selector.open();
// 启动服务器
for (int i = 0; i < ports.length; i++) {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置是否为阻塞
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
// 创建一个套接字地址,其中IP地址为通配符地址,端口号为指定值。
InetSocketAddress address = new InetSocketAddress(ports[i]);
// 将 ServerSocket绑定到特定地
serverSocket.bind(address);
// 使用给定的选择器注册此频道,返回一个选择键。
// SelectionKey.OP_ACCEPT 操作集位确认事件。(服务器开启必须先关联这个)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("监听端口" + ports[i]);
}
// 死循环,保证服务器一直开启
while (true) {
// 选择一组其相应通道准备好进行I / O操作的键。
int i = selector.select();
System.out.println("i : " + i);
// 获得选择器的键集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("selectionKeys" + selectionKeys);
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍历获得SelectionKey
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 判断是不是带连接状态
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = channel.accept();
socketChannel.configureBlocking(false);
// 连接成功之后将读注册到socketChannel中
socketChannel.register(selector, SelectionKey.OP_READ);
// Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
// 没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.
iterator.remove();
System.out.println("获取客户端连接:" + socketChannel);
// 判断是不是读
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
int byteRead = 0;
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) {
byteBuffer.clear();
int read = channel.read(byteBuffer);
if (read <= 0) {
break;
}
byteBuffer.flip();
channel.write(byteBuffer);
byteRead += read;
}
System.out.println("读取" + byteRead + ", 来自" + channel);
iterator.remove();
// selector.close();
}
}
}
}
}
上诉代码需要注意的是:
-
Selector的创建
Selector selector = Selector.open();
-
向Selector注册通道
serverSocketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ);
-
Selectionkey的四种状态
- SelectionKey.OP_CONNECT //某个channel成功连接到另一个服务器称为“连接就绪”
- SelectionKey.OP_ACCEPT //一个server socket channel准备好接收新进入的连接称为“接收就绪”。
- SelectionKey.OP_READ //一个有数据可读的通道可以说是“读就绪”。
- SelectionKey.OP_WRITE // 等待写数据的通道可以说是“写就绪”。
-
监听结束一定要删除
iterator.remove();