5. 断开重连
Netty 客户端需要实现断开重连机制,解决各种情况下的断开情况。例如说:
- Netty 客户端启动时,Netty 服务端处于挂掉,导致无法连接上。
- 在运行过程中,Netty 服务端挂掉,导致连接被断开。
- 任一一端网络抖动,导致连接异常断开。
具体的代码实现比较简单,只需要在两个地方增加重连机制。
- Netty 客户端启动时,无法连接 Netty 服务端时,发起重连。
- Netty 客户端运行时,和 Netty 断开连接时,发起重连。
考虑到重连会存在失败的情况,我们采用定时重连的方式,避免占用过多资源。
5.1 具体代码
① 在 NettyClient 中,提供 #reconnect() 方法,实现定时重连的逻辑。代码如下:
// NettyClient.javapublic void reconnect() { eventGroup.schedule(new Runnable() { @Override public void run() { logger.info("[reconnect][开始重连]"); try { start(); } catch (InterruptedException e) { logger.error("[reconnect][重连失败]", e); } } }, RECONNECT_SECONDS, TimeUnit.SECONDS); logger.info("[reconnect][{} 秒后将发起重连]", RECONNECT_SECONDS);}
通过调用 EventLoop 提供的 #schedule(Runnable command, long delay, TimeUnit unit) 方法,实现定时逻辑。而在内部的具体逻辑,调用 NettyClient 的 #start() 方法,发起连接 Netty 服务端。
又因为 NettyClient 在 #start() 方法在连接 Netty 服务端失败时,又会调用 #reconnect()方法,从而再次发起定时重连。如此循环反复,知道 Netty 客户端连接上 Netty 服务端。如下图所示:
NettyClient 重连
② 在 NettyClientHandler 中,实现 #channelInactive(ChannelHandlerContext ctx) 方法,在发现和 Netty 服务端断开时,调用 Netty Client 的 #reconnect() 方法,发起重连。代码如下:
// NettyClientHandler.java@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception { // 发起重连 nettyClient.reconnect(); // 继续触发事件 super.channelInactive(ctx);}
5.2 简单测试
① 启动 Netty Client,不要启动 Netty Server,控制台打印日志如下图:
重连失败
可以看到 Netty Client 在连接失败时,不断发起定时重连。
② 启动 Netty Server,控制台打印如下图:
重连成功
可以看到 Netty Client 成功重连上 Netty Server。
6. 心跳机制与空闲检测
在上文中,艿艿推荐胖友阅读《TCP Keepalive 机制刨根问底》文章,我们可以了解到 TCP 自带的空闲检测机制,默认是 2 小时。这样的检测机制,从系统资源层面上来说是可以接受的。
但是在业务层面,如果 2 小时才发现客户端与服务端的连接实际已经断开,会导致中间非常多的消息丢失,影响客户的使用体验。
因此,我们需要在业务层面,自己实现空闲检测,保证尽快发现客户端与服务端实际已经断开的情况。实现逻辑如下:
- 服务端发现 180 秒未从客户端读取到消息,主动断开连接。
- 客户端发现 180 秒未从服务端读取到消息,主动断开连接。
考虑到客户端和服务端之间并不是一直有消息的交互,所以我们需要增加心跳机制:
- 客户端每 60 秒向服务端发起一次心跳消息,保证服务端可以读取到消息。
- 服务端在收到心跳消息时,回复客户端一条确认消息,保证客户端可以读取到消息。
“
友情提示:
为什么是 180 秒?可以加大或者减小,看自己希望多快检测到连接异常。过短的时间,会导致心跳过于频繁,占用过多资源。
为什么是 60 秒?三次机会,确认是否心跳超时。
虽然听起来有点复杂,但是实现起来并不复杂哈。
6.1 服务端的空闲检测
在 NettyServerHandlerInitializer 中,我们添加了一个 ReadTimeoutHandler 处理器,它在超过指定时间未从对端读取到数据,会抛出 ReadTimeoutException 异常。如下图所示:
ReadTimeoutHandler
通过这样的方式,实现服务端发现 180 秒未从客户端读取到消息,主动断开连接。
6.2 客户端的空闲检测
“
友情提示:和「6.1 服务端的空闲检测」一致。
在 NettyClientHandlerInitializer 中,我们添加了一个 ReadTimeoutHandler 处理器,它在超过指定时间未从对端读取到数据,会抛出 ReadTimeoutException 异常。如下图所示:
ReadTimeoutHandler
通过这样的方式,实现客户端发现 180 秒未从服务端读取到消息,主动断开连接。
6.3 心跳机制
Netty 提供了 IdleStateHandler 处理器,提供空闲检测的功能,在 Channel 的读或者写空闲时间太长时,将会触发一个 IdleStateEvent 事件。
这样,我们只需要在 NettyClientHandler 处理器中,在接收到 IdleStateEvent 事件时,客户端向客户端发送一次心跳消息。如下图所示:
客户端心跳
- 其中,HeartbeatRequest 是心跳请求。
同时,我们在服务端项目中,创建了一个 HeartbeatRequestHandler 消息处理器,在收到客户端的心跳请求时,回复客户端一条确认消息。代码如下:
@Componentpublic class HeartbeatRequestHandler implements MessageHandler { private Logger logger = LoggerFactory