对于客户端,如果它想往服务端发送键盘录入的内容时,获取键盘录入对象是免不了的事, 但是这对象会阻塞,于是客户端不得不开启一条新的线程运行读取键盘录入,让自己具有键盘录入的功能,同时又不会被阻塞, 如果客户端想要接受服务端推送回来的数据怎么办呢? 于是我们就得告诉客户端的选择器,添加一个感兴趣的事件,read, 这样,一旦服务端有数据推送过来的,客户端的选择器就会感知到这个事件,并且这个事件的selectionKay是可读的,这样一个比较完善的客户端就ok了
executorService.submit(() -> { while (true) { try { // 清空上面的缓存 byteBuffer.clear(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); String msg = bufferedReader.readLine(); byteBuffer.put(msg.getBytes()); byteBuffer.flip(); finalClient.write(byteBuffer); } catch (Exception e) { e.printStackTrace(); } } }); } // 上面的代码是发生了 请求连接事件 // todo 给客户端注册一个读取客户端返回数据的事件 client.register(selector, SelectionKey.OP_READ);服务端在建立连接时,就给客户端的通道绑定了感兴趣的事件是read, 于是当客户端往channel中write数据了,服务端就会来到下面的代码块, 如果是群聊的话, 我们就得知道,往哪些用户转发信息, 于是我们提前构造了map,这个map存放就是一个一个和服务的channel建立连接的SocketChannel; 只需要遍历map, 往里面的chanel,write数据即可
else if (selectionKey.isReadable()) { System.out.println("readable..."); // 获取客户端的通道 socketChannel = (SocketChannel) selectionKey.channel(); System.out.println("当前的客户端 通道实例: socketChannel == " + socketChannel); // 获取当前 是哪个客户端发起的信息 ByteBuffer byteBuffer = ByteBuffer.allocate(512); // 读取客户端发送的消息 while (true) {// todo todo todo 很重要的一点!!! read方法是非阻塞的, 很可能还有没读取到数据就返回了 int read = socketChannel.read(byteBuffer); System.out.println("read == : " + read); if (read <= 0) { break; } } // 往其他客户端写 byteBuffer.flip(); Charset charset = Charset.forName("utf-8"); String msg = String.valueOf(charset.decode(byteBuffer).array()); // Buffer转字符串 System.out.println("收到客户端: " + socketChannel + " 发送的消息: " + msg); // 遍历map for (Map.Entry<String, SocketChannel> map : clientMap.entrySet()) { if (socketChannel == map.getValue()) { sendKey = map.getKey(); } } // todo 转发给全部的客户端发送 for (Map.Entry<String, SocketChannel> map : clientMap.entrySet()) { SocketChannel socketChannel1 = map.getValue(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(512); // 把信息放进 byteBuffer1中 String message = msg + " : " + sendKey; byteBuffer1.put(message.getBytes()); byteBuffer.flip(); socketChannel1.write(byteBuffer); }客户端断开了怎么办呢? 在一台电脑上,手动将一个客户端停掉,服务端会运行到selectionKey.isReadable() 并且进入这个if块, 当它尝试从里面读取的时候,就发现这个连接已经坏掉了,于是报错,强制断开连接, 因为还要继续轮询,全集key set 中依然保存着当前的客户端的channel, 所以会一直报错下去, 怎么办呢? 如下
// selectionKey.cancel(); 常规 try { // 这样也能取消这个键 socketChannel.close(); } catch (IOException e1) { e1.printStackTrace(); } // 当然我们现在还要多一步, 因为他还在我们的map里面 不然一会发消息的时候,会出错 // todo 移除出map 中失效的 channel // todo 遍历map for (Map.Entry<String, SocketChannel> map : clientMap.entrySet()) { if (socketChannel == map.getValue()) { sendKey = map.getKey(); } } clientMap.remove(sendKey, socketChannel);