1. 程式人生 > >使用netty進行客戶端網路程式設計及斷線重連功能實現

使用netty進行客戶端網路程式設計及斷線重連功能實現

       不管做哪個方向開發,都會有那麼一兩個牛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();
        }
    }

}