先打个小广告,关注辛星教程,我的微信号xinxing0913,该项目源码所在的github地址: https://github.com/xinxing0913/xinxing-nio-guide。
在传统的服务器模型中,一般都是用一个线程去处理一个连接,等这个连接处理完毕,这个线程就归还到线程池中,从而保证有条不紊的执行。
但是当连接数量非常多的时候,我们的线程数量就会非常多,这在连接数量非常多的情况下会有不小的挑战,为了解决这个问题,nio引入了selector这个概念。
selector即选择器,它表示检查一个或者多个nio的channel的状态是否处于可读可写的状态,它可以用于实现单线程管理多个channel,从而管理多个网络连接。
我们的一般使用方式就变成了多个连接对应一个selector,然后这个selector属于一个线程,这样就完成了一个线程内处理多个连接。
我们可以使用如下的方式来创建一个selector:
Selector selector = Selector.open();
然后我们把非阻塞的channel注册到selector上,注意这里的channel必须是非阻塞的:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
对于register的第二个参数,它表示关注集合,也就是我们关注的channel的状态,它的中可以有四个:
SelectionKey.OP_CONNECT表示连接状态
SelectionKey.OP_ACCEPT表示接受状态
SelectionKey.OP_READ表示读取数据
SelectionKey.OP_WRITE表示写入数据
当一个channel与服务端连接成功后,它就处于连接就绪状态,如果是可读的时候,就处于可读就绪状态。多个状态之间可以使用"|"来进行组合。
我们可以通过selectionKey的channel()方法获取Channel,我们可以通过它的selector()方法来获取Selector。
我们可以通过Selector对象的三个方法来获取一个或者多个Channel,它有三个方法:
第一个是select()方法,它在返回channel前处于阻塞状态,它返回当前就绪的channel的个数。
第二个是select(long timeout)方法,它在返回channel前也处于阻塞状态,它返回当前可以选择的channel的个数。
第三个是selectNow()方法,它不会阻塞,它会立即返回有多少合适的channel。
我们可以用selector来改造我们之前的基于TCP的服务端,我们这里新建范例代码如下:
/**
* 基于nio并且使用了selector的服务端
*/
public class Demo11 {
public static void main(String[] args) throws Exception {
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
ServerSocket socket = channel.socket();
socket.bind(new InetSocketAddress(8080));
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器已启动...");
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
handle(key, selector);
}
}
}
public static void handle(SelectionKey key, Selector selector) throws Exception{
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.finishConnect();
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端已连接...");
} else if (key.isReadable()) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = (SocketChannel) key.channel();
socketChannel.read(byteBuffer);
socketChannel.write(ByteBuffer.wrap("received!!".getBytes()));
socketChannel.close();
System.out.println("从客户端收到的信息:" + new String(byteBuffer.array()));
} else {
System.out.println("内部错误...");
}
}
}
然后我们这里的客户端可以用Selector去重新实现,也可以直接复用Demo9的代码,不过我这里在读和写之间加了一秒钟的等待时间来确保写操作完成,如下:
/**
* 基于nio实现的TCP客户端
*/
public class Demo12 {
public static void main(String[] args) throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // 设置为非阻塞
socketChannel.connect(new InetSocketAddress(8080));
socketChannel.finishConnect(); // 等待连接完成
// 向服务端发送一段文本
byte[] bytes = "hello 辛星".getBytes();
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
socketChannel.write(byteBuffer);
// 等待一秒钟
Thread.sleep(1000);
// 最多只接受服务端的1024个字节
ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
socketChannel.read(responseBuffer);
System.out.println("服务端响应的数据:" + new String(responseBuffer.array()));
}
}
我们首先运行服务器,如下所示:
然后我们运行Demo12这个客户端,如下所示:
然后我们在Demo11的控制台下就会看到对应的输出:
对于Selector,就暂时介绍到这里啦。