使用netty進行客戶端網路程式設計及斷線重連功能實現
阿新 • • 發佈:2019-02-15
不管做哪個方向開發,都會有那麼一兩個牛B閃閃的庫,可以極大的方便開發,比如java網路程式設計中的netty庫。無論客戶端還是服務端網路程式設計,netty基本都是首選網路庫,健壯、高效、穩定,並且已經得到很多商業專案驗證。
當用netty進行客戶端網路程式設計時,與服務端建立連線並完成資料編碼、解碼、通訊是最基礎功能,考慮程式的健壯性,則斷線重連是必不可少的一個功能點。netty原始碼的example資料夾中uptime目錄中有相關示例demo,但是總覺得該樣例程式碼封裝的不夠好,於是決定自己動手重新寫一個,如果有更優雅的斷線重連實現方法,希望大家將連結留言發我。下面是主體程式碼,Handler部分就不分享了。
public class BaseClient implements Runnable { private static Logger LOGGER = LoggerTools.getInstance(BaseClient.class); private String host_; private int port_; private int reConnectCount_ = 0; private ClientHandler clientHandler_; private OnMessageListener messageListener_; private OnStartupListener startupListener_; private volatile boolean isChannelPrepared_; private final static int MAX_MESSAGE_LENGTH = 8192; public BaseClient(String host, int port, OnMessageListener messageListener, OnStartupListener startupListener) { host_ = host; port_ = port; messageListener_ = messageListener; startupListener_ = startupListener; } @Override public void run() { connect(host_, port_); } // 傳送訊息 public void sendMessage(String msg, ResultListener<String> listener) { if (isChannelPrepared_) { clientHandler_.sendMessage(msg, listener); } else { listener.onFailure(msg); LOGGER.error("連線還未建立, 無法傳送資料..."); } } // 建立連線 private void connect(String host, int port) { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group); b.channel(NioSocketChannel.class); b.option(ChannelOption.TCP_NODELAY, true); b.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new LengthFieldBasedFrameDecoder(MAX_MESSAGE_LENGTH, 0, 4, 0 ,4)); p.addLast(new LengthFieldPrepender(4)); p.addLast(new StringDecoder(CharsetUtil.UTF_8)); p.addLast(new StringEncoder(CharsetUtil.UTF_8)); clientHandler_ = new ClientHandler(messageListener_); p.addLast(clientHandler_); } }); ChannelFuture f = b.connect(host, port).sync(); f.addListener(new GenericFutureListener<Future<? super Void>>() { @Override public void operationComplete(Future<? super Void> future) throws Exception { if (future.isSuccess()) { reConnectCount_ = 0; isChannelPrepared_ = true; startupListener_.onCompletion(true); LOGGER.info("與伺服器{}:{}連線建立成功...", host_, port_); } else { isChannelPrepared_ = false; startupListener_.onCompletion(false); LOGGER.info("與伺服器{}:{}連線建立失敗...", host_, port_); } } }); f.channel().closeFuture().sync(); } catch (Exception e) { isChannelPrepared_ = false; LOGGER.error("與伺服器{}:{}連接出現異常...", host_, port_); } finally { isChannelPrepared_ = false; group.shutdownGracefully(); reConnect(host, port); } } // 斷線重連 private void reConnect(String host, int port) { // fixme: 重連顯式退出? try { isChannelPrepared_ = false; int delay = ++reConnectCount_ * 5; reConnectCount_ = reConnectCount_ > 23 ? 23 : reConnectCount_; LOGGER.error("與伺服器{}:{}連線已斷開, {}秒後重連...", host, port, delay); Thread.sleep(delay * 1000); connect(host, port); } catch (Exception e) { e.printStackTrace(); } } }