Android基于Mina實(shí)現(xiàn)的Socket長(zhǎng)連接(二)

關(guān)于Mina實(shí)現(xiàn)的安卓端Socket長(zhǎng)連接,我找了很多博客,都只是粗略大概的能夠與服務(wù)器進(jìn)行通訊,沒(méi)有詳細(xì)談到長(zhǎng)連接编土罚活和性能優(yōu)化,本篇博客記錄了我在封裝Mina長(zhǎng)連接時(shí)候遇到的一些問(wèn)題和相關(guān)代碼,以及一些不懂的地方,希望大佬能夠指正!

github直接依賴使用

我們?cè)趯?shí)現(xiàn)Socket長(zhǎng)連接需要考慮的問(wèn)題:

  • 何為長(zhǎng)連接?
  • 長(zhǎng)連接斷開(kāi)之后需要怎么重連?
  • 與服務(wù)端怎么約定長(zhǎng)連接?服務(wù)端怎么知道我連著還是沒(méi)有連上?
  • 網(wǎng)絡(luò)不好的時(shí)候怎么操作才能既保證長(zhǎng)連接及時(shí)的連接上,又保證良好的性能(電量?jī)?yōu)化)?

由于我做的是股票app,股票的實(shí)時(shí)行情需要在服務(wù)端更新數(shù)據(jù)之后推送給客戶端,這樣就是我要用到Socket的地方;

  1. 創(chuàng)建一個(gè)Service,這個(gè)Service就是Socket發(fā)送和接收數(shù)據(jù)的核心,這個(gè)Service需要最大限度的保證它的存活率,參考了一些文章,做了一些笨垓撸活的(zhuang)策略(bi),其實(shí)也沒(méi)啥卵用,像小米這種手機(jī),要?dú)⑦€是分分鐘殺掉我的進(jìn)程,除非跟QQ微信一樣加入白名單,進(jìn)程苯刃冢活參考文章

以下是我Service的部分代碼,都做了詳細(xì)的注釋

public class BackTradeService extends Service {
    private static final String TAG = "BackTradeService";
    private ConnectionThread thread;
    public String HOST = "127.0.0.1";
    public String PORT = "2345";
    private ConnectServiceBinder binder = new ConnectServiceBinder() {
        @Override
        public void sendMessage(String message) {
            super.sendMessage(message);
            SessionManager.getInstance().writeTradeToServer(message);//通過(guò)自定義的SessionManager將數(shù)據(jù)發(fā)送給服務(wù)器
        }

        @Override
        public void changeHost(String host, String port) {
            super.changeHost(host, port);
            releaseHandlerThread();
            startHandlerThread(HOST, PORT);
        }
    };


    @Override
    public IBinder onBind(Intent intent) {
        Bus.register(this);
        SocketCommandCacheUtils.getInstance().initTradeCache();
        KLog.i(TAG, "交易服務(wù)綁定成功--->");
        HOST = intent.getStringExtra("host");
        PORT = intent.getStringExtra("port");
        startHandlerThread(HOST, PORT);
        return binder;
    }


    @Override
    public boolean onUnbind(Intent intent) {
        Bus.unregister(this);
        SocketCommandCacheUtils.getInstance().removeAllTradeCache();
        KLog.i(TAG, "交易行情服務(wù)解綁成功--->");
        releaseHandlerThread();
        return super.onUnbind(intent);
    }

//這里是創(chuàng)建連接的配置,端口號(hào),超時(shí)時(shí)間,超時(shí)次數(shù)等
    public void startHandlerThread(String host, String port) {
        ConnectionConfig config = new ConnectionConfig.Builder(getApplicationContext())
                .setIp(host)
                .setPort(MathUtils.StringToInt(port))
                .setReadBufferSize(10240)
                .setIdleTimeOut(30)
                .setTimeOutCheckInterval(10)
                .setRequestInterval(10)
                .builder();
        thread = new ConnectionThread("BackTradeService", config);
        thread.start();
    }

    public void releaseHandlerThread() {
        if (null != thread) {
            thread.disConnect();
            thread.quit();
            thread = null;
            KLog.w("TAG", "連接被釋放,全部重新連接");
        }
    }

    /***
     * 心跳超時(shí),在此重啟整個(gè)連接
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMainThread(ConnectClosedEvent event) {
        if (event.getColseType() == SocketConstants.TRADE_CLOSE_TYPE) {
            KLog.w("TAG", "BackTradeService接收到心跳超時(shí),重啟整個(gè)推送連接");
            releaseHandlerThread();
            startHandlerThread(HOST, PORT);
        }
    }

    /***
     * 無(wú)網(wǎng)絡(luò)關(guān)閉所有連接,不再繼續(xù)重連
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMainThread(ConnectCloseAllEvent event) {
        if(event.isCloseAll()){
            releaseHandlerThread();
        }
    }

    /***
     * 連接成功之后,在這里重新訂閱所有交易信息
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMainThread(ConnectSuccessEvent event) {
        if (event.getConnectType() == SocketConstants.TRADE_CONNECT_SUCCESS) {
            ArrayList<Integer> tradeCache = SocketCommandCacheUtils.getInstance().getTradeCache();
            if (null != tradeCache) {
                for (int i = 0; i < tradeCache.size(); i++) {
                    String tm = String.valueOf(System.currentTimeMillis());
                    String s = ...json //這里是發(fā)送的數(shù)據(jù)格式,與后臺(tái)約定好
                    SessionManager.getInstance().writeTradeToServer(s);
                }
            }
        }
    }

    class ConnectionThread extends HandlerThread {

        TradeConnectionManager mManager;

        public ConnectionThread(String name, ConnectionConfig config) {
            super(name);
            if (null == mManager)
                mManager = new TradeConnectionManager(config,SocketConstants.TRADE_CLOSE_TYPE);
        }

        @Override
        protected void onLooperPrepared() {
            if (null != mManager)
                mManager.connnectToServer();
        }

        public void disConnect() {
            if (null != mManager)
                mManager.disContect();
        }
    }

Service中有幾個(gè)比較重要的地方

  1. Servvice的生命周期跟MainActivity綁定,也就是說(shuō)我是在MainActivity里面啟動(dòng)的這個(gè)Service,因?yàn)槲业腶pp在退出的時(shí)候就需要不參與數(shù)據(jù)的實(shí)時(shí)更新了;但是當(dāng)用戶按下home鍵之后,app沒(méi)有退出,當(dāng)用戶再次通過(guò)后臺(tái)調(diào)起app時(shí),如果在后臺(tái)停留時(shí)間過(guò)長(zhǎng),Service可能會(huì)被殺掉(在老的手機(jī)上出現(xiàn)過(guò)這種情況,且很頻繁,這里service的笨瞥蓿活就顯得微不足道),這時(shí)候會(huì)出現(xiàn)各種問(wèn)題;我參考了一些app的做法就是,在applcation里面去監(jiān)聽(tīng)app進(jìn)程,當(dāng)進(jìn)程被殺掉,就手動(dòng)重啟整個(gè)app.這個(gè)方法很湊效,貌似當(dāng)下只能這么做,后面會(huì)給一篇博客寫(xiě)這個(gè)小技巧
  2. 在做心跳監(jiān)測(cè)的時(shí)候,當(dāng)出現(xiàn)網(wǎng)絡(luò)頻繁的斷開(kāi)連接的時(shí)候,會(huì)出現(xiàn)網(wǎng)絡(luò)連接正常之后,Mina的Session連接不成功,一直處于重新連接,我猜想可能是因?yàn)镾ession的Buffer導(dǎo)致(google了一些大牛是這么說(shuō)的,水平有限,未能深入研究),所以這里干脆將整個(gè)服務(wù)里的線程干掉,重新創(chuàng)建所有對(duì)象,相當(dāng)于service重新啟動(dòng)了一遍
if (null != thread) {
            thread.disConnect();
            thread.quit();
            thread = null;
            KLog.w("TAG", "連接被釋放,全部重新連接");
        }
  1. 性能優(yōu)化,當(dāng)我們的手機(jī)處于無(wú)網(wǎng)絡(luò)狀態(tài)的時(shí)候,是連接不上socket的,那么這時(shí)候的斷開(kāi)我們就沒(méi)有必要重連,所以我使用了廣播去監(jiān)聽(tīng)網(wǎng)絡(luò)連接狀態(tài),當(dāng)廣播監(jiān)聽(tīng)到網(wǎng)絡(luò)狀態(tài)斷開(kāi)之后,會(huì)自動(dòng)重連10次,達(dá)到10次,如果還是沒(méi)有網(wǎng),就徹底不再重連,關(guān)閉整個(gè)服務(wù),這樣能優(yōu)化一些性能,服務(wù)在后臺(tái)跑,也是有性能消耗的;當(dāng)廣播監(jiān)聽(tīng)網(wǎng)絡(luò)連接上之后,就又重新開(kāi)啟服務(wù)去重連..
  2. 由于項(xiàng)目中Socket訂閱是通過(guò)特定的commond去觸發(fā)的,比如我發(fā)送2,服務(wù)器就會(huì)給我返回當(dāng)前開(kāi)市情況,發(fā)送3,服務(wù)器就返回公告信息;所以當(dāng)我啟動(dòng)Service,在某個(gè)特定的頁(yè)面(一般在頁(yè)面的生命周期,如onCreat)向服務(wù)器一次發(fā)送多條訂閱,此時(shí)有可能與服務(wù)器恰好斷開(kāi)了連接,正在重連,那么重連成功之后,不可能再走那個(gè)生命周期,所以需要將訂閱的command緩存,重新連接之后,再次發(fā)送一遍,確保服務(wù)器接收到了訂閱的內(nèi)容
 /***
     * 連接成功之后,在這里重新訂閱所有交易信息
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMainThread(ConnectSuccessEvent event) {
        if (event.getConnectType() == SocketConstants.TRADE_CONNECT_SUCCESS) {
            ArrayList<Integer> tradeCache = SocketCommandCacheUtils.getInstance().getTradeCache();
            if (null != tradeCache) {
                for (int i = 0; i < tradeCache.size(); i++) {
                    String tm = String.valueOf(System.currentTimeMillis());
                    String s = ...json //這里是發(fā)送的數(shù)據(jù)格式,與后臺(tái)約定好
                    SessionManager.getInstance().writeTradeToServer(s);
                }
            }
        }
    }

  • 連接管理類,這個(gè)類處理了Socket連接,發(fā)送數(shù)據(jù),接收數(shù)據(jù),長(zhǎng)連接監(jiān)聽(tīng)
public class TradeConnectionManager {
    private final int closeType;
    private ConnectionConfig mConfig;
    private WeakReference<Context> mContext;
    private NioSocketConnector mConnection;
    private IoSession mSession;
    private InetSocketAddress mAddress;

    private enum ConnectStatus {
        DISCONNECTED,//連接斷開(kāi)
        CONNECTED//連接成功
    }

    private ConnectStatus status = ConnectStatus.DISCONNECTED;

    public ConnectStatus getStatus() {
        return status;
    }

    public void setStatus(ConnectStatus status) {
        this.status = status;
    }

    public TradeConnectionManager(ConnectionConfig config, int closeType) {
        this.mConfig = config;
        this.mContext = new WeakReference<>(config.getContext());
        this.closeType = closeType;
        init();
    }

    private void init() {
        mAddress = new InetSocketAddress(mConfig.getIp(), mConfig.getPort());
        mConnection = new NioSocketConnector();
        mConnection.getSessionConfig().setReadBufferSize(mConfig.getReadBufferSize());
        mConnection.getSessionConfig().setKeepAlive(true);//設(shè)置心跳
        //設(shè)置超過(guò)多長(zhǎng)時(shí)間客戶端進(jìn)入IDLE狀態(tài)
        mConnection.getSessionConfig().setBothIdleTime(mConfig.getIdleTimeOut());
        mConnection.setConnectTimeoutCheckInterval(mConfig.getConnetTimeOutCheckInterval());//設(shè)置連接超時(shí)時(shí)間
        mConnection.getFilterChain().addLast("Logging", new LoggingFilter());
        mConnection.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MessageLineFactory()));
        mConnection.setDefaultRemoteAddress(mAddress);
        //設(shè)置心跳監(jiān)聽(tīng)的handler
        KeepAliveRequestTimeoutHandler heartBeatHandler = new KeepAliveRequestTimeoutHandlerImpl(closeType);
        KeepAliveMessageFactory heartBeatFactory = new TradeKeepAliveMessageFactoryImpm();
        //設(shè)置心跳
        KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE, heartBeatHandler);
        //是否回發(fā)
        heartBeat.setForwardEvent(false);
        //設(shè)置心跳間隔
        heartBeat.setRequestInterval(mConfig.getRequsetInterval());
        mConnection.getFilterChain().addLast("heartbeat", heartBeat);
        mConnection.setHandler(new DefaultIoHandler());
    }

    /**
     * 與服務(wù)器連接
     *
     * @return
     */
    public void connnectToServer() {
        int count = 0;
        if (null != mConnection) {
            while (getStatus() == ConnectStatus.DISCONNECTED) {
                try {
                    Thread.sleep(3000);
                    ConnectFuture future = mConnection.connect();
                    future.awaitUninterruptibly();// 等待連接創(chuàng)建成功
                    mSession = future.getSession();
                    if (mSession.isConnected()) {
                        setStatus(ConnectStatus.CONNECTED);
                        SessionManager.getInstance().setTradeSeesion(mSession);
                        KLog.e("TAG", "trade連接成功:mSession-->" + mSession);
                        Bus.post(new ConnectSuccessEvent(SocketConstants.TRADE_CONNECT_SUCCESS));
                        break;
                    }
                } catch (Exception e) {
                    count++;
                    KLog.e("TAG", "connnect中連接失敗,trade每三秒重新連接一次:mSession-->" + mSession + ",count" + count);
                    if (count == 10) {
                        Bus.post(new ConnectClosedEvent(closeType));
                    }
                }
            }
        }
    }

    /**
     * 斷開(kāi)連接
     */
    public void disContect() {
        setStatus(ConnectStatus.CONNECTED);
        mConnection.getFilterChain().clear();
        mConnection.dispose();
        SessionManager.getInstance().closeSession(closeType);
        SessionManager.getInstance().removeSession(closeType);
        mConnection = null;
        mSession = null;
        mAddress = null;
        mContext = null;
        KLog.e("tag", "斷開(kāi)連接");
    }

    /***
     * Socket的消息接收處理和各種連接狀態(tài)的監(jiān)聽(tīng)在這里
     */
    private class DefaultIoHandler extends IoHandlerAdapter {

        @Override
        public void sessionOpened(IoSession session) throws Exception {
            super.sessionOpened(session);
        }

        @Override
        public void messageReceived(IoSession session, Object message) throws Exception {
            KLog.e("tag", "接收到服務(wù)器端消息:" + message.toString());
            SessionManager.getInstance().writeTradeToClient(message.toString());

        }

        @Override
        public void sessionCreated(IoSession session) throws Exception {
            super.sessionCreated(session);
            KLog.e("tag", "sessionCreated:" + session.hashCode());
        }

        @Override
        public void sessionClosed(IoSession session) throws Exception {
            super.sessionClosed(session);
            KLog.e("tag", "sessionClosed,連接斷掉了,需要在此重新連接:" + session.hashCode());
            setStatus(ConnectStatus.DISCONNECTED);
            Bus.post(new ConnectClosedEvent(closeType));
        }

        @Override
        public void messageSent(IoSession session, Object message) throws Exception {
            super.messageSent(session, message);
            KLog.e("tag", "messageSent");
        }

        @Override
        public void inputClosed(IoSession session) throws Exception {
            super.inputClosed(session);
            KLog.w("tag", "server or client disconnect");
            Bus.post(new ConnectClosedEvent(closeType));
        }

        @Override
        public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
            super.sessionIdle(session, status);
            KLog.e("tag", "sessionIdle:" + session.toString() + ",status:" + status);
            if (null != session) {
                session.closeNow();
            }
        }
    }

以上代碼中,最核心的是IoHandlerAdapter ,我們自定義的DefaultIoHandler 繼承自這個(gè)IoHandlerAdapter,所有處理連接成功,連接失敗,失敗重連,接收服務(wù)器發(fā)回的數(shù)據(jù)都在這里處理

這里可以看一下messageSent和messageReceived兩個(gè)方法,分別是發(fā)送數(shù)據(jù)給服務(wù)器和接收服務(wù)器的數(shù)據(jù),這也就是Mina的高明之處(數(shù)據(jù)層與業(yè)務(wù)層剝離,互不干涉)

還有一個(gè)核心,就是自定義過(guò)濾器

mConnection.getFilterChain().addLast("Logging", new LoggingFilter());
mConnection.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MessageLineFactory()));

上面一個(gè)是日志過(guò)濾器,規(guī)范寫(xiě)法,下面這個(gè)就是我們與服務(wù)器約定好的編碼格式和一些數(shù)據(jù)截取,如報(bào)頭,報(bào)文,心跳,數(shù)據(jù),等等,需要我們?nèi)プ远x;這也突出了Mina的核心,使用過(guò)濾器去將業(yè)務(wù)層與數(shù)據(jù)包分離;

  • 自定義的數(shù)據(jù)編碼器,Mina的規(guī)范寫(xiě)法
public class MessageLineEncoder implements ProtocolEncoder {
    @Override
    public void encode(IoSession ioSession, Object message, ProtocolEncoderOutput protocolEncoderOutput) throws Exception {
        String s = null ;
        if(message instanceof  String){
            s = (String) message;
        }
        CharsetEncoder charsetEncoder = (CharsetEncoder) ioSession.getAttribute("encoder");
        if(null == charsetEncoder){
            charsetEncoder = Charset.defaultCharset().newEncoder();
            ioSession.setAttribute("encoder",charsetEncoder);
        }

        if(null!=s){
            IoBuffer buffer = IoBuffer.allocate(s.length());
            buffer.setAutoExpand(true);//設(shè)置是否可以動(dòng)態(tài)擴(kuò)展大小
            buffer.putString(s,charsetEncoder);
            buffer.flip();
            protocolEncoderOutput.write(buffer);
        }
    }

    @Override
    public void dispose(IoSession ioSession) throws Exception {

    }
}
  • 數(shù)據(jù)解碼器,需要根據(jù)與服務(wù)器約定的格式來(lái)編寫(xiě),編碼格式,數(shù)據(jù)截取等都是約定好的
public class MessageLineCumulativeDecoder extends CumulativeProtocolDecoder {
    @Override
    protected boolean doDecode(IoSession ioSession, IoBuffer in, ProtocolDecoderOutput protocolDecoderOutput) throws Exception {
        int startPosition = in.position();
        while (in.hasRemaining()) {
            byte b = in.get();
            if (b == '\n') {//讀取到\n時(shí)候認(rèn)為一行已經(jīng)讀取完畢
                int currentPosition = in.position();
                int limit = in.limit();
                in.position(startPosition);
                in.limit(limit);
                IoBuffer buffer = in.slice();
                byte[] bytes = new byte[buffer.limit()];
                buffer.get(bytes);
                String message = new String(bytes);
                protocolDecoderOutput.write(message);
                in.position(currentPosition);
                in.limit(limit);
                return true;
            }
        }
        in.position(startPosition);
        return false;
    }
}
  • 最后是編解碼工廠類
public class MessageLineFactory implements ProtocolCodecFactory {
    private MessageLineCumulativeDecoder messageLineDecoder;
    private MessageLineEncoder messageLineEncoder;

    public MessageLineFactory() {
        messageLineDecoder = new MessageLineCumulativeDecoder();
        messageLineEncoder = new MessageLineEncoder();
    }

    @Override
    public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {
        return messageLineEncoder;
    }

    @Override
    public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception {
        return messageLineDecoder;
    }
}
  • 長(zhǎng)連接中心跳的監(jiān)測(cè),Mina使用KeepAliveRequestTimeoutHandler來(lái)為我們實(shí)現(xiàn)了心跳的監(jiān)聽(tīng),開(kāi)發(fā)者只需要實(shí)現(xiàn)KeepAliveRequestTimeoutHandler,重寫(xiě)keepAliveRequestTimedOut方法,就能夠接收到之前設(shè)置好的心跳超時(shí)的回調(diào)
//設(shè)置心跳監(jiān)聽(tīng)的handler
KeepAliveRequestTimeoutHandler heartBeatHandler = new KeepAliveRequestTimeoutHandlerImpl(closeType);
KeepAliveMessageFactory heartBeatFactory = new MarketKeepAliveMessageFactoryImpm();
 //設(shè)置心跳
KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE, heartBeatHandler);

心跳超時(shí)的回調(diào)

public class KeepAliveRequestTimeoutHandlerImpl implements KeepAliveRequestTimeoutHandler {

    private final int closeType;

    public KeepAliveRequestTimeoutHandlerImpl(int closeType) {
        this.closeType = closeType ;
    }

    @Override
    public void keepAliveRequestTimedOut(KeepAliveFilter keepAliveFilter, IoSession ioSession) throws Exception {
        KLog.e("TAG","心跳超時(shí),重新連接:"+closeType);
        Bus.post(new ConnectClosedEvent(closeType));
    }
}
  • 有一個(gè)更巧妙的地方就是,Mina能夠?qū)⑿奶鴥?nèi)容跟業(yè)務(wù)內(nèi)容通過(guò)KeepAliveMessageFactory區(qū)分開(kāi)來(lái),心跳內(nèi)容可以在客戶端空閑一段時(shí)間之后自動(dòng)發(fā)送給服務(wù)端,服務(wù)端發(fā)回一段特殊內(nèi)容(一般固定不變)給客戶端,表明此時(shí)連接正常;這樣就不需要客戶端和服務(wù)端來(lái)區(qū)分哪些包是心跳包,哪些是業(yè)務(wù)內(nèi)容;
public class MarketKeepAliveMessageFactoryImpm implements KeepAliveMessageFactory {
    /***
     * 行情心跳包的request
     */
    public  final String marketHeartBeatRequest = "[0,0]\n";
    /***
     * 行情心跳包的response
     */
    public  final String marketHeartBeatResponse = "[0,10]\n";

    @Override
    public boolean isRequest(IoSession ioSession, Object o) {
        if (o.equals(marketHeartBeatRequest)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean isResponse(IoSession ioSession, Object o) {
        if (o.equals(marketHeartBeatResponse)) {
            return true;
        }
        return false;
    }

    @Override
    public Object getRequest(IoSession ioSession) {
        return marketHeartBeatRequest;
    }

    @Override
    public Object getResponse(IoSession ioSession, Object o) {
        return marketHeartBeatResponse;
    }
}

request是發(fā)送過(guò)去的心跳包內(nèi)容,response是服務(wù)器返回的心跳內(nèi)容,開(kāi)發(fā)者只需要判斷服務(wù)器返回的內(nèi)容是約定的心跳答復(fù)內(nèi)容,那就表明當(dāng)前連接完全正常

    @Override
    public boolean isResponse(IoSession ioSession, Object o) {
        if (o.equals(marketHeartBeatResponse)) {
            return true;
        }
        return false;
    }
總結(jié):

以上就是我在項(xiàng)目中使用Mina封裝的Socket,基本能夠保證在有網(wǎng)絡(luò)的情況下長(zhǎng)連接,并且能夠監(jiān)聽(tīng)心跳,斷開(kāi)重連,無(wú)網(wǎng)絡(luò)不再重連,節(jié)省資源,正常收發(fā)內(nèi)容;整個(gè)過(guò)程總結(jié)如下:

  1. 使用Service,保證Socket的內(nèi)容收發(fā);
  2. 確保Mina幾個(gè)關(guān)鍵點(diǎn)設(shè)置正確,否則無(wú)法收發(fā)內(nèi)容;主要就是Session,IoHandler,發(fā)送和接收數(shù)據(jù)編解碼的ProtocolCodecFilter,以及監(jiān)測(cè)心跳的KeepAliveFilter和KeepAliveRequestTimeoutHandler;
  3. 各種綜合情況考慮下的重連,包括網(wǎng)絡(luò)一直連接,網(wǎng)絡(luò)時(shí)斷時(shí)續(xù),網(wǎng)絡(luò)徹底斷開(kāi),數(shù)據(jù)發(fā)送的時(shí)機(jī);
  4. 踩坑,當(dāng)網(wǎng)絡(luò)時(shí)斷時(shí)續(xù)時(shí),網(wǎng)絡(luò)連接上之后,發(fā)送數(shù)據(jù)會(huì)沾滿Buffer導(dǎo)致一直連接不上,重置整個(gè)連接,目前為止能夠解決;
疑點(diǎn):
mConnection.getSessionConfig().setBothIdleTime(mConfig.getIdleTimeOut());//設(shè)置客戶端空閑時(shí)間
mConnection.getSessionConfig().setKeepAlive(true);//設(shè)置心跳 
mConnection.setConnectTimeoutCheckInterval(mConfig.getConnetTimeOutCheckInterval());//設(shè)置連接超時(shí)時(shí)間
heartBeat.setRequestInterval(mConfig.getRequsetInterval());//設(shè)置心跳間隔時(shí)間
  1. 這幾個(gè)時(shí)間我在config中配置了,貌似不起作用,按正常情況來(lái)說(shuō),java的時(shí)間都是以毫秒計(jì)算,比如把客戶端空閑時(shí)間設(shè)置成了30*1000這種,也就是30秒,但是在客戶端空閑時(shí)發(fā)送心跳的時(shí)間跟我設(shè)置的對(duì)不上,我設(shè)置了30秒,但是空閑我測(cè)了一下好像10秒就開(kāi)始發(fā)送;糾結(jié)...
  2. 心跳間隔時(shí)間也不對(duì),我設(shè)置了10秒,也就是客戶端空閑30秒之后,每10秒發(fā)送一次心跳給服務(wù)端,但是時(shí)間上貌似都不對(duì)

請(qǐng)大佬解答!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冗栗,一起剝皮案震驚了整個(gè)濱河市泄朴,隨后出現(xiàn)的幾起案子罢吃,更是在濱河造成了極大的恐慌蔬顾,老刑警劉巖宴偿,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異诀豁,居然都是意外死亡窄刘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)舷胜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)娩践,“玉大人,你說(shuō)我怎么就攤上這事》牛” “怎么了材泄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吨岭。 經(jīng)常有香客問(wèn)我拉宗,道長(zhǎng),這世上最難降的妖魔是什么辣辫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任簿废,我火速辦了婚禮,結(jié)果婚禮上络它,老公的妹妹穿的比我還像新娘。我一直安慰自己歪赢,他們只是感情好化戳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著埋凯,像睡著了一般点楼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上白对,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天掠廓,我揣著相機(jī)與錄音,去河邊找鬼甩恼。 笑死蟀瞧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的条摸。 我是一名探鬼主播悦污,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼钉蒲!你這毒婦竟也來(lái)了切端?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顷啼,失蹤者是張志新(化名)和其女友劉穎踏枣,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钙蒙,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茵瀑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躬厌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘾婿。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出偏陪,到底是詐尸還是另有隱情抢呆,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布笛谦,位于F島的核電站抱虐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏饥脑。R本人自食惡果不足惜恳邀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灶轰。 院中可真熱鬧谣沸,春花似錦、人聲如沸笋颤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)伴澄。三九已至赋除,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間非凌,已是汗流浹背举农。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敞嗡,地道東北人颁糟。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喉悴,于是被迫代替她去往敵國(guó)和親滚停。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理粥惧,服務(wù)發(fā)現(xiàn)键畴,斷路器,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • 1突雪、TCP狀態(tài)linux查看tcp的狀態(tài)命令:1)起惕、netstat -nat 查看TCP各個(gè)狀態(tài)的數(shù)量2)、lso...
    北辰青閱讀 9,398評(píng)論 0 11
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評(píng)論 25 707
  • 前言 兩個(gè)進(jìn)程如果要進(jìn)行通訊最基本的一個(gè)前提就是能夠唯一的標(biāo)識(shí)一個(gè)進(jìn)程咏删,在本地進(jìn)程通訊中我們可以使用 PID...
    米奇小林閱讀 3,638評(píng)論 3 19
  • 在四十歲之后惹想,你會(huì)明白人的一生其實(shí)干不了幾樣事情,而且所干的事情都是在尋找自己的位置督函。性格為生命密碼排列了定數(shù)嘀粱,所...
    清峰哥閱讀 171評(píng)論 0 0