今天将NIO实现简版心跳检测功能做一下笔记,旨在加深理解NIO客户端与服务端交互的状态监听,以及固定的编码套路。其实跟产品级的心跳检测(包括但不限于token验证、服务性能参数获取等)尚且存在差距。暂且忽略。
一、方案设计
二、服务端代码实现
1 public class HeartBeatServer { 2 @Test 3 public void Test() throws IOException { 4 // 构建服务端Socket 5 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 6 // 绑定端口 7 serverSocketChannel.bind(new InetSocketAddress(8080)); 8 // 设置为异步Socket 9 serverSocketChannel.configureBlocking(false); 10 // 建立服务端多路复用器 11 Selector selector = Selector.open(); 12 // 服务端Socket注册接入监听 13 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 14 try { 15 dispatch(selector, serverSocketChannel); 16 } catch (Exception e) { 17 e.printStackTrace(); 18 } 19 } 20 private void dispatch(Selector selector, ServerSocketChannel serverSocketChannel) throws IOException { 21 while (true) { 22 selector.select(); 23 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 24 if (iterator.hasNext()) { 25 SelectionKey key = iterator.next(); 26 iterator.remove(); 27 if (!key.isValid()) { 28 continue; 29 } 30 if (key.isAcceptable()) { 31 // 服务端通过serverSocketChannel.accept()不断的处理新的客户端接入 32 SocketChannel socketChannel = serverSocketChannel.accept(); 33 socketChannel.configureBlocking(false); 34 // 注册新接入的客户端channel为可读,即等待读取客户端发送心跳信息 35 socketChannel.register(selector, SelectionKey.OP_READ); 36 } else if (key.isReadable()) { 37 SocketChannel channel = (SocketChannel) key.channel(); 38 ByteBuffer buffer = ByteBuffer.allocate(1024); 39 channel.read(buffer); 40 if (buffer.hasRemaining() && buffer.get(0) == 4) { 41 channel.close(); 42 System.out.println("关闭管道:" + channel); 43 break; 44 } 45 System.out.println(new String(buffer.array(), 0, 20)); 46 buffer.put(String.valueOf(System.currentTimeMillis()).getBytes()); 47 buffer.flip(); 48 channel.write(buffer); 49 } 50 } 51 } 52 } 53 }
三、客户端代码
1 public class HeartBeatClient1 { 2 @Test 3 public void Test() throws IOException, InterruptedException { 4 // 初始化客户端Socket 5 SocketChannel socketChannel = SocketChannel.open(); 6 socketChannel.configureBlocking(false); 7 Selector selector = Selector.open(); 8 // 注册连接成功事件监听 9 socketChannel.register(selector, SelectionKey.OP_CONNECT); 10 // 发起连接 11 socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); 12 while (true) { 13 selector.select(); 14 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 15 if (iterator.hasNext()) { 16 SelectionKey key = iterator.next(); 17 iterator.remove();; 18 if (!key.isValid()) { 19 continue; 20 } 21 // 当连接到服务端成功时,变更监听为 可写 22 if (key.isConnectable()) { 23 SocketChannel channel = (SocketChannel) key.channel(); 24 channel.finishConnect(); 25 key.interestOps(SelectionKey.OP_WRITE); 26 } 27 // 写入心跳信息,然后变更监听为 可读,即等待读取服务端 回执 28 if (key.isWritable()) { 29 SocketChannel channel = (SocketChannel) key.channel(); 30 channel.write(ByteBuffer.wrap("heartBeatClient_1__".getBytes())); 31 key.interestOps(SelectionKey.OP_READ); 32 } 33 // 读取服务端回执后,然后变更监听为 可写,准备下一次写入心跳信息 34 if (key.isReadable()) { 35 SocketChannel channel = (SocketChannel) key.channel(); 36 ByteBuffer buffer = ByteBuffer.allocate(64); 37 channel.read(buffer); 38 buffer.flip(); 39 System.out.println(new String(buffer.array(), 0, buffer.limit())); 40 key.interestOps(SelectionKey.OP_WRITE); 41 Thread.sleep(2000); 42 } 43 } 44 } 45 } 46 }
四、总结
其实这个Demo虽然很糙,但是基本囊括了原生NIO编程的常规套路。总结如下:
服务端:
<1>、构建ServerSocketChannel。并设置为非阻塞。
<2>、构建Selector。并注册服务端accept监听,来等待客户端接入事件。
<3>、监听到客户端接入后,将客户端接入的SocketChannel注册到Selector,并监听该SocketChannel的Read事件,即等待读取客户端发送的数据。
<4>、对于服务端来说,可能会有多个客户端接入,所以每次Selector轮询,都要从key里边取channel。
客户端:
<1>、构建客户端SockeChannel,并设置为非阻塞。
<2>、构建Selector。并注册客户端connect监听。
<3>、客户端发起connect操作,如果连接成功,则会触发<2>的监听。
<4>、客户端自始至终都是只有一个channel,所以不用每次都从key里边取,即始终都是构建时的那个channel对象,另外,每次执行完操作都要考虑下一步要监听的操作。