如上图, 在NIO网络编程模式中,不再是传统的多线程编程模型,当有新的客户端的连接到来,不再重新开辟新的线程去跑本次连接,而是统一,一条线程处理所有的连接, 而一次连接本质上就是一个Channel, NIO网络编程模型是基于事件驱动型的; 即,有了提前约定好的事件发生,接着处理事件,没有时间发生,选择器就一直轮询 下面解释上图的流程
服务端创建代表服务端的Channel,绑定好端口,设置成非阻塞的通道 并且初始化选择器,然后开始轮询绑定在自己身上的通道,此时的通道只有一个ServerSocketChannel,而选择器只关心ServerSocketChannel上发生的OP_ACCEPT事件,而又没有客户端来链接 所以他被阻塞在了select()
System.out.println("Server..."); // 获取服务端的SerSokcetChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // todo 一定要把他配置成 非阻塞的 serverSocketChannel.configureBlocking(false); // 从通道中获取 服务端的对象 ServerSocket serverSocket = serverSocketChannel.socket(); serverSocket.bind(new InetSocketAddress(8899)); // 创建选择器 Selector selector = Selector.open(); // 把通到注册到 选择器上 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { // 阻塞式等待 channel上有事件发生 int select = selector.select();客户端 创建代表自己的SocketChannel, 创建选择器,把自己的注册在上面,如下代码, 初始化自己,SocketChannel, 把客户端的通道注册进选择器,并告诉选择器SocketChannel的感兴趣事件是OP_CONNECT连接事件; 当执行到下面的socketChannel.connect(new InetSocketAddress("localhost", 8899)); 连接的请求就已经发送出去了,也就是说,如果没有意外,执行完这一行代码,服务端的select()方法已经返回了, 但是客户端的connect()是非阻塞的,立即返回,故在客户端依然会继续执行, 进而判断一下是否是真的连接上了
// 获取客户端的通道 SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); // 把客户端的通道注册进选择器 socketChannel.register(selector, SelectionKey.OP_CONNECT); // todo 连接客户端, 执行完这行代码后, 服务端就能就收到通知!!! socketChannel.connect(new InetSocketAddress("localhost", 8899)); while (true) { int number = selector.select(); // 选择器阻塞式的 等待 Channel上发生它关心的事件 System.out.println(" 发生了感兴趣的事件: " + number); Set<SelectionKey> keySet = selector.selectedKeys(); // 验证 for (SelectionKey selectionKey : keySet) { SocketChannel client = null; if (selectionKey.isConnectable()) { // 强转成 有连接事件发生的Channel client = (SocketChannel) selectionKey.channel(); // 完成连接 if (client.isConnectionPending()) { client.finishConnect(); ByteBuffer byteBuffer = ByteBuffer.allocate(512); byteBuffer.put((LocalDate.now() + "连接成功").getBytes()); byteBuffer.flip(); client.write(byteBuffer);对于服务端,轮询了这么久,终于有连接进来了,于是进一步处理, 判断如果当前的连接是请求建立连接的话,就去建立连接, 对于服务端来说,建立连接就是然服务端记住客户端, 客户端是谁呢?SocketChanel, 怎么获取呢? serverSocketChannel1.accept(); 怎么建立连接呢? 实际上就是把当前的客户端的channel注册在服务端的选择器上,并告诉它自己关心的事件啥, 当然一开始建立连接时, 服务端肯定首先要做的就是监听客户端发送过来的数据,于是 绑定上感兴趣的事件是read, 并且不要忘了,每次遍历感兴趣的key的集合时,都要及时的把当前的key剔除
selectionKeys.forEach(selectionKey -> { SocketChannel socketChannel = null; String sendKey = null; try { if (selectionKey.isAcceptable()) { // 1. 用户请求建立连接, 根据SelectionKey 获取服务端的通道 // todo 当前的这个SelecttionKey 是有 ServerSocketChannel 和 selector 联系生成的, 因此我们 强制转换回 ServerSocketChannel ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel(); // todo !!!!!!! 这是重点, 这里的accept是非阻塞的 !!!!!!!! // 根据服务的 通道 获取到客户端的通道 socketChannel = serverSocketChannel1.accept(); System.out.println("socketChannel.class: " + socketChannel.getClass()); // todo 配置成非阻塞的 socketChannel.configureBlocking(false); // todo 新获取的通道 注册进选择器 socketChannel.register(selector, SelectionKey.OP_READ); // 保存客户端的信息 String key = "[ " + UUID.randomUUID().toString() + " ]"; clientMap.put(key, socketChannel); // todo 把 拥有当前事件SelectionKey 剔除