在上一篇博文实战:ServerSocketChannel(模拟多路IO复用)我们用循环和列表实现了多路IO复用,现在我们用官方提供的Selector来实现同样的功能。
一 Selector(选择器)介绍
Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
二 Selector(选择器)的使用方法介绍
1. Selector的创建
通过调用Selector.open()方法创建一个Selector对象,如下:
Selector selector = Selector.open();
这里需要说明一下
2. 注册Channel到Selector
channel.configureBlocking(false);SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Channel必须是非阻塞的。所以FileChannel不适用Selector,因为FileChannel不能切换为非阻塞模式,更准确的来说是因为FileChannel没有继承SelectableChannel。Socket channel可以正常使用。
SelectableChannel抽象类 有一个 configureBlocking() 方法用于使通道处于阻塞模式或非阻塞模式。
abstract SelectableChannel configureBlocking(boolean block)
SelectableChannel抽象类的configureBlocking() 方法是由 AbstractSelectableChannel抽象类实现的,SocketChannel、ServerSocketChannel、DatagramChannel都是直接继承了 AbstractSelectableChannel抽象类
register() 方法的第二个参数。这是一个“ interest集合 ”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
- Connect
- Accept
- Read
- Write
通道触发了一个事件意思是该事件已经就绪。比如某个Channel成功连接到另一个服务器称为“ 连接就绪 ”。一个Server Socket Channel准备好接收新进入的连接称为“ 接收就绪 ”。一个有数据可读的通道可以说是“ 读就绪 ”。等待写数据的通道可以说是“ 写就绪 ”。
这四种事件用SelectionKey的四个常量来表示:
SelectionKey.OP_CONNECTSelectionKey.OP_ACCEPTSelectionKey.OP_READSelectionKey.OP_WRITE
如果你对不止一种事件感兴趣,使用或运算符即可,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
select()方法介绍
在刚初始化的Selector对象中,这三个集合都是空的。 通过Selector的select()方法可以选择已经准备就绪的通道 (这些通道包含你感兴趣的的事件)。比如你对读就绪的通道感兴趣,那么select()方法就会返回读事件已经就绪的那些通道。
三、实战
这里模拟实战:ServerSocketChannel(模拟多路IO复用)这一篇博文里写一个功能一样的例子。
其实总体长得很相似
服务器端
public class Server2 {private static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);public static void main(String[] args) {try {//1、创建ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//2、绑定我们的端口号serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 80));//3、设置连接模式为非阻塞式serverSocketChannel.configureBlocking(false);Selector selector = Selector.open();// 注册 channel,并且指定感兴趣的事件是 AcceptserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while(true) {//4、非阻塞式不会阻塞int readyNum = selector.select();if (readyNum == 0) {continue;}//这里就表示有准备好的消息Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> it = selectedKeys.iterator();while(it.hasNext()) {SelectionKey key = it.next();if(key.isAcceptable()) {System.out.println("有新连接");// 接受连接// 创建新的连接,并且把连接注册到selector上,而且,// 声明这个channel只对读操作感兴趣。SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {//// 通道可读SocketChannel socketChannel = (SocketChannel) key.channel();//清除缓存中的信息byteBuffer.clear();int read = socketChannel.read(byteBuffer);if(read>0) {//Buffer切换为读模式byteBuffer.flip();//设定编码Charset charset = Charset.forName("UTF-8");String receiveText = charset.newDecoder().decode(byteBuffer.asReadOnlyBuffer()).toString();System.out.println("receiveText:"+receiveText);}}else if (key.isWritable()) {// 通道可写}//写完后就移除it.remove();}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
客户端
public class Client {public static void main(String[] args) throws IOException {Socket socket = new Socket();socket.connect(new InetSocketAddress("127.0.0.1", 80));while(true) {Scanner scanner = new Scanner(System.in);socket.getOutputStream().write(scanner.next().getBytes());}}}
结束:具体原理以后再研究啦!

