首先說一下業務場景:
不同於netty常用的im,我這里只是單純的實現服務端與客戶端做一個心跳檢測,查看客戶端是否在線即可。因為是領導指定用netty,所以簡單的看了下demo,又因為業務需求的簡單,所以也只是淺顯的了解了一下,還有一點:正常來講客戶端和服務端監聽都可以。但是我們這是領導覺得少占用服務端資源,所以選擇了客戶端監聽。
1.導包。(雖然我沒用過,但是網上很多人都說了netty的向下兼容問題。所以有遇到的可以考慮一下這個問題)
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version
</dependency>
2.代碼實現。其實這個涉及到很深奧的東西,比如什么編碼解碼之類的!但是因為我要做的比較簡單,所以也沒太仔細看這方面的東西。然后再重申!我這里只要實現簡單的心跳監控就ok了!
package com.dsyl.done.netty;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
public class HeartBeatClientHandler extends SimpleChannelInboundHandler<String> {
private ClientStarter clientStarter;
public HeartBeatClientHandler(ClientStarter clientStarter) {
this.clientStarter = clientStarter;
}
/**
* 客戶端監聽寫事件。也就是設置時間內沒有與服務端交互則發送ping 給服務端
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent)evt).state();
if(state == IdleState.ALL_IDLE) {
ctx.writeAndFlush("PING");
System.out.println("send PING");
}
}
super.userEventTriggered(ctx, evt);
}
/**
* channelInactive 被觸發一定是和服務器斷開了。分兩種情況。一種是服務端close,一種是客戶端close。
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
System.err.println("客戶端與服務端斷開連接,斷開的時間為:"+(new Date()).toString());
// 定時線程 斷線重連
final EventLoop eventLoop = ctx.channel().eventLoop();
//設置斷開連接后重連時間,此設置是斷開連接一分鍾(60秒)后重連
eventLoop.schedule(() -> clientStarter.connect(), 60, TimeUnit.SECONDS);
}
/**
* 在服務器端不使用心跳檢測的情況下,如果客戶端突然拔掉網線斷網(注意這里不是客戶度程序關閉,而僅是異常斷網)
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if(cause instanceof IOException) {
System.out.println("server "+ctx.channel().remoteAddress()+"關閉連接");
}
}
/**
* 消息監控,監聽服務端傳來的消息(和netty版本有關,有的版本這個方法叫做clientRead0)
*/
@Override
protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
if (msg.equals("PONG")) {
System.out.println("receive form server PONG");
}
ReferenceCountUtil.release(msg);
}
}
以上代碼大部分出自於技術貼,但是很多注釋都是我一點點整理的。可能措辭不准確但是挺便於理解的。我當時就是一個個方法查找含義的。然后也沒啥業務邏輯
package com.dsyl.done.netty;
import java.net.InetSocketAddress;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
public class ClientStarter {
private Bootstrap bootstrap;
private int times = 0;
public ClientStarter(Bootstrap bootstrap) {
this.bootstrap = bootstrap;
ClientStarter clientStarter = this;
bootstrap.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new StringDecoder());
//發送消息頻率。單位秒。此設置是60秒發送一次消息
ch.pipeline().addLast(new IdleStateHandler(60, 60, 60));
ch.pipeline().addLast(new HeartBeatClientHandler(clientStarter));
}
});
}
public void connect() {
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("ip", 端口));
channelFuture.addListener(future ->{
if (future.isSuccess()) {
System.out.println("connect to server success");
} else {
System.out.println("connect to server failed,try times:" + ++times);
connect();
}
});
}
}
這個是客戶端實現的代碼(ip和端口自己按照情況填寫)。測試 的時候以下兩行代碼:
ClientStarter starter = new ClientStarter(new Bootstrap());
starter.connect();
使用的時候服務端還需要一些額外的設置,不然可能發生下圖這種情況:

連接成功后服務端就把客戶端踹下線了。然后重復這個過程。這個是服務端設置的問題。
