网络应用程序中普遍存在一个问题:连接假死,连接假死现象是:在某一端(服务器端|客户端)看来,底层的TCP连接已经断开,但是应用程序没有捕获到,因此会认为这个连接还存在。从TCP层面来说,只有收到四次握手数据包,或者一个RST数据包,才表示连接状态已断开;
连接假死会带来以下两大问题:
1.对于服务来说,因为每个连接都会消耗CPU和内存资源,大量的假死的连接会逐渐消耗光服务器的资源,最终导致性能逐渐下降,程序崩溃;
2.对应客户端来说,连接假死会造成发送数据超时,影响用户体验.
通常,连接假死由下面的几个原因造成.
1.应用程序出现线程阻塞,无法进行数据的读写.
2.客户端或者服务器端网络相关设备出现故障,别网卡,或者机房.....
3.公网丢包。公共网络相对内网而言,非常容易出现丢包,网络抖动等现象,如果在一段时间内用户接入的网络连接出现丢包现象,那么对于客户端来说,数据一直发送不出去,而服务端也一直收不到客户端的数据,连接一直这样耗着.
如果应用程序时面向用户的,那么公网丢包这个问题出现的概率是非常高的,对于内网来说丢包,网络抖动也会有一定概率发生;
下面我们从服务端与客户端分别来处理,需要完成服务端与客户端的闲暇假死检测和服务端与客户端的心跳检测:
(一)闲暇检测:直接上检测的代码:
========================客户端与服务器端代码一样================================
(客户端定时向服务器端发送闲暇检测|服务端定时向服务器端发送闲暇检测)
===============Netty框架为我们提交了一个实现了IdleStateHandler======================
package org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* 客户端闲暇心跳检测
*/
@Slf4j
public class ChatIMIdleStateHandler extends IdleStateHandler {
private static final int READER_IDLE_TIME = 20;
public ChatIMIdleStateHandler() {
super(READER_IDLE_TIME, 0, 0, TimeUnit.SECONDS);
}
/**
* 监听10分钟没有读或者写的时候关闭管道
* @param ctx
* @param evt
*/
@Override
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) {
log.info(READER_IDLE_TIME + " 秒内未读到数据,关闭连接");
ctx.flush();
ctx.channel().close();
}
}
下面分别在客户端与服务器端配置闲暇检测: 服务器端代码
package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.ChatIMIdleStateHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.ChatPacketCodecHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.server.ChatHeartBeatRequestHandler;
/**
* IM服务器端
*/
public class ChatCenterServer {
@SneakyThrows
public static void main(String[] args) {
// 服务器端启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
// boss主线程(父线程)
NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup();
// 工作线程(子线程)
NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();
serverBootstrap // 父子线程建立组连接
.group(bossLoopGroup, workerLoopGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) {
// 闲暇检测
channel.pipeline().addLast(new ChatIMIdleStateHandler());
channel.pipeline().addLast(ChatPacketCodecHandler.INSTANCE);
channel.pipeline().addLast(ChatHeartBeatRequestHandler.INSTANCE);
}
}).bind(8000).sync();
}
}
下面分别在客户端与服务器端配置闲暇检测: 客户端代码
package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatedcode.PacketDecoder;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatedcode.PacketEncodeDecode;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatedcode.PacketEncoder;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.ChatIMIdleStateHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.client.ChatHeartBeatResponseHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.client.ChatLoginServerResponseHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.client.ChatServerMessageResponseHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatMessageRequestPacket;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;
import java.util.Date;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
*
* <p>
* 工具主要是封装了java.util.Random对象,提供了一些产生随机数或者随机字符串的方法。随机工具类是RandomUtil,里面的方法都是静态方法。
* <p>
* 673,798 访问数
*/
@Slf4j
public class ChatCenterClient {
public static final ConcurrentHashMap<Byte, Bootstrap> bootStrapMap = new ConcurrentHashMap();
@SneakyThrows
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
// 闲暇检测
channel.pipeline().addLast(new ChatIMIdleStateHandler());
channel.pipeline().addLast(new PacketDecoder());
channel.pipeline().addLast(new PacketEncoder());
// 心跳定时器
channel.pipeline().addLast(new ChatHeartBeatResponseHandler());
}
});
connect(bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);
// 重连指令与对应客户端启动的实例映射关系
bootStrapMap.put(ChatRetryCommand.CHAT_RETRY_CONNECTION, bootstrap);
}
/**
* @param bootstrap 客户端启动器
* @param host 主机
* @param port 端口
* @param retryNum 指数退避重新连接的次数
* @author yh19166
* @deprecated 连接和重连机制,实现了指数退避重连
*/
private static void connect(Bootstrap bootstrap, String host, int port, int retryNum) {
bootstrap.connect(host, port).addListener(future -> {
// 通过future.isSuccess() 判断是否连接成功
if (future.isSuccess()) {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " -->> 连接成功....");
} else if (retryNum == ChatClientConstant.CHAT_INI_RETRY_NUM) {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " -->> 重连次数已用完,放弃连接....");
System.exit(0);
} else {
// 第几次重连
int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;
// 客户考虑重新连接的逻辑,分梯度连接......
System.out.println("第 " + order + " 连接失败......");
// 本次重连的间隔: 通过左移操作快速计算出重连时间间隔
int delay = ChatClientConstant.CHAT_STEP_RETRY_LENGTH << order;
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " -->> 连接失败,第" + order + "次重新连接....");
// 实现定时任务逻辑
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);
}
});
}
/**
* @param bootstrap 客户端启动类
* @param host 主机
* @param port 端口
* @param retryNum 指数退避重新连接的次数
* @author yh19166
* @deprecated 连接和重连机制,实现了指数退避重连
*/
@Deprecated
public static void retryConnect(Bootstrap bootstrap, String host, int port, int retryNum) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
log.info("连接服务器成功!");
Channel channel = ((ChannelFuture) future).channel();
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " -->> 连接成功....");
// 连接成功后启动控制台线程
startConsoleTask(channel);
} else if (retryNum == ChatClientConstant.CHAT_INI_RETRY_NUM) {
log.error("重连次数已用完,放弃连接!");
System.exit(0);
} else {
// 第几次重连
int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;
// 本次重连的间隔
int delay = ChatClientConstant.CHAT_STEP_RETRY_LENGTH << order;
log.error(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + ": 连接失败,第" + order + "次重新连接....");
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);
}
});
}
}
至此服务端与客户端的闲暇检测依据配置完成
(二)客户端与服务器端心跳检测
通常服务器端未收到客户端的数据,这个现象产生的原因可分为两种情况:
1,连接假死
2,非假死状态下确实没有发送数据
我们只需要排除第二种情况,那么自然就是连接假死。要排除第二种情况,我们客户定期发送数据包到服务器端,这里的数据包通常称之为心跳数据包,服务器端收到数据包后,把响应数据包发送到客户端。
===========================================================================
直接上客户端心跳数据定时发送代码:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.client;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response.ChatHeartBeatResponsePacket;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 客户端心跳检测包
*/
@Slf4j
public class ChatHeartBeatResponseHandler extends SimpleChannelInboundHandler<ChatHeartBeatResponsePacket> {
private static final int HEARTBEAT_INTERVAL = 5;
/**
* 通道处于就绪状态
*
* @param ctx 通道上下文
* @throws Exception 抛出异常信息
*/
@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()) {
ChatHeartBeatRequestPacket chatHeartBeatRequestPacket = new ChatHeartBeatRequestPacket();
chatHeartBeatRequestPacket.setRequestTime(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
ctx.writeAndFlush(chatHeartBeatRequestPacket);
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " : 客户端每间隔" + HEARTBEAT_INTERVAL + "秒向服务器端发送心跳数据包: " + ReflectionToStringBuilder.toString(chatHeartBeatRequestPacket,ToStringStyle.MULTI_LINE_STYLE));
scheduleSendHeartBeat(ctx);
} else {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " : 客户端通道已关闭,通道是否激活: " + ctx.channel().isActive());
}
}, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
}
/**
* 读取服务端返回的心跳包
* @param ctx 通道处理器上下文
* @param chatHeartBeatResponsePacket 服务端响应的心跳包
* @throws Exception 抛出异常
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ChatHeartBeatResponsePacket chatHeartBeatResponsePacket) throws Exception {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " : 客户端收到服务器端的心跳包: " + ReflectionToStringBuilder.toString(chatHeartBeatResponsePacket,ToStringStyle.MULTI_LINE_STYLE));
}
}
心跳响应数据包:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response;
import lombok.Data;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.DataPacket;
import static org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatCommand.HEARTBEAT_RESPONSE_COMMAND;
/**
* 心跳数据响应包
*/
@Data
public class ChatHeartBeatResponsePacket extends DataPacket {
private String responseTime;
@Override
public Byte getCommand() {
return HEARTBEAT_RESPONSE_COMMAND;
}
}
======================================================================
服务器端处理客户端发送的心跳包代码:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.server;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response.ChatHeartBeatResponsePacket;
/**
* 系统心跳请求数据包处理器
*/
@Slf4j
@ChannelHandler.Sharable
public class ChatHeartBeatRequestHandler extends SimpleChannelInboundHandler<ChatHeartBeatRequestPacket> {
public static final ChatHeartBeatRequestHandler INSTANCE = new ChatHeartBeatRequestHandler();
private ChatHeartBeatRequestHandler() {
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ChatHeartBeatRequestPacket chatMessageRequestPacket) {
System.out.println("服务器端收到的客户端心跳检测消息: " + ReflectionToStringBuilder.toString(chatMessageRequestPacket, ToStringStyle.MULTI_LINE_STYLE));
ChatHeartBeatResponsePacket chatHeartBeatResponsePacket = new ChatHeartBeatResponsePacket();
chatHeartBeatResponsePacket.setResponseTime(chatMessageRequestPacket.getRequestTime());
chatHeartBeatResponsePacket.setVersion(chatMessageRequestPacket.getVersion());
ctx.writeAndFlush(chatHeartBeatResponsePacket);
}
}
心跳请求数据包:
package org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request;
import lombok.Data;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.DataPacket;
import static org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatCommand.HEARTBEAT_REQUEST_COMMAND;
/**
* 心跳请求数据包
*/
@Data
public class ChatHeartBeatRequestPacket extends DataPacket {
private String requestTime;
@Override
public Byte getCommand() {
return HEARTBEAT_REQUEST_COMMAND;
}
}
至此,心跳检测,闲暇检测已完成, 闲暇检测与心跳检查配置如上客户端与服务端处理器的配置;
测试数据:
心跳检测的日志:
客户端日志:
14:28:10.304 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
14:28:10.304 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
14:28:10.304 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
14:28:10.304 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
14:28:10.321 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
14:28:10.322 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
14:28:10.322 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
2023-05-28 14:28:10.373 -->> 连接成功....
输入发送到服务器端内容:
14:28:15.448 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
14:28:15.448 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
14:28:15.448 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
14:28:15.448 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
14:28:15.464 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
14:28:15.466 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@aeadd3
2023-05-28 14:28:15.614 : 客户端每间隔5秒向服务器端发送心跳数据包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@af1a9f[
requestTime=2023-05-28 14:28:15.398
version=1
]
2023-05-28 14:28:15.874 : 客户端收到服务器端的心跳包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response.ChatHeartBeatResponsePacket@1de699a[
responseTime=2023-05-28 14:28:15.398
version=1
]
2023-05-28 14:28:20.630 : 客户端每间隔5秒向服务器端发送心跳数据包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@1b10535[
requestTime=2023-05-28 14:28:20.629
version=1
]
2023-05-28 14:28:20.631 : 客户端收到服务器端的心跳包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response.ChatHeartBeatResponsePacket@1bf5daa[
responseTime=2023-05-28 14:28:20.629
version=1
]
2023-05-28 14:28:25.631 : 客户端每间隔5秒向服务器端发送心跳数据包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@618c9d[
requestTime=2023-05-28 14:28:25.631
version=1
]
2023-05-28 14:28:25.632 : 客户端收到服务器端的心跳包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response.ChatHeartBeatResponsePacket@83a8b2[
responseTime=2023-05-28 14:28:25.631
version=1
]
2023-05-28 14:28:30.633 : 客户端每间隔5秒向服务器端发送心跳数据包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@1c49c3a[
requestTime=2023-05-28 14:28:30.633
version=1
]
2023-05-28 14:28:30.634 : 客户端收到服务器端的心跳包: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.response.ChatHeartBeatResponsePacket@9fd965[
responseTime=2023-05-28 14:28:30.633
version=1
]
2023-05-28 14:28:35.634 : 客户端每间隔5秒向服务器端发送心跳数据包
===========================================================================
服务器端日志:
14:28:02.672 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
14:28:02.672 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
14:28:02.672 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
14:28:15.620 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
14:28:15.620 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
14:28:15.620 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
14:28:15.620 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
14:28:15.638 [nioEventLoopGroup-3-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
14:28:15.641 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@14711d0
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@1e1c50[
requestTime=2023-05-28 14:28:15.398
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@1fc82e3[
requestTime=2023-05-28 14:28:20.629
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@44fd87[
requestTime=2023-05-28 14:28:25.631
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@604ff4[
requestTime=2023-05-28 14:28:30.633
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@25e8dd[
requestTime=2023-05-28 14:28:35.634
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@9fd328[
requestTime=2023-05-28 14:28:40.635
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@a1de30[
requestTime=2023-05-28 14:28:45.637
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@12d255d[
requestTime=2023-05-28 14:28:50.638
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@168f248[
requestTime=2023-05-28 14:28:55.639
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@16a0aee[
requestTime=2023-05-28 14:29:00.640
version=1
]
服务器端收到的客户端心跳检测消息: org.jy.sso.websocket.stomp.push.netty.chat.system.chatprotocol.request.ChatHeartBeatRequestPacket@174b2fd[
requestTime=2023-05-28 14:29:05.641
version=1
]
================================闲暇检测日志=============================
服务器端检测到20秒没数据,就会关闭连接通道:
15:25:19.094 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: 00:50:56:ff:fe:c0:00:01 (auto-detected)
15:25:19.094 [main] DEBUG io.netty.util.internal.ThreadLocalRandom - -Dio.netty.initialSeedUniquifier: 0x8553a5744ebfc459
15:25:19.109 [main] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.level: simple
15:25:19.109 [main] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.maxRecords: 4
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numHeapArenas: 2
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numDirectArenas: 2
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.pageSize: 8192
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxOrder: 11
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.chunkSize: 16777216
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.tinyCacheSize: 512
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
15:25:19.140 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
15:25:19.156 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
15:25:19.156 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
15:25:19.156 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
15:25:46.335 [nioEventLoopGroup-3-1] INFO org.jy.sso.websocket.stomp.push.netty.chat.system.chathandler.ChatIMIdleStateHandler - 20 秒内未读到数据,关闭连接
客户端再向服务器端发送消息:则通道已经关闭,无法发送
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numHeapArenas: 2
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numDirectArenas: 2
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.pageSize: 8192
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxOrder: 11
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.chunkSize: 16777216
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.tinyCacheSize: 512
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
15:25:26.257 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
15:25:26.271 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
15:25:26.272 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
15:25:26.272 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
2023-05-28 15:25:26.321 -->> 连接成功....
输入发送到服务器端内容:
15:25:46.355 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
15:25:46.355 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
15:25:46.355 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
15:25:46.355 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
15:25:46.373 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
15:25:46.376 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@f95dbb
4332432432222222222222222222222222222222222
[当前通道已经关闭:]ChatCenterClient.startConsoleTask channel isOpen: false, isActive: false
输入发送到服务器端内容: