Nio实战演练1(心跳服务)

实现长连接心跳服务

在类似RPC(远程过程调用)场景中为了保证传输的效率,通常情况下会采用长链接,而长链接的保持即通过定时心跳实现。类似场景在消息推送服务中也是非常常见。所以心跳服务是网络编程中的基础且普遍的应用。
在这里插入图片描述
Dubbo和zookpper都有心跳饱和机制

心跳服务需求说明

由客户端定时发送给服务端,服务端作出响应。因为一直要发送,所以发送的消息和返回的消息的体量必须足够小,只要能标识心跳事件即可。具体消息格式设计要根据所使用的应用协议而定。

心跳服务设计

在客户端我们采用SocketChannel来连接服务并注册到选择器,由选择器来监听管道的状态并触发相对应的事件处理。并通过线程休眠来实现,定时发送。其整个流程如下图
在这里插入图片描述

  1. 先初始化初始化管道,建立连接()。
    连接远程服务,在非阻塞模式,connect()是异步完成的,当服务端Accept链接,客户端会触发OP_CONNECT事件,然后必须调用 finishConnect() 才能真正完成调用。
SocketChannel channel = SocketChannel.open();// 打开管道
channel.configureBlocking(false); //设置非阻塞模型
Selector selector = Selector.open();//打开选择器
channel.register(selector, SelectionKey.OP_CONNECT);//监听连接事件
  1. 注册选择器。
  2. 选择器进行循环遍历(基于对择器的轮询,就可以获得选择集,并触发相对应事件)。
while (true) {//轮询选择器
    selector.select(100);// 刷新选择集
    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    while (iterator.hasNext()) {//遍历选择集
        SelectionKey key = iterator.next();
        iterator.remove();
        if (!key.isValid()) {
            continue;
        }
    }
}

循序操作首先调用select()刷新键,刷新键的时候会有三个事件

  • op_conec监听连接 一旦变成可连接的就会把转态切换为OP_write

当服务端Accept连接后,客户端就会触发OP_CONNECT事件。此时并不代表连接已完成,这时往管道中写数据是会报NotYetConnectedException异常的。必须调用 finishConnect()才会真正建立好连接。当建立连接后,就不在需要监听 OP_CONNECT,而是OP_WRITE事件,以将心跳事件数据写入管道。

if (key.isConnectable()) {
    //返回false ,因为还没有真正建立链接 
    System.out.println("是否连接:"+channel.isConnected());
    channel.finishConnect();
    key.interestOps(SelectionKey.OP_WRITE);// 监听写数据
}

OP_write 监听可写 一旦写入心跳就是把转态切换为 op_read

什么时候会触发OP_WRITE 事件?当建立连接后管道就是一个可写状态,所以直接就能触发OP_WRITE事件。并且在关闭连接前OP_WRITE事件,一直会被触发。所以写入心跳后必须移除对OP_WRITE事件的监听,改为监听OP_READ事件。

if (key.isConnectable()) {
    //返回false ,因为还没有真正建立链接 
    System.out.println("是否连接:"+channel.isConnected());
    channel.finishConnect();
    key.interestOps(SelectionKey.OP_WRITE);// 监听写数据
}

op_read 监听可读 等待读取结果等待两秒钟就会切换为OP_write

当服务端消息返回客户后就会触发客户端的OP_READ事件,此时直接读取即可。然后在将线程休眠2s后切换监听到OP_WRITE。如果不休眠会立马触发OP_WRITE事件。

channel.read(ByteBuffer.allocate(64));
key.interestOps(SelectionKey.OP_WRITE);
Thread.sleep(2000);// 休眠2秒防止立马进行写入

因为是客户端不是服务端没有accpet

2.心跳服务端设计

在这里插入图片描述
服务端流程说明

  1. 初始化管道
    与客户端类似 也是NIO中常规操作,打开管道与选择器,设置阻塞然后注册到选择器。

这里服务端使用ServerSocketChannel ,而客户端使用SocketChannel。两者区别是ServerSocketChannel仅用于接受连接,不支持读写。而SocketChannel用于连接和读写。

ServerSocketChannel serverListener=ServerSocketChannel.open();
serverListener.bind(new InetSocketAddress(8080));// 绑定TCP端口
serverListener.configureBlocking(false);
Selector selector = Selector.open();
// 注册ACCEPT 事件,用于同意客户端连接
serverListener.register(selector,SelectionKey.OP_ACCEPT);
  1. 轮询选择器
    请参照上面选择器轮询
  2. 触发OP_ACCEPT事件
    OP_ACCEPT指有新连接到达,通过ServerSocketChannel.accept() 即可获取一个新管道SocketChannel。 基于它就可以与客户端进行读写。这种读写同样基于非阻塞方式执行,并注册到选择器。
if(key.isAcceptable()){
    SocketChannel socketChannel = serverListener.accept();
    socketChannel.configureBlocking(false);
    // 将新管道注册到选择器
    socketChannel.register(selector, SelectionKey.OP_READ);
}

注:假设服务端接收的连接到达极限,是否可以直接勿略客户端连接,不执行accept()方法?这是不行的,在TCP中所有事件都必须进行处理,否则会一直触发该事件,造成死循环。
4. 触发OP_READ事件
当客户端发送数据过来,即会触发读取事件,这时直接读取即可,然后在写回响应数据即可。

if(key.isReadable()){
    SocketChannel channel = (SocketChannel) key.channel();
    ByteBuffer buffer=ByteBuffer.allocate(64);
    channel.read(buffer);// 读取数据到缓冲区
    if (buffer.get(0)==4) {//客户端主动关闭连接, 传输结束
      channel.close();
      System.out.println("关闭管道:"+channel);
      break;
    }
    buffer.put(String.valueOf(System.currentTimeMillis()).getBytes());
    buffer.flip();
    channel.write(buffer);// 写回数据到管道
}

字节’4’ 在ASCII中 表示EOT (end of transmission )传输结束,所以在管道中读取到4这个字节,即可手动的去关闭连接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值