首页 > 技术文章 > NIO实践-心跳检测

UYGHYTYH 2020-07-18 22:25 原文

  今天将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对象,另外,每次执行完操作都要考虑下一步要监听的操作。

  

 

推荐阅读