netty实现长连接心跳检

阅读本文前,你必须了解netty相关的一些基础知识,了解怎么使用netty创建服务器端及客户端,了解一些编解码技术来避免粘包拆包问题,推荐李林锋的《netty权威指南》。

 

主要逻辑

使用netty实现长连接,主要靠心跳来维持服务器端及客户端连接。

实现的逻辑主要是:

服务器端方面

 

1, 服务器在网络空闲操作一定时间后,服务端失败心跳计数器加1。

2, 如果收到客户端的ping心跳包,则清零失败心跳计数器,如果连续n次未收到客户端的ping心跳包,则关闭链路,释放资源,等待客户端重连。

 

客户端方面

 

1, 客户端网络空闲在一定时间内没有进行写操作时,则发送一个ping心跳包。

2, 如果服务器端未在发送下一个心跳包之前回复pong心跳应答包,则失败心跳计数器加1。

3, 如果客户端连续发送n(此处根据具体业务进行定义)次ping心跳包,服务器端均未回复pong心跳应答包,则客户端断开连接,间隔一定时间进行重连操作,直至连接服务器成功。

环境:netty5,tomcat7,jdk7,myeclipse

服务器端心跳处理类:

 

public class HeartBeatRespHandler extends ChannelInboundHandlerAdapter { 
	private  final Logger log=Logger.getLogger(HeartBeatRespHandler.class);
	   //线程安全心跳失败计数器
	   private AtomicInteger unRecPingTimes = new AtomicInteger(1);
	   @Override
	   public void channelRead(ChannelHandlerContext ctx, Object msg)  
	            throws Exception {  
		   NettyMessageProto message = (NettyMessageProto)msg;
		   unRecPingTimes = new AtomicInteger(1);
		   //接收客户端心跳信息
	       if(message.getHeader() != null  && message.getHeader().getType() == Constants.MSGTYPE_HEARTBEAT_REQUEST){
	    	    //清零心跳失败计数器
	    	    log.info("server receive client"+ctx.channel().attr(SysConst.SERIALNO_KEY)+" ping msg :---->"+message);
	        	//接收客户端心跳后,进行心跳响应
	        	NettyMessageProto replyMsg = buildHeartBeat();
	        	ctx.writeAndFlush(replyMsg);
	        }else{
	        	ctx.fireChannelRead(msg);
	        }
	    }
	   
	   
	    /**
	     * 事件触发器,该处用来处理客户端空闲超时,发送心跳维持连接。
	     */
	    @Override
	    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {  
 	        if (evt instanceof IdleStateEvent) {  
	            IdleStateEvent event = (IdleStateEvent) evt;  
	            if (event.state() == IdleState.READER_IDLE) {  
	                /*读超时*/  
	            	log.info("===服务器端===(READER_IDLE 读超时)");
	                unRecPingTimes.getAndIncrement(); 
	              //客户端未进行ping心跳发送的次数等于3,断开此连接
	                if(unRecPingTimes.intValue() == 3){  
	                	
	                	  ctx.disconnect();
	                	  System.out.println("此客户端连接超时,服务器主动关闭此连接....");
	                	  log.info("此客户端连接超时,服务器主动关闭此连接....");
	                } 
	            } else if (event.state() == IdleState.WRITER_IDLE) {  
	                /*服务端写超时*/     
	            	log.info("===服务器端===(WRITER_IDLE 写超时)");
	                
	            } else if (event.state() == IdleState.ALL_IDLE) {  
	                /*总超时*/  
	            	log.info("===服务器端===(ALL_IDLE 总超时)");  
	            }  
	        }  
	    }
	    
	   
	   /**
	    * 创建心跳响应消息
	    * @return
	    */
	   private NettyMessageProto buildHeartBeat(){
		   HeaderProto header = HeaderProto.newBuilder().setType(Constants.MSGTYPE_HEARTBEAT_RESPONSE).build();
		   NettyMessageProto message =NettyMessageProto.newBuilder().setHeader(header).build();
		   return message;
	   }


客户端心跳处理类:

 

 

public class HeartBeatReqHandler extends ChannelHandlerAdapter {
	private  final Logger log=Logger.getLogger(HeartBeatReqHandler.class);
	
	//线程安全心跳失败计数器
	private AtomicInteger unRecPongTimes = new AtomicInteger(1);
	
    public void channelRead(ChannelHandlerContext ctx, Object msg)  
            throws Exception {  
        NettyMessageProto message = (NettyMessageProto)msg;  
        //服务器端心跳回复
        if(message.getHeader() != null  && message.getHeader().getType() == Constants.MSGTYPE_HEARTBEAT_RESPONSE){
        	//如果服务器进行pong心跳回复,则清零失败心跳计数器
        	unRecPongTimes = new AtomicInteger(1);
        	log.debug("client receive server pong msg :---->"+message);
        }else{
        	ctx.fireChannelRead(msg);
        }
    }  
    
    /**
     * 事件触发器,该处用来处理客户端空闲超时,发送心跳维持连接。
     */
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {  
        if (evt instanceof IdleStateEvent) {  
            IdleStateEvent event = (IdleStateEvent) evt;  
            if (event.state() == IdleState.READER_IDLE) {  
                /*读超时*/  
                log.info("===客户端===(READER_IDLE 读超时)");
            } else if (event.state() == IdleState.WRITER_IDLE) {  
                /*客户端写超时*/     
            	log.info("===客户端===(WRITER_IDLE 写超时)");
                unRecPongTimes.getAndIncrement();  
                //服务端未进行pong心跳响应的次数小于3,则进行发送心跳,否则则断开连接。
                if(unRecPongTimes.intValue() < 3){  
                	//发送心跳,维持连接
                    ctx.channel().writeAndFlush(buildHeartBeat()) ; 
                    log.info("客户端:发送心跳");
                }else{  
                    ctx.channel().close();  
                }  
            } else if (event.state() == IdleState.ALL_IDLE) {  
                /*总超时*/  
            	log.info("===客户端===(ALL_IDLE 总超时)");  
            }  
        }  
    }
        
    private NettyMessageProto buildHeartBeat(){
    	HeaderProto header = HeaderProto.newBuilder().setType(Constants.MSGTYPE_HEARTBEAT_REQUEST).build();
    	NettyMessageProto  message = NettyMessageProto.newBuilder().setHeader(header).build();
		return message;
	}
    
    /**
     * 异常处理
     */
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception{
    	ctx.fireExceptionCaught(cause);
    }

}

 

 

 

 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

你可能感兴趣的:(netty实现长连接心跳检)