在之前的netty文章裏,剛開始學,利用netty實現websocket寫了一個聊天程序。
本文的需求已經在文章的標題體現了。那接下來一一實現吧!首先從連接開始!
如果先啓動客戶端client,客戶端連接不上服務端server,客戶端就會無限重試
關鍵代碼,添加一個連接監聽器:(一會完整的代碼我會粘貼在下面)
class ConnectionListener implements ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (!channelFuture.isSuccess()) {
final EventLoop loop = channelFuture.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
System.err.println("服務端鏈接不上,開始重連操作...");
try {
NettyClient.connect(10007, "127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}, 3, TimeUnit.SECONDS);
} else {
System.err.println("服務端鏈接成功...");
}
}
}
需要注意的是,添加了監聽器之後,吧下面這個優雅關機的代碼註釋掉!因爲我們要實現的就是不關機,無限重試,如果沒有註釋這個,我們的監聽器將會不起效,這個優雅關機的條就是,沒有連接時關機,那就違背了我們要做到的沒有連接時無限重試了!
這樣實現了沒有連接上服務器端時無限重試,下面是沒連接上時的控制檯輸出:
代碼裏我們設置的是3秒重試一次!
下面解決第二個問題:如果因爲運行期間拋出異常導致,服務端客戶端連接斷開,我的客戶端還是要無限重試連接!
解釋一下爲什麼這樣做,這裏我服務端只是一個模擬,我真實的業務場景是要用客戶端通訊硬件設備,所以不關心服務端,只要異常就讓客戶端無限重試!
解決辦法:在重寫客戶端的handler中,重寫這個方法!
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
System.err.println("服務端鏈接不上,開始重連操作...");
try {
NettyClient.connect(10007, "127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}, 3, TimeUnit.SECONDS);
}
這樣會在因爲種種原因斷開鏈接後使客戶端無限重試!
下面解決第三個問題:當客戶端和服務端簡歷連接後無限發送數據!
解釋一下應用場景,我客戶端開啓後,需要在和服務端建立鏈接後無限拉取服務端的數據,就是要實時獲取了,並且定期存儲數據庫!
剛開始直接這樣在重寫的channelActive方法中加了個while(true):
這樣做也是完全體現了我的無知和青澀.....還會引發粘包問題...(這裏是錯的所以一帶而過了,下面說正確怎麼做)
這個方法是在建立連接後執行,貌似沒有問題,但是,客戶端的讀方法是在這個方法之後執行的,這樣就知道怎麼改了。
我需要發送數據,和接收數據是並行執行,而不是串行。並行怎麼實現呢?當然是線程了!
於是這樣寫channelActive方法,直接在裏面開啓線程,無限發送消息!
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
ByteBuf bf = Unpooled.copiedBuffer(("你好服務端:"+Math.random()).getBytes());
ctx.writeAndFlush(bf);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
下面看控制檯的打印
下面是完整的客戶端和服務端的代碼:
我直接分在了兩個類裏面,方便大家粘貼了以後就可以執行:
服務端:
package com.ning.nett;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
public class TimeServer {
public void bind(int port) throws Exception{
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) throws Exception {
new TimeServer().bind(10007);
}
}
class TimeServerHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf bb = (ByteBuf) msg;
byte[] b = new byte[bb.readableBytes()];
bb.readBytes(b);
System.out.println("收到客戶端數據:"+new String(b));
ByteBuf bf = Unpooled.copiedBuffer(("你好客戶端:"+Math.random()).getBytes());
ctx.writeAndFlush(bf);
}
}
客戶端
package com.ning.nett;
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.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public static void connect(int port,String host) throws Exception{
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new MessageHandler());
}
});
ChannelFuture f = b.connect(host, port);
f.addListener(new ConnectionListener());
f.channel().closeFuture().sync();
} finally {
//group.shutdownGracefully();
}
}
public static void main(String[] args){
try {
connect(10007, "127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MessageHandler extends ChannelInboundHandlerAdapter{
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("關閉客戶端連接");
cause.printStackTrace();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
System.err.println("服務端鏈接不上,開始重連操作...");
try {
NettyClient.connect(10007, "127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}, 3, TimeUnit.SECONDS);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
ByteBuf bf = Unpooled.copiedBuffer(("你好服務端:"+Math.random()).getBytes());
ctx.writeAndFlush(bf);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf bb = (ByteBuf) msg;
byte[] b = new byte[bb.readableBytes()];
bb.readBytes(b);
System.out.println("收到服務器端數據:"+new String(b));
}
}
class ConnectionListener implements ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (!channelFuture.isSuccess()) {
final EventLoop loop = channelFuture.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
System.err.println("服務端鏈接不上,開始重連操作...");
try {
NettyClient.connect(10007, "127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}, 3, TimeUnit.SECONDS);
} else {
System.err.println("服務端鏈接成功...");
}
}
}
還有一點要說的是:
這裏開啓線程只是測試階段這樣做,在項目裏,springboot項目是用線程池去執行這個線程的,
在springBoot開啓這個客戶端當然也不是用main方法啓動,需要配置springboot的啓動時初始化這個客戶端。
(這裏也有坑的下面的文章我會解釋)