一、网络问题
1.连接假死:
连接假死的现象是:在某一端看来,底层的Tcp连接已经断开,但是应用程序没有捕获到,会认为这条连接仍然是存在的。从TCP层面来说,只有收到四次握手数据包或者一个RST数据包才可以表示连接状态已经断开
2.连接假死带来的问题:
对于服务端来说,每隔连接都会消耗cpu和内存资源,大量的假死的连接会消耗完服务器的资源最终导致性能逐渐下降,程序崩溃
对于客户端来说,连接假死会造成发送数据超时影响用户体验
3.造成连接假死的原因:
应用程序出现线程阻塞无法进行数据的读写
客户端或者服务端网络相关的设备出现故障
公网丢包
4.服务端空闲检测:对于服务端来说,客户端的连接如果出现假死,那么服务端将无法收到客户端的数据,反之,这个连接还是活的,服务端对于连接假死的应对策略是空闲检测
空闲检测:每隔一段时间,检测这段时间内是否有数据的读写,即服务端只需要检测一段时间内是否收到过客户端发来的数据
Netty自带的IdleStateHandler的类来定义检测到假死连接之后的逻辑:
public class IMIdleStateHandler extends IdleStateHandler {
private static final int READER_IDLE_TIME = 15;
public IMIdleStateHandler() {
super(READER_IDLE_TIME, 0, 0, TimeUnit.SECONDS);
}
@Override
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e
vt) {
System.out.println(READER_IDLE_TIME + "秒内未读到数据,关
闭连接");
ctx.channel().close();
}
}
其中第一个参数是读空闲时间,第二个参数是写空闲时间,第三个参数是读写空闲时间,最后一个参数是,如果在它们定义的那些时间里都没有读或者写到数据就认为连接假死
如果出现假死回调用channelIdle()方法,然后将这个Handler插到服务端Pipeline之前
但是,现实问题是在一段时间内没有读到客户端的数据不一定就是连接假死而有可能是客户端确实没有数据发送过来,但是连接是正常的
5.客户端定时发送心跳数据包:
服务端在一段时间没没有收到客户端的数据,这个现象产生的原因可能是:
连接假死
非假死状态下确实没有发送数据
可以在客户端定期发送数据包到服务端(心跳数据包)来排除第二种情况
示例代码:
public class HeartBeatTimerHandler extends ChannelInboundHandlerAdap
ter {
private static final int HEARTBEAT_INTERVAL = 5;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
scheduleSendHeartBeat(ctx);
super.channelActive(ctx);
}
private void scheduleSendHeartBeat(ChannelHandlerContext ctx) {
ctx.executor().schedule(() -> {
if (ctx.channel().isActive()) {
ctx.writeAndFlush(new HeartBeatRequestPacket());
scheduleSendHeartBeat(ctx);
}
}, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
}
}
ctx.executor()方法返回的是当前Channel绑定的NIO线程,NIO线程有一个schedule()方法类似于jdk的延时任务机制,可以隔一段时间执行一个任务。上述代码实现了每隔5s向服务端发送一个心跳数据包,这个间隔时间通常要比服务端的空闲时间的一半短,一般可以定义为空闲检测时间的三分之一
6.服务端回复心跳与客户端空闲检测:
bootstrap
.handler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) {
// 空闲检测
ch.pipeline().addLast(new IMIdleStateHandler());
ch.pipeline().addLast(new Spliter());
// ...
如果在一段时间内客户端没有收到服务端发来的数据包,则可以判定这个连接为假死状态,因此,服务端的Pipeline中需要再加上一个Handler,这个处理无需登录,所以一般会将这个Handler放在AuthHandler前面
HeartBeatRequestHandler:
@ChannelHandler.Sharable
public class HeartBeatRequestHandler extends
SimpleChannelInboundHandler<HeartBeatRequestPacket> {
public static final HeartBeatRequestHandler INSTANCE = new HeartBea
tRequestHandler();
private HeartBeatRequestHandler() {
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HeartBeatReq
uestPacket
requestPacket) {
ctx.writeAndFlush(new HeartBeatResponsePacket());
}
}