NIO核心之选择器(Selector)与NIO网络通信原理
选择器(Selector)
选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心。
-
-
Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector,如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
-
只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
-
-
选择 器(Selector)的应用
创建 Selector :通过调用 Selector.open() 方法创建一个 Selector。
Selector selector = Selector.open();
向选择器注册通道:SelectableChannel.register(Selector sel, int ops)
//1. 获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); //2. 切换非阻塞模式 ssChannel.configureBlocking(false); //3. 绑定连接 ssChannel.bind(new InetSocketAddress(9898)); //4. 获取选择器 Selector selector = Selector.open(); //5. 将通道注册到选择器上, 并且指定“监听接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT);
当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。可以监听的事件类型(用 可使用 SelectionKey 的四个常量 表示):
-
读 : SelectionKey.OP_READ (1)
-
写 : SelectionKey.OP_WRITE (4)
-
连接 : SelectionKey.OP_CONNECT (8)
-
接收 : SelectionKey.OP_ACCEPT (16)
若注册时不止监听一个事件,则可以使用“位或”操作符连接。
int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE
NIO网络通信原理
Selector可以实现: 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
public class Server { public static void main(String[] args) throws Exception { System.out.println("----服务端启动---"); // 1、获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 2、切换为非阻塞模式 ssChannel.configureBlocking(false); // 3、绑定连接的端口 ssChannel.bind(new InetSocketAddress(9999)); // 4、获取选择器Selector Selector selector = Selector.open(); // 5、将通道都注册到选择器上去,并且开始指定监听接收事件 ssChannel.register(selector , SelectionKey.OP_ACCEPT); // 6、使用Selector选择器轮询已经就绪好的事件 while (selector.select() > 0){ System.out.println("开始新一轮事件处理"); // 7、获取选择器中的所有注册的通道中已经就绪好的事件 迭代器的方式获得:selectedKeys获取当前选择器轮询到的所有事件 iterator()迭代器 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // 8、开始遍历这些准备好的事件 如果有下一个 就提取 while (it.hasNext()){ // 提取当前这个事件 SelectionKey sk = it.next(); // 9、判断这个事件具体是什么 是接入事件?是 则接入客户端通道 if(sk.isAcceptable()){ // 10、直接获取当前接入的客户端通道 SocketChannel schannel = ssChannel.accept(); // 11 、切换成非阻塞模式 schannel.configureBlocking(false); // 12、将本客户端通道注册到选择器 进行读的监听(客户端写过来 服务端负责读) schannel.register(selector , SelectionKey.OP_READ); //如果是读事件 }else if(sk.isReadable()){ // 13、获取当前选择器上的读就绪事件 先获得通道 获得客户端连接 有通道则说明有数据过来 SocketChannel sChannel = (SocketChannel) sk.channel(); // 14、设置buffer读取数据 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; //循环读取通道中的缓冲区 >0 还有数据未读 循环读完 while((len = sChannel.read(buf)) > 0){ buf.flip();//归位 System.out.println(new String(buf.array() , 0, len)); buf.clear();// 清除之前的数据 } } it.remove(); // 处理完毕之后需要移除当前事件 } } } }
public class Client { public static void main(String[] args) throws Exception { // 1、获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); // 2、切换成非阻塞模式 sChannel.configureBlocking(false); // 3、分配指定缓冲区大小 ByteBuffer buf = ByteBuffer.allocate(1024); // 4、发送数据给服务端 Scanner sc = new Scanner(System.in); while (true){ System.out.println("请说:"); String msg = sc.nextLine(); buf.put(("theone:"+msg).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } } }
总结:
BIO、NIO:
-
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
-
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
BIO、NIO适用场景分析:
-
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
-
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。