netty(八)WebSocket心跳
在
tcp心跳模型,
TCP新手误区–心跳的意义
与
netty(六)WebSocket实践
的基础上:
主要修改:
TextWebSocketFrameHandler文件:
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<String> {
private static final String SPLIT = ":\t";
private static final ChannelGroup group = new DefaultChannelGroup("ChannelGroups", GlobalEventExecutor.INSTANCE);
private static final ConcurrentHashMap<String, Channel> userChannel = new ConcurrentHashMap<String, Channel>();
private static String getOnine() {
。。。。
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
// 移除性能更加
ctx.pipeline().remove(HttpRequestHandler.class);
String userName = ctx.channel().attr(HttpRequestHandler.key).get();
Channel old = userChannel.put(userName, ctx.channel());
if(old != null) {
group.remove(old);
System.out.println(old.attr(HttpRequestHandler.key).get() + SPLIT + "[切换设备]");
ChannelFuture channelFuture = old.writeAndFlush("您的账户在其它地方登陆");
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
ctx.writeAndFlush("-=====登录成功=====-");
String up = userName + SPLIT + "[上线]";
System.out.println(up);
group.writeAndFlush(up);
group.add(ctx.channel());
String online = getOnine();
System.out.println(online);
group.writeAndFlush(online);
}else if (evt instanceof IdleStateEvent) {
// 2*4+1 s内读空闲时,关掉连接,表示客户端不可见了
IdleStateEvent evnet = (IdleStateEvent) evt;
if (evnet.state().equals(IdleState.READER_IDLE)) {
ctx.close();
}
} else {
super.userEventTriggered(ctx, evt);
}
}
@Override
public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
// 这一段也是错的,书上因为是直接write,因此要retain,这里已经新建了String实例,所以retain会造成内存泄漏
// String send = ctx.channel().attr(HttpRequestHandler.key).get() + SPLIT + msg.retain().text();
// System.out.println(send);
// group.writeAndFlush(new TextWebSocketFrame(send));
if("HeartBeat".equals(msg)) {
// 心跳只给自己发
ctx.writeAndFlush(msg);
} else {
// 聊天发给所有人
String send = ctx.channel().attr(HttpRequestHandler.key).get() + SPLIT + msg;
System.out.println(send);
group.writeAndFlush(send);
}
}
WebSocketServerInitializer文件:
// 读空闲5秒激发 ch.pipeline().addLast(new IdleStateHandler(9, 0, 0, TimeUnit.SECONDS));
前端:
function WSonClose() { ws = null; alert("连接关闭。"); }; var heart = null; var receiveTimeOut = 4000; var lastTime = false; function WSonMessage(event) { $("#board").val(event.data + "\n" + $("#board").val()); // 收到消息保留4秒,4秒后消灭 lastTime = true; setTimeout(function(){ lastTime = false; }, receiveTimeOut); // 收到消息后4秒发送一次心跳,要求服务器channel一旦连接就发一条消息过来 clearTimeout(heart); heart = setTimeout(function(){ ws.send("HeartBeat"); }, receiveTimeOut); // 5 秒后查看 checkHeart(); }; function checkHeart() { setTimeout(function(){ // 5s后查看 if(!lastTime) { alert('与服务器断开连接了'); ws.close(); } }, receiveTimeOut + 1000) }
实践:
2台服务器,A放前端 B放后端
客户端发4次心跳后,拔掉网线
服务端5秒后下线
客户端:
前端代码有巨大问题:改为:
var lastTime = new Date().valueOf(); function WSonOpen() { setInterval(function(){ console.log('send - ' + new Date().valueOf()); ws.send("HeartBeat"); }, 4000); setTimeout(function(){ setInterval(function(){ var now = new Date().valueOf(); console.log('chek - ' + now); if((now - lastTime) > 8000) { alert('与服务器断开连接了'); ws.close(); } }, 4000); }, 1000); }; function WSonMessage(event) { lastTime = new Date().valueOf(); console.log('get - ' + lastTime); if(event.data == 'HeartBeat') $("#heart").val("=" + $("#heart").val()); else if(-1 != event.data.indexOf("[在线用户]")) $("#online").val(event.data); else $("#board").val(event.data + "\n" + $("#board").val()); };
经验值服务端9秒未收到数据,断开连接,客户端4s发一次ping,客户端9秒内,连续2次未收到pong,切断连接