前言
大概的了解了NIO的运行与三个组件
Java网络编程(4)NIO的理解与NIO的三个组件
并详细学习了:Buffer和Channel
Java网络编程(5)NIO - Buffer详解
Java网络编程(6)NIO - Channel详解
接下来完成Selector的学习
目录
Selector作用
NIO编程的结构:
选择器让一个线程能够处理多个通道,选择器轮询注册在其上的通道,Selector只能管理非阻塞的通道,文件通道(FileChannel等等)是阻塞的,无法管理
所以Selector是网络通信通道的选择器,最常见的是对ServerSocketChannel和SocketChannel的操作
Selector类继承关系
Selector是一个抽象类,有多个子类,其中实现类是WindowsSelectorImpl
常用方法
Selector自身有这些方法:
- 实例化
因为Selector是抽象类,即不能new 实例化,要通open()方法获得一个Selector对象
Selector selector = Selector.open();
- select()
select()方法是监控所有注册在选择器上的通道,当有IO操作可以进行时,将该通道对应的SelectionKey加入集合并返回(SelectionKey是一个重要的对象)
- select(long timeout)
因为select方法是阻塞的,即一定要等到一个通道有事件发生,可以设置TimeOut时间解除阻塞 - selectNow()
非阻塞的select方法 - selectedKeys()
获得存在事件的SelectionKey - keys()
保存了所有已注册的通道的SelectionKey - isOpen()
判断该选择器是否打开 - wakeup()
唤醒选择器
selector.select()是阻塞的,通常情况下,只有注册在selector上的channel有事件就绪时,select()才会从阻塞中被唤醒,处理就绪事件,当selector上的channel无就绪事件时,如果想要唤醒阻塞在select()操作上的线程去处理一些别的工作就可以通过wakeup方法
SelectionKey
SelectionKey是一个抽象类,实现类是SelectionKeyImpl类
SelectionKey表示SelectableChannel在Selector中注册的标识.每个Channel向Selector注册时,都将会创建一个selectionKey
SelectableChannel是各种网络通道的父类,可以向下转型为我们想要的Channel
使用
SelectionKey对象。这个对象包含了一些属性:
- interest集合:所选择的感兴趣的事件集合
- ready集合:通道已经准备就绪的操作的集合
- Channel:SelectionKey可以得到标记的Channel对象
- Selector:SelectionKey可以得到使用的Selector对象
- 附加的对象(可选):可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道,例如将Buffer附加上去
- 操作集
通道注册到选择器时,可以选择监听的事件
例如:serverSocketChannel就需要监听请求连接事件
//注册,服务器监听请求连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey.OP_ACCEPT就是一个操作集
SelectionKey有四个操作集,是位运算值:
OP_READ值为1
OP_WRITE值为4
OP_CONNECT值为8
OP_ACCEPT值为16
interesOps():获得此SelectionKey的interes集合.
interestOps(int ops):将此SelectionKey的interst设置为指定值(前面是在注册时设置)
- Channel()
返回此选择键所关联的通道.即使此key已经被取消,仍然会返回
在读写操作中,一般都是用这个方法通过SelectionKey获得Channel - Selector()
返回此选择键所关联的Selector - 附加对象:attachment()
在注册时可以选择设置一个缓冲区
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
这样可以减少缓冲区的开销,然后通过attachment方法获得该缓冲区
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
当然可以在SelectionKey上也可以设置
attach(Object ob):将给定的对象作为附件添加到此key上
- ready集合:判断事件
isAcceptable():判断是否是连接事件(服务器通道使用)
isValid():判断该key是否有效
isReadable(): 检测此键是否为"read"事件
一个聊天系统案例
服务器
package com.company.Selector.Selector1;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws Exception {
//打开ServerSocketChannel等待连接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置非阻塞
serverSocketChannel.configureBlocking(false);
//绑定端口号6666
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//打开选择器
Selector selector = Selector.open();
//注册,监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
//循环监听
//select是阻塞方法
/* if ( selector.select(5000)==0){
System.out.println("没有客户端连接");
continue;
}*/
selector.select();
//获得存在事件的SelectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//迭代Set集合
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
if (key.isAcceptable()){
System.out.println("得到客户端连接");
//accept得到SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//设置非阻塞
socketChannel.configureBlocking(false);
//注册,socketChannel监听读事件
//关联一个bytebuffer
//socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()){
//SelectionKey的channel方法得到SocketChannel
SocketChannel socketChannel = (SocketChannel) key.channel();
//获得buffer
//ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//从通道读出数据
socketChannel.read(byteBuffer);
byteBuffer.clear();
System.out.println(new String(byteBuffer.array()));
}
keyIterator.remove();
}
}
}
}
客户端
package com.company.Selector.Selector1;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class NIOClient {
public static void main(String[] args) throws Exception{
//打开通道
SocketChannel socketChannel = SocketChannel.open();
//设置为非阻塞
socketChannel.configureBlocking(false);
//绑定
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("连接失败");
}
}
while (true) {
Scanner scanner = new Scanner(System.in);
String str = scanner.nextLine();
System.out.println(str);
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
socketChannel.write(byteBuffer);
}
}
}
总结
- Selector是一个选择器,用于处理非阻塞的通道,即一个线程设置一个选择器,可以处理多个通道
- Selector用于处理网络通道,常用的有ServerSocketChannel、SocketChannel,需要这些通道注册在Selector上才可以管理
- Selector有许多方法,其中select()方法监控所有通道,当有通道发生事件时,会将该通道的SelectionKey对象保存到集合
- SelectionKey是对象是一个标识对象,里面保存着许多属性