まず最初に言っておきますが、ハートビート監視を実現する方法は複数あるはずです. それを行う前に、リーダーから与えられた要件は、 netty を使用してそれを実装することでした. 1日以上それを見た後、小さなデモはnettyで完了しましたが、サーバーを接続したときに発見されました.socket io. ということで、socket ioの実装に変更。
他にも実装があるはずですが、ここでは取り上げていないので、当面は説明しません。まずは netty から始めましょう。
網目
最初のステップ: ガイド パッケージ
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version
</dependency>
この netty パッケージに関して、インターネット上の多くの人が、netty には下位互換性があると述べています。ただ、実際に遭遇したことがないので、あまり良いアドバイスはできませんが、問題が発生した場合は、この方向で考えてください。
コード。実際、これには、エンコードやデコードなど、非常に難解なことが含まれます。しかし、私がしなければならないことは比較的単純であるため、この側面をあまり注意深く見ていません。ここで簡単なハートビート モニタリングを実装すれば、問題ありません。
主に SimpleChannelInboundHandler<String> クラスを実装します。netty のバージョンによって、messageReceive などのメソッド名がわずかに異なり、一部のバージョンは clientRead0 と呼ばれることに注意してください。これはただのランダムです。
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();
}
});
}
}
これで設定は完了です。テストするときは、新しい ClientStarter オブジェクトを作成して start メソッドを呼び出すだけです。
ClientStarter starter = new ClientStarter(new Bootstrap());
starter.connect();
次に、テストするときにサーバーをローカルに構築し、問題なくテストしますが、サーバーとの共同デバッグに問題があります。
これは、接続が成功した後、サーバーがクライアントをオフラインにするためです。その後、プロセスを繰り返します。これはサーバー側のセットアップの問題です。
ソケットio
これはnettyの一種のカプセル化と言えますが、とにかくnettyよりもシンプルで使い勝手が良く、主に弊社サーバーで使用しているのでこの形に変更しました。
最初のステップは、パッケージをガイドすることです
<!-- netty socketio -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>io.socket</groupId>
<artifactId>socket.io-client</artifactId>
<version>1.0.0</version>
</dependency>
ここでお話しさせてください。以下は、クライアントがインポートする必要がある依存関係と上記です。. . サーバーをインポートする必要がありますか? とにかく、サーバーのプロジェクト コードを調べたところ、両方の依存関係がインポートされていました。
私のローカルコードに直接行きましょう、それはとても簡単です:
public void connectServer() {
String url = "http://" + serverListenIp;
try {
IO.Options options = new IO.Options();
options.transports = new String[] { "websocket" };
options.reconnectionAttempts = 1000000000;
options.reconnectionDelay = 60000;// 失败重连的时间间隔
options.timeout = 10000;// 连接超时时间(ms)
// par1 是任意参数
final Socket socket = IO.socket(url + "?userType=0", options);
socket.on(Socket.EVENT_CONNECT, objects -> {
System.out.println("client: 连接成功");
System.out.println("拉取缓存的数据信息!");
//做你想做的操作
});
socket.on(Socket.EVENT_CONNECTING, objects -> System.out.println("client: " + "连接中"));
socket.on(Socket.EVENT_CONNECT_TIMEOUT, objects -> System.out.println("client: " + "连接超时"));
socket.on(Socket.EVENT_CONNECT_ERROR, objects -> System.out.println("client: " + "连接失败"));
socket.connect();
} catch (Exception ex) {
ex.printStackTrace();
}
}
ここで、Socket.EVENT_CONNECT、Socket.EVENT_CONNECTING などに定数を与えます。わからなければ定義を見ればいい