Netty 4.0 實現心跳檢測和斷線重連

一 實現心跳檢測 
原理:當服務端每隔一段時間就會向客戶端發送心跳包,客戶端收到心跳包後一樣也會回一個心跳包給服務端 
通常狀況下,客戶端與服務端在指定時間內沒有任何讀寫請求,就會認爲鏈接是idle(空閒的)的。此時,客戶端須要向服務端發送心跳消息,來維持服務端與客戶端的連接。那麼怎麼判斷客戶端在指定時間裏沒有任何讀寫請求呢?netty中爲咱們提供一個特別好用的IdleStateHandler來幹這個苦差事! 

在服務端工做線程中添加: 
java

Java代碼  收藏代碼android

  1. arg0.pipeline().addLast("ping"new IdleStateHandler(251510,TimeUnit.SECONDS));  bootstrap



這個處理器,它的做用就是用來檢測客戶端的讀取超時的,該類的第一個參數是指定讀操做空閒秒數,第二個參數是指定寫操做的空閒秒數,第三個參數是指定讀寫空閒秒數,當有操做操做超出指定空閒秒數時,便會觸發UserEventTriggered事件。因此咱們只須要在本身的handler中截獲該事件,而後發起相應的操做便可(好比說發起心跳操做)。如下是咱們自定義的handler中的代碼: 
服務器

Java代碼  收藏代碼socket

  1. /** ide

  2.      * 一段時間未進行讀寫操做 回調 函數

  3.      */  oop

  4.     @Override  spa

  5.     public void userEventTriggered(ChannelHandlerContext ctx, Object evt)  .net

  6.             throws Exception {  

  7.         // TODO Auto-generated method stub  

  8.         super.userEventTriggered(ctx, evt);  

  9.   

  10.         if (evt instanceof IdleStateEvent) {  

  11.   

  12.             IdleStateEvent event = (IdleStateEvent) evt;  

  13.               

  14.             if (event.state().equals(IdleState.READER_IDLE)) {  

  15.                 //未進行讀操做  

  16.                 System.out.println("READER_IDLE");  

  17.                 // 超時關閉channel  

  18.                  ctx.close();  

  19.   

  20.             } else if (event.state().equals(IdleState.WRITER_IDLE)) {  

  21.                   

  22.   

  23.             } else if (event.state().equals(IdleState.ALL_IDLE)) {  

  24.                 //未進行讀寫  

  25.                 System.out.println("ALL_IDLE");  

  26.                 // 發送心跳消息  

  27.                 MsgHandleService.getInstance().sendMsgUtil.sendHeartMessage(ctx);  

  28.                   

  29.             }  

  30.   

  31.         }  

  32.     }  



也就是說 服務端在10s內未進行讀寫操做,就會向客戶端發送心跳包,客戶端收到心跳包後當即回覆心跳包給服務端,此時服務端就進行了讀操做,也就不會觸發IdleState.READER_IDLE(未讀操做狀態),若客戶端異常掉線了,並不能響應服務端發來的心跳包,在25s後就會觸發IdleState.READER_IDLE(未讀操做狀態),此時服務器就會將通道關閉 

客戶端代碼略 


二 客戶端實現斷線重連 
原理當客戶端鏈接服務器時 

Java代碼  收藏代碼

  1. bootstrap.connect(new InetSocketAddress(  

  2.                     serverIP, port));  


會返回一個ChannelFuture的對象,咱們對這個對象進行監聽 
代碼以下: 

Java代碼  收藏代碼

  1. import android.os.Handler;  

  2. import android.os.HandlerThread;  

  3. import android.os.Message;  

  4. import android.util.Log;  

  5.   

  6. import com.ld.qmwj.Config;  

  7. import com.ld.qmwj.MyApplication;  

  8.   

  9. import java.net.InetSocketAddress;  

  10. import java.nio.charset.Charset;  

  11. import java.util.concurrent.TimeUnit;  

  12.   

  13. import io.netty.bootstrap.Bootstrap;  

  14. import io.netty.buffer.ByteBuf;  

  15. import io.netty.buffer.Unpooled;  

  16. import io.netty.channel.ChannelFuture;  

  17. import io.netty.channel.ChannelFutureListener;  

  18. import io.netty.channel.ChannelInitializer;  

  19. import io.netty.channel.ChannelOption;  

  20. import io.netty.channel.nio.NioEventLoopGroup;  

  21. import io.netty.channel.socket.SocketChannel;  

  22. import io.netty.channel.socket.nio.NioSocketChannel;  

  23. import io.netty.handler.codec.DelimiterBasedFrameDecoder;  

  24. import io.netty.handler.codec.string.StringDecoder;  

  25. import io.netty.handler.codec.string.StringEncoder;  

  26.   

  27. /** 

  28.  * Created by zsg on 2015/11/21. 

  29.  */  

  30. public class MyClient implements Config {  

  31.     private static Bootstrap bootstrap;  

  32.     private static ChannelFutureListener channelFutureListener = null;  

  33.   

  34.     public MyClient() {  

  35.   

  36.     }  

  37.   

  38.   

  39.     // 初始化客戶端  

  40.     public static void initClient() {  

  41.   

  42.         NioEventLoopGroup group = new NioEventLoopGroup();  

  43.   

  44.         // Client服務啓動器 3.x的ClientBootstrap  

  45.         // 改成Bootstrap,且構造函數變化很大,這裏用無參構造。  

  46.         bootstrap = new Bootstrap();  

  47.         // 指定EventLoopGroup  

  48.         bootstrap.group(group);  

  49.         // 指定channel類型  

  50.         bootstrap.channel(NioSocketChannel.class);  

  51.         // 指定Handler  

  52.         bootstrap.handler(new ChannelInitializer<SocketChannel>() {  

  53.             @Override  

  54.             protected void initChannel(SocketChannel ch) throws Exception {  

  55.                 // 建立分隔符緩衝對象  

  56.                 ByteBuf delimiter = Unpooled.copiedBuffer("#"  

  57.                         .getBytes());  

  58.                 // 當達到最大長度仍沒找到分隔符 就拋出異常  

  59.                 ch.pipeline().addLast(  

  60.                         new DelimiterBasedFrameDecoder(10000truefalse, delimiter));  

  61.                 // 將消息轉化成字符串對象 下面的到的消息就不用轉化了  

  62.                 //解碼  

  63.                 ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));  

  64.                 ch.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));  

  65.                 ch.pipeline().addLast(new MyClientHandler());  

  66.             }  

  67.         });  

  68.         //設置TCP協議的屬性  

  69.         bootstrap.option(ChannelOption.SO_KEEPALIVE, true);  

  70.         bootstrap.option(ChannelOption.TCP_NODELAY, true);  

  71.         bootstrap.option(ChannelOption.SO_TIMEOUT, 5000);  

  72.   

  73.         channelFutureListener = new ChannelFutureListener() {  

  74.             public void operationComplete(ChannelFuture f) throws Exception {  

  75.                 //  Log.d(Config.TAG, "isDone:" + f.isDone() + "     isSuccess:" + f.isSuccess() +  

  76.                 //          "     cause" + f.cause() + "        isCancelled" + f.isCancelled());  

  77.   

  78.                 if (f.isSuccess()) {  

  79.                     Log.d(Config.TAG, "從新鏈接服務器成功");  

  80.   

  81.                 } else {  

  82.                     Log.d(Config.TAG, "從新鏈接服務器失敗");  

  83.                     //  3秒後從新鏈接  

  84.                     f.channel().eventLoop().schedule(new Runnable() {  

  85.                         @Override  

  86.                         public void run() {  

  87.                             doConnect();  

  88.                         }  

  89.                     }, 3, TimeUnit.SECONDS);  

  90.                 }  

  91.             }  

  92.         };  

  93.   

  94.     }  

  95.   

  96.     //  鏈接到服務端  

  97.     public static void doConnect() {  

  98.         Log.d(TAG, "doConnect");  

  99.         ChannelFuture future = null;  

  100.         try {  

  101.             future = bootstrap.connect(new InetSocketAddress(  

  102.                     serverIP, port));  

  103.             future.addListener(channelFutureListener);  

  104.   

  105.         } catch (Exception e) {  

  106.             e.printStackTrace();  

  107.             //future.addListener(channelFutureListener);  

  108.             Log.d(TAG, "關閉鏈接");  

  109.         }  

  110.   

  111.     }  

  112.   

  113. }  



監聽到鏈接服務器失敗時,會在3秒後從新鏈接(執行doConnect方法) 

這還不夠,當客戶端掉線時要進行從新鏈接 
在咱們本身定義邏輯處理的Handler中 

Java代碼  收藏代碼

  1. @Override  

  2.     public void channelInactive(ChannelHandlerContext ctx) throws Exception {  

  3.         Log.d(Config.TAG, "與服務器斷開鏈接服務器");  

  4.         super.channelInactive(ctx);  

  5.         MsgHandle.getInstance().channel = null;  

  6.   

  7.         //從新鏈接服務器  

  8.         ctx.channel().eventLoop().schedule(new Runnable() {  

  9.             @Override  

  10.             public void run() {  

  11.                 MyClient.doConnect();  

  12.             }  

  13.         }, 2, TimeUnit.SECONDS);  

  14.         ctx.close();  

  15.     }  

相關文章
相關標籤/搜索