netty客戶端連接後無限發送數據,連接不上時無限重試,斷線重連

在之前的netty文章裏,剛開始學,利用netty實現websocket寫了一個聊天程序。

純netty實現http,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的啓動時初始化這個客戶端。

(這裏也有坑的下面的文章我會解釋)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章