作者 crossoverJie

:sparkles: Introducing new features.服务端心跳检测

... ... @@ -24,8 +24,8 @@ public class CIMClientHandleInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
//60 秒没发送消息 将IdleStateHandler 添加到 ChannelPipeline 中
.addLast(new IdleStateHandler(0, 60, 0))
//45 秒没发送消息 将IdleStateHandler 添加到 ChannelPipeline 中
.addLast(new IdleStateHandler(0, 45, 0))
//心跳解码
//.addLast(new HeartbeatEncode())
... ...
package com.crossoverjie.cim.server.config;
import com.crossoverjie.cim.common.constant.Constants;
import com.crossoverjie.cim.common.protocol.CIMRequestProto;
import okhttp3.OkHttpClient;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -39,4 +41,19 @@ public class BeanConfig {
.retryOnConnectionFailure(true);
return builder.build();
}
/**
* 创建心跳单例
* @return
*/
@Bean(value = "heartBeat")
public CIMRequestProto.CIMReqProtocol heartBeat() {
CIMRequestProto.CIMReqProtocol heart = CIMRequestProto.CIMReqProtocol.newBuilder()
.setRequestId(0L)
.setReqMsg("ping")
.setType(Constants.CommandType.PING)
.build();
return heart;
}
}
... ...
... ... @@ -8,10 +8,10 @@ import com.crossoverjie.cim.common.protocol.CIMRequestProto;
import com.crossoverjie.cim.server.config.AppConfiguration;
import com.crossoverjie.cim.server.util.SessionSocketHolder;
import com.crossoverjie.cim.server.util.SpringBeanFactory;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
... ... @@ -31,16 +31,52 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
private final static Logger LOGGER = LoggerFactory.getLogger(CIMServerHandle.class);
private final MediaType mediaType = MediaType.parse("application/json");
/**
* 取消绑定
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
CIMUserInfo userInfo = SessionSocketHolder.getUserId((NioSocketChannel) ctx.channel());
LOGGER.info("用户[{}]下线",userInfo.getUserName());
SessionSocketHolder.remove((NioSocketChannel) ctx.channel());
userOffLine(userInfo, (NioSocketChannel) ctx.channel());
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
if (idleStateEvent.state() == IdleState.READER_IDLE) {
//向客户端发送消息
CIMRequestProto.CIMReqProtocol heartBeat = SpringBeanFactory.getBean("heartBeat",
CIMRequestProto.CIMReqProtocol.class);
ctx.writeAndFlush(heartBeat).addListeners(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
//下线客户端
CIMUserInfo userInfo = SessionSocketHolder.getUserId((NioSocketChannel) future.channel());
userOffLine(userInfo, (NioSocketChannel) future.channel());
}
}
});
}
}
super.userEventTriggered(ctx, evt);
}
/**
* 用户下线
* @param userInfo
* @param channel
* @throws IOException
*/
private void userOffLine(CIMUserInfo userInfo, NioSocketChannel channel) throws IOException {
LOGGER.info("用户[{}]下线", userInfo.getUserName());
SessionSocketHolder.remove(channel);
SessionSocketHolder.removeSession(userInfo.getUserId());
//清除路由关系
... ... @@ -49,6 +85,7 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
/**
* 清除路由关系
*
* @param userInfo
* @throws IOException
*/
... ... @@ -71,7 +108,7 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
}finally {
} finally {
response.body().close();
}
}
... ... @@ -81,20 +118,19 @@ public class CIMServerHandle extends SimpleChannelInboundHandler<CIMRequestProto
protected void channelRead0(ChannelHandlerContext ctx, CIMRequestProto.CIMReqProtocol msg) throws Exception {
LOGGER.info("收到msg={}", msg.toString());
if (msg.getType() == Constants.CommandType.LOGIN){
if (msg.getType() == Constants.CommandType.LOGIN) {
//保存客户端与 Channel 之间的关系
SessionSocketHolder.put(msg.getRequestId(),(NioSocketChannel)ctx.channel()) ;
SessionSocketHolder.saveSession(msg.getRequestId(),msg.getReqMsg());
LOGGER.info("客户端[{}]上线成功",msg.getReqMsg());
SessionSocketHolder.put(msg.getRequestId(), (NioSocketChannel) ctx.channel());
SessionSocketHolder.saveSession(msg.getRequestId(), msg.getReqMsg());
LOGGER.info("客户端[{}]上线成功", msg.getReqMsg());
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (CIMException.isResetByPeer(cause.getMessage())){
if (CIMException.isResetByPeer(cause.getMessage())) {
return;
}
... ...
... ... @@ -8,6 +8,7 @@ import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.timeout.IdleStateHandler;
/**
* Function:
... ... @@ -24,6 +25,8 @@ public class CIMServerInitializer extends ChannelInitializer<Channel> {
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
//45 秒没有向客户端发送消息就发生心跳
.addLast(new IdleStateHandler(45, 0, 0))
// google Protobuf 编解码
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(CIMRequestProto.CIMReqProtocol.getDefaultInstance()))
... ...