1、NIO 非阻塞式网络编程
前面我们讲解了使用多线程来解决IO线程的柱塞问题,那么今天我们来介绍一个java NIO中的非阻塞式的实现,那么它是怎么实现的呢?其中一个核心的组件就是Selector(选择器 也可以叫IO多路复用器),具体的模型如下
2、IO多路复用器Selector
2.1、在java NIO中使用类Selector来描述多路选择器,它的方法列表如下:
2.2、SelectionKey介绍
SelectionKey叫选择键,一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。
方法列表:
key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候 指定。
key.channel(); // 返回该SelectionKey对应的channel。
key.selector(); // 返回该SelectionKey对应的Selector。
key.interestOps();返回代表当前SelectionKey感兴趣的事件列表,感兴趣的列表的含义就是,例
如在注册的通道的时候serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 会创建一个
SelectionKey,然后设置感兴趣的事件列表为OP_ACCEPT,那么就只有当前通道发生OP_ACCEPT事件,创建的
SelectionKey才会被Selector选中。在处理完成被选中的SelectionKey后,SelectionKey是不会被Selector
删除的,这个时候就可以使用 SelectionKey.interestOps(OP_ACCEPT)设置SelectionKey的感兴趣列表,注意
设置规则,比如register阶段是OP_ACCEPT,那么调用interestOps(int) 的时候也只能是OP_ACCEPT,如果之
前是OP_READ可以改成除了OP_ACCEPT的其他,也就是OP_ACCEPT 与其他三种任意一种都不能替换,其他三种之
间可以随意进行设置。在调用SelectionKey.isXXXable()的时候会进行解除之前感兴趣的事件,我们通过重新
SelectionKey.interestOps(OP_ACCEPT)后,可以让这个SelectionKey能够被复用。
key.readyOps(); // 返回一个bit mask,代表在相应channel上可以进行的IO操作。
key.isReadable()//返回当前注册的通道是否可读。
key.isWritable(); //返回当前注册的通道是否可写。
key.isConnectable();//回当前注册的通道是否连接就绪。
key.isAcceptable(); //返回当前注册的通道是否是被结束的。
SelectionKey类中的常量:
SelectionKey.OP_ACCEPT : 表示accept事件类型
SelectionKey.OP_READ : 表示可读事件类型
SelectionKey.OP_WRITE; : 表示可写事件类型
SelectionKey.OP_CONNECT : 表示连接就绪事件类型
2.3、Selector中的主要方法介绍:
1、open():构建一个选择器实例。
2、isOpen():判断当前选择器是否是开启状态。
3、keys():返回当前选择器上所有注册的选择列表。
4、selectedKeys():返回当前选择器上被选中的选择键列表,被选中表示有可读或可写等通道了。
5、select():返回当前被选择器选择的数量。
3、java NIO 非阻塞式网络编程实战:
3.1、服务端:
@Test
public void nioNonBlockingServer() throws IOException {
//1、开启一个ServerSocketChannel 服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2、配置服务端通道为非阻塞的
serverSocketChannel.configureBlocking(false);
//3、绑定服务端通道 端口为9898
serverSocketChannel.bind(new InetSocketAddress(9898));
//4、打开一个选择器
Selector selector = Selector.open();
//5、将服务端的 通道 注册到 选择器 上,注册的事件类型是接受accept类型。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6、轮询选择器的选择数量,只要大于1 就表示有事件需要处理。
while (selector.select() >= 1) {
//7、获取到选择器上所有被选择的 选择键列表,循环处理每一个选择键。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//如果当前循环的 选择键 是accept事件,那就使用服务端 通道进行 接受处理。
if (selectionKey.isAcceptable()) {
//接受客户端连接后,一定要把客户端通道也同样设置为非阻塞,然后注册到选择器上。
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//如果当前循环的 选择键 是 通道数据可读 事件,那就使用选择键获取通道,然后进行数据读取。
} else if (selectionKey.isReadable()) {
SelectableChannel channel = selectionKey.channel();
if (channel instanceof SocketChannel) {
SocketChannel socketChannel = (SocketChannel) channel;
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = socketChannel.read(byteBuffer)) > 0) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
}
} //其他事件也是同样的处理方式。
//8、选择键处理完成后一定要当前循环的选择键。
iterator.remove();
}
}
}
3.2、客户端实现:
@Test
public void nioNonBolckingClient1() throws IOException {
//1、构建客户端通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2、将客户端设置为非阻塞的
socketChannel.configureBlocking(false);
//3、分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//4、向缓冲区中存入 需要发送到服务端的数据。
byteBuffer.put("你好我是客户端!".getBytes());
byteBuffer.flip();
//6、使用客户端通道进行数据传输。
socketChannel.write(byteBuffer);
byteBuffer.clear();
}