实现基于websocket的心跳检测
目录
在之前的handler上面,添加userTriggerEvent方法
此方法可以让使用者像使用netty那样触发事件回调
因为websocket握手成功之后,需要发送一个事件到管道里面,然后handler从上到下的方式触发userTriggerEvent方法,通过订阅此方法,实现心跳逻辑
websocket心跳检测handler实现
package com.lhstack.bio.handler.websocket;
import com.lhstack.bio.channel.ChannelHandlerContext;
import com.lhstack.bio.channel.SimpleChannelAdapterHandler;
import com.lhstack.bio.codec.websocket.frame.PingWebSocketFrame;
import com.lhstack.bio.codec.websocket.frame.PongWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
/**
* websocket心跳检测程序
* @author lhstack
*/
public class WebsocketMessageIdleStateHandler extends SimpleChannelAdapterHandler<PongWebSocketFrame> {
private static final Logger LOGGER = LoggerFactory.getLogger(WebsocketMessageIdleStateHandler.class);
private final int maxFailCount;
private final int maxWaitTime;
private int failCount;
private long preRequestTime;
public WebsocketMessageIdleStateHandler(){
this(3,30);
}
public WebsocketMessageIdleStateHandler(int maxFailCount,int maxWaitTime){
this.maxFailCount = maxFailCount;
this.maxWaitTime = maxWaitTime;
}
@Override
public void userTriggerEvent(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt == WebSocketEvent.ON_OPEN){
ctx.channel().inEventLoop()
.schedule(new Runnable() {
@Override
public void run() {
try{
if(failCount >= maxFailCount){
ctx.close();
return ;
}else if(System.currentTimeMillis() / 1000 - preRequestTime >= maxWaitTime){
this.failCount ++;
return ;
}
LOGGER.debug("websocket server ping client {},date {}",ctx.channel().id(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
ctx.writeAndFlush(new PingWebSocketFrame());
ctx.channel().inEventLoop().schedule(this,25,TimeUnit.SECONDS);
}catch (Exception e){
}
}
},25, TimeUnit.SECONDS);
LOGGER.debug("websocket server ping client {},date {}",ctx.channel().id(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
ctx.writeAndFlush(new PingWebSocketFrame());
}
}
@Override
public void channelRead0(ChannelHandlerContext ctx, PongWebSocketFrame msg) throws Exception {
LOGGER.debug("client {} pong server,date {}",ctx.channel().id(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
this.preRequestTime = System.currentTimeMillis() / 1000;
this.failCount = 0;
}
}
WebSocketEvent对象
package com.lhstack.bio.handler.websocket;
/**
* @author lhstack
* websocket 事件
*/
public enum WebSocketEvent {
/**
* websocket事件
*/
ON_OPEN,ON_MESSAGE,ON_CLOSE
}
Server端实现
public static void websocketServer() throws Exception {
ServerBootStrap serverBootStrap = new ServerBootStrap(8080);
serverBootStrap.group(Executors.newFixedThreadPool(200),new DefaultEventLoopGroup())
.handler(new ChannelInitializeHandler() {
@Override
public void initializer(Channel ch) throws Exception {
ch.pipeline()
.addLast(new HttpServerMessageCodec())
.addLast(new WebsocketMessageHandler("/test", new SimpleChannelAdapterHandler<TextWebSocketFrame>() {
@Override
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println(msg.text());
ctx.writeAndFlush(new TextWebSocketFrame("this is test message"));
}
@Override
public void channelRemove(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx);
}
@Override
public void exceptionCatch(ChannelHandlerContext ctx, Throwable throwable) throws Exception {
System.out.println(ctx);
throwable.printStackTrace();
}
}))
.addLast(new WebsocketMessageHandler("/hello", new SimpleChannelAdapterHandler<TextWebSocketFrame>() {
@Override
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println(msg.text());
ctx.writeAndFlush(new TextWebSocketFrame("this is hello world message"));
}
@Override
public void channelRemove(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx);
}
@Override
public void exceptionCatch(ChannelHandlerContext ctx, Throwable throwable) throws Exception {
System.out.println(ctx);
throwable.printStackTrace();
}
}))
.addLast(new WebsocketMessageIdleStateHandler())
.addLast(new SimpleChannelAdapterHandler<HttpRequest>() {
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
System.out.println(msg);
ctx.writeAndFlush(new HttpResponse("HTTP/1.1",200,Unpooled.wrappedBuffer("hello world".getBytes(StandardCharsets.UTF_8))));
}
});
}
}).start();
心跳检测的handler放到websocket握手handler之后即可
使用客户端连接服务端,看是否收到心跳包
第一次握手成功之后,会触发一次心跳检测
然后每隔25s触发一次心跳检测
每次发送心跳包时,会判断上一次请求的时间,如果大于maxWaitTime,则失败次数加一
当心跳失败次数大于等于maxFailCount,则会关闭客户端
下一期实现多客户端群聊功能