前面两篇文章介绍了NIO中的Buffer和Channel,有了以前的基础,这篇文章来介绍一下另外一个比较重要的概念----Selector。咱们知道系统线程的切换是消耗系统资源的,若是咱们每个链接都用一个线程来管理,资源的开销会很是大,这个时候就能够用Selector。经过Selector能够实现一个线程管理多个Channel,以下图:java
使用以前得到一个Selector对象bash
Selector selector = Selector.open();
复制代码
要把Channel注册到Selector上,Channel必需是非阻塞的。所以FileChannel是没法注册到Selector的。若是注册的时候不调用configureBlocking
方法就会抛出IllegalBlockingModeException
异常。微信
SelectionKey共有四种socket
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
复制代码
ServerSocketChannel的Operation Set只能是OP_ACCEPT,若是在注册的时候添加了OP_CONNECT、OP_WRITE或OP_READ会报异常。例如按照如下写法ui
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT);
复制代码
就会抛出下面的异常spa
Exception in thread "main" java.lang.IllegalArgumentException
at java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:199)
at java.nio.channels.SelectableChannel.register(SelectableChannel.java:280)
at com.nio.sample.selector.SelectorServerSocketChannelSample.main(SelectorServerSocketChannelSample.java:27)
复制代码
ServerSocketChannel的validOps
能够看到只有OP_ACCEPT是合法的线程
public final int validOps() {
return SelectionKey.OP_ACCEPT;
}
复制代码
socketChannel.register(selector, SelectionKey.OP_CONNECT);
复制代码
SocketChannel的Operation Set只能是OP_CONNECT、OP_WRITE和OP_READ,若是在注册的时候添加了OP_ACCEPT一样会报异常。3d
SocketChannel的validOps
能够看到只有OP_READ、OP_WRITE、OP_CONNECT是合法的rest
public final int validOps() {
return (SelectionKey.OP_READ
| SelectionKey.OP_WRITE
| SelectionKey.OP_CONNECT);
}
复制代码
注册成功以后,咱们经过一个demo实现,客户端和服务端交互:code
服务端:
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
// configureBlocking 若是不设置非阻塞,register的时候会报异常
// java.nio.channels.IllegalBlockingModeException
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int selected = selector.select();
if (selected > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isAcceptable()) {
System.err.println("Acceptable");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
System.err.println("Readable");
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(128);
socketChannel.read(buffer);
System.out.println("接收来自客户端的数据:" + new String(buffer.array()));
selectionKey.interestOps(SelectionKey.OP_WRITE);
} else if (selectionKey.isWritable()) {
System.err.println("Writable");
SocketChannel channel = (SocketChannel) selectionKey.channel();
String content = "向客户端发送数据 : " + System.currentTimeMillis();
ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
channel.write(buffer);
selectionKey.interestOps(SelectionKey.OP_READ);
}
}
}
}
}
复制代码
咱们来看一下服务端的逻辑
一、服务端注册到selector,而后interest set(ops)设置为SelectionKey.OP_ACCEPT等待客户端链接。
二、客户端链接到达,调用到selectionKey.isAcceptable()方法,接收客户端链接,而后得到一个channel,并把
interest set设置为SelectionKey.OP_READ等待从通道中读数据。
三、当客户端发送的数据到达,selectionKey.isReadable() 被触发,接收客户端的数据并打印,而后把selectionKey.interestOps 设置为SelectionKey.OP_WRITE,向客户端发送数据。
四、当可写以后selectionKey.isWritable()被触发,向客户端发送数据,同时selectionKey.interestOps再次设置为
SelectionKey.OP_READ等待客户端数据到达。
客户端:
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000));
while (true) {
int select = selector.select();
if (select > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isConnectable()) {
System.err.println("Connectable");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
clientChannel.finishConnect();
selectionKey.interestOps(SelectionKey.OP_WRITE);
} else if (selectionKey.isReadable()) {
System.out.println("Readable");
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(128);
channel.read(buffer);
selectionKey.interestOps(SelectionKey.OP_WRITE);
System.out.println("收到服务端数据" + new String(buffer.array()));
} else if (selectionKey.isWritable()) {
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
String str = "qiwoo mobile";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
clientChannel.write(buffer);
selectionKey.interestOps(SelectionKey.OP_READ);
System.out.println("向服务端发送数据" + new String(buffer.array()));
}
iterator.remove();
}
}
}
}
复制代码
再来看一下服务端的逻辑
一、向服务端发起链接请求。
二、selectionKey.isConnectable()被触发,链接成功以后,selectionKey.interestOps设置为SelectionKey.OP_WRITE,准备向服务端发送数据。
三、channel可写以后selectionKey.isWritable()被触发,向服务端发送数据,以后selectionKey.interestOps设置为SelectionKey.OP_READ,等待服务端过来的数据。
四、服务端数据发过来以后,selectionKey.isReadable()被触发,读取服务端数据以后selectionKey.interestOps设置为SelectionKey.OP_WRITE向服务端写数据。