一 實現心跳檢測
原理:當服務端每隔一段時間就會向客戶端發送心跳包,客戶端收到心跳包後一樣也會回一個心跳包給服務端
通常狀況下,客戶端與服務端在指定時間內沒有任何讀寫請求,就會認爲鏈接是idle(空閒的)的。此時,客戶端須要向服務端發送心跳消息,來維持服務端與客戶端的連接。那麼怎麼判斷客戶端在指定時間裏沒有任何讀寫請求呢?netty中爲咱們提供一個特別好用的IdleStateHandler來幹這個苦差事!
在服務端工做線程中添加:
java
arg0.pipeline().addLast("ping", new IdleStateHandler(25, 15, 10,TimeUnit.SECONDS)); bootstrap
這個處理器,它的做用就是用來檢測客戶端的讀取超時的,該類的第一個參數是指定讀操做空閒秒數,第二個參數是指定寫操做的空閒秒數,第三個參數是指定讀寫空閒秒數,當有操做操做超出指定空閒秒數時,便會觸發UserEventTriggered事件。因此咱們只須要在本身的handler中截獲該事件,而後發起相應的操做便可(好比說發起心跳操做)。如下是咱們自定義的handler中的代碼:
服務器
/** ide
* 一段時間未進行讀寫操做 回調 函數
*/ oop
@Override spa
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) .net
throws Exception {
// TODO Auto-generated method stub
super.userEventTriggered(ctx, evt);
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)) {
//未進行讀操做
System.out.println("READER_IDLE");
// 超時關閉channel
ctx.close();
} else if (event.state().equals(IdleState.WRITER_IDLE)) {
} else if (event.state().equals(IdleState.ALL_IDLE)) {
//未進行讀寫
System.out.println("ALL_IDLE");
// 發送心跳消息
MsgHandleService.getInstance().sendMsgUtil.sendHeartMessage(ctx);
}
}
}
也就是說 服務端在10s內未進行讀寫操做,就會向客戶端發送心跳包,客戶端收到心跳包後當即回覆心跳包給服務端,此時服務端就進行了讀操做,也就不會觸發IdleState.READER_IDLE(未讀操做狀態),若客戶端異常掉線了,並不能響應服務端發來的心跳包,在25s後就會觸發IdleState.READER_IDLE(未讀操做狀態),此時服務器就會將通道關閉
客戶端代碼略
二 客戶端實現斷線重連
原理當客戶端鏈接服務器時
bootstrap.connect(new InetSocketAddress(
serverIP, port));
會返回一個ChannelFuture的對象,咱們對這個對象進行監聽
代碼以下:
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import com.ld.qmwj.Config;
import com.ld.qmwj.MyApplication;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* Created by zsg on 2015/11/21.
*/
public class MyClient implements Config {
private static Bootstrap bootstrap;
private static ChannelFutureListener channelFutureListener = null;
public MyClient() {
}
// 初始化客戶端
public static void initClient() {
NioEventLoopGroup group = new NioEventLoopGroup();
// Client服務啓動器 3.x的ClientBootstrap
// 改成Bootstrap,且構造函數變化很大,這裏用無參構造。
bootstrap = new Bootstrap();
// 指定EventLoopGroup
bootstrap.group(group);
// 指定channel類型
bootstrap.channel(NioSocketChannel.class);
// 指定Handler
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 建立分隔符緩衝對象
ByteBuf delimiter = Unpooled.copiedBuffer("#"
.getBytes());
// 當達到最大長度仍沒找到分隔符 就拋出異常
ch.pipeline().addLast(
new DelimiterBasedFrameDecoder(10000, true, false, delimiter));
// 將消息轉化成字符串對象 下面的到的消息就不用轉化了
//解碼
ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
ch.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
ch.pipeline().addLast(new MyClientHandler());
}
});
//設置TCP協議的屬性
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.option(ChannelOption.SO_TIMEOUT, 5000);
channelFutureListener = new ChannelFutureListener() {
public void operationComplete(ChannelFuture f) throws Exception {
// Log.d(Config.TAG, "isDone:" + f.isDone() + " isSuccess:" + f.isSuccess() +
// " cause" + f.cause() + " isCancelled" + f.isCancelled());
if (f.isSuccess()) {
Log.d(Config.TAG, "從新鏈接服務器成功");
} else {
Log.d(Config.TAG, "從新鏈接服務器失敗");
// 3秒後從新鏈接
f.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
doConnect();
}
}, 3, TimeUnit.SECONDS);
}
}
};
}
// 鏈接到服務端
public static void doConnect() {
Log.d(TAG, "doConnect");
ChannelFuture future = null;
try {
future = bootstrap.connect(new InetSocketAddress(
serverIP, port));
future.addListener(channelFutureListener);
} catch (Exception e) {
e.printStackTrace();
//future.addListener(channelFutureListener);
Log.d(TAG, "關閉鏈接");
}
}
}
監聽到鏈接服務器失敗時,會在3秒後從新鏈接(執行doConnect方法)
這還不夠,當客戶端掉線時要進行從新鏈接
在咱們本身定義邏輯處理的Handler中
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Log.d(Config.TAG, "與服務器斷開鏈接服務器");
super.channelInactive(ctx);
MsgHandle.getInstance().channel = null;
//從新鏈接服務器
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
MyClient.doConnect();
}
}, 2, TimeUnit.SECONDS);
ctx.close();
}