Android基于Mina實現的Socket長連接(二)

關于Mina實現的安卓端Socket長連接,我找了很多博客,都只是粗略大概的能夠與服務器進行通訊,沒有詳細談到長連接保活和性能優化,本篇博客記錄了我在封裝Mina長連接時候遇到的一些問題和相關代碼,以及一些不懂的地方,希望大佬能夠指正!

github直接依賴使用

我們在實現Socket長連接需要考慮的問題:

  • 何為長連接?
  • 長連接斷開之后需要怎么重連?
  • 與服務端怎么約定長連接?服務端怎么知道我連著還是沒有連上?
  • 網絡不好的時候怎么操作才能既保證長連接及時的連接上,又保證良好的性能(電量優化)?

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

  1. 創建一個Service,這個Service就是Socket發送和接收數據的核心,這個Service需要最大限度的保證它的存活率,參考了一些文章,做了一些保活的(zhuang)策略(bi),其實也沒啥卵用,像小米這種手機,要殺還是分分鐘殺掉我的進程,除非跟QQ微信一樣加入白名單,進程保活參考文章

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

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);//通過自定義的SessionManager將數據發送給服務器
        }

        @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, "交易服務綁定成功--->");
        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, "交易行情服務解綁成功--->");
        releaseHandlerThread();
        return super.onUnbind(intent);
    }

//這里是創建連接的配置,端口號,超時時間,超時次數等
    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", "連接被釋放,全部重新連接");
        }
    }

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

    /***
     * 無網絡關閉所有連接,不再繼續重連
     * @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 //這里是發送的數據格式,與后臺約定好
                    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中有幾個比較重要的地方

  1. Servvice的生命周期跟MainActivity綁定,也就是說我是在MainActivity里面啟動的這個Service,因為我的app在退出的時候就需要不參與數據的實時更新了;但是當用戶按下home鍵之后,app沒有退出,當用戶再次通過后臺調起app時,如果在后臺停留時間過長,Service可能會被殺掉(在老的手機上出現過這種情況,且很頻繁,這里service的保活就顯得微不足道),這時候會出現各種問題;我參考了一些app的做法就是,在applcation里面去監聽app進程,當進程被殺掉,就手動重啟整個app.這個方法很湊效,貌似當下只能這么做,后面會給一篇博客寫這個小技巧
  2. 在做心跳監測的時候,當出現網絡頻繁的斷開連接的時候,會出現網絡連接正常之后,Mina的Session連接不成功,一直處于重新連接,我猜想可能是因為Session的Buffer導致(google了一些大牛是這么說的,水平有限,未能深入研究),所以這里干脆將整個服務里的線程干掉,重新創建所有對象,相當于service重新啟動了一遍
if (null != thread) {
            thread.disConnect();
            thread.quit();
            thread = null;
            KLog.w("TAG", "連接被釋放,全部重新連接");
        }
  1. 性能優化,當我們的手機處于無網絡狀態的時候,是連接不上socket的,那么這時候的斷開我們就沒有必要重連,所以我使用了廣播去監聽網絡連接狀態,當廣播監聽到網絡狀態斷開之后,會自動重連10次,達到10次,如果還是沒有網,就徹底不再重連,關閉整個服務,這樣能優化一些性能,服務在后臺跑,也是有性能消耗的;當廣播監聽網絡連接上之后,就又重新開啟服務去重連..
  2. 由于項目中Socket訂閱是通過特定的commond去觸發的,比如我發送2,服務器就會給我返回當前開市情況,發送3,服務器就返回公告信息;所以當我啟動Service,在某個特定的頁面(一般在頁面的生命周期,如onCreat)向服務器一次發送多條訂閱,此時有可能與服務器恰好斷開了連接,正在重連,那么重連成功之后,不可能再走那個生命周期,所以需要將訂閱的command緩存,重新連接之后,再次發送一遍,確保服務器接收到了訂閱的內容
 /***
     * 連接成功之后,在這里重新訂閱所有交易信息
     * @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 //這里是發送的數據格式,與后臺約定好
                    SessionManager.getInstance().writeTradeToServer(s);
                }
            }
        }
    }

  • 連接管理類,這個類處理了Socket連接,發送數據,接收數據,長連接監聽
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,//連接斷開
        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);//設置心跳
        //設置超過多長時間客戶端進入IDLE狀態
        mConnection.getSessionConfig().setBothIdleTime(mConfig.getIdleTimeOut());
        mConnection.setConnectTimeoutCheckInterval(mConfig.getConnetTimeOutCheckInterval());//設置連接超時時間
        mConnection.getFilterChain().addLast("Logging", new LoggingFilter());
        mConnection.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MessageLineFactory()));
        mConnection.setDefaultRemoteAddress(mAddress);
        //設置心跳監聽的handler
        KeepAliveRequestTimeoutHandler heartBeatHandler = new KeepAliveRequestTimeoutHandlerImpl(closeType);
        KeepAliveMessageFactory heartBeatFactory = new TradeKeepAliveMessageFactoryImpm();
        //設置心跳
        KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE, heartBeatHandler);
        //是否回發
        heartBeat.setForwardEvent(false);
        //設置心跳間隔
        heartBeat.setRequestInterval(mConfig.getRequsetInterval());
        mConnection.getFilterChain().addLast("heartbeat", heartBeat);
        mConnection.setHandler(new DefaultIoHandler());
    }

    /**
     * 與服務器連接
     *
     * @return
     */
    public void connnectToServer() {
        int count = 0;
        if (null != mConnection) {
            while (getStatus() == ConnectStatus.DISCONNECTED) {
                try {
                    Thread.sleep(3000);
                    ConnectFuture future = mConnection.connect();
                    future.awaitUninterruptibly();// 等待連接創建成功
                    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));
                    }
                }
            }
        }
    }

    /**
     * 斷開連接
     */
    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", "斷開連接");
    }

    /***
     * Socket的消息接收處理和各種連接狀態的監聽在這里
     */
    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", "接收到服務器端消息:" + 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 繼承自這個IoHandlerAdapter,所有處理連接成功,連接失敗,失敗重連,接收服務器發回的數據都在這里處理

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

還有一個核心,就是自定義過濾器

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

上面一個是日志過濾器,規范寫法,下面這個就是我們與服務器約定好的編碼格式和一些數據截取,如報頭,報文,心跳,數據,等等,需要我們去自定義;這也突出了Mina的核心,使用過濾器去將業務層與數據包分離;

  • 自定義的數據編碼器,Mina的規范寫法
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);//設置是否可以動態擴展大小
            buffer.putString(s,charsetEncoder);
            buffer.flip();
            protocolEncoderOutput.write(buffer);
        }
    }

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

    }
}
  • 數據解碼器,需要根據與服務器約定的格式來編寫,編碼格式,數據截取等都是約定好的
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時候認為一行已經讀取完畢
                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;
    }
}
  • 長連接中心跳的監測,Mina使用KeepAliveRequestTimeoutHandler來為我們實現了心跳的監聽,開發者只需要實現KeepAliveRequestTimeoutHandler,重寫keepAliveRequestTimedOut方法,就能夠接收到之前設置好的心跳超時的回調
//設置心跳監聽的handler
KeepAliveRequestTimeoutHandler heartBeatHandler = new KeepAliveRequestTimeoutHandlerImpl(closeType);
KeepAliveMessageFactory heartBeatFactory = new MarketKeepAliveMessageFactoryImpm();
 //設置心跳
KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE, heartBeatHandler);

心跳超時的回調

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","心跳超時,重新連接:"+closeType);
        Bus.post(new ConnectClosedEvent(closeType));
    }
}
  • 有一個更巧妙的地方就是,Mina能夠將心跳內容跟業務內容通過KeepAliveMessageFactory區分開來,心跳內容可以在客戶端空閑一段時間之后自動發送給服務端,服務端發回一段特殊內容(一般固定不變)給客戶端,表明此時連接正常;這樣就不需要客戶端和服務端來區分哪些包是心跳包,哪些是業務內容;
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是發送過去的心跳包內容,response是服務器返回的心跳內容,開發者只需要判斷服務器返回的內容是約定的心跳答復內容,那就表明當前連接完全正常

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

以上就是我在項目中使用Mina封裝的Socket,基本能夠保證在有網絡的情況下長連接,并且能夠監聽心跳,斷開重連,無網絡不再重連,節省資源,正常收發內容;整個過程總結如下:

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

請大佬解答!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,825評論 18 139
  • 1、TCP狀態linux查看tcp的狀態命令:1)、netstat -nat 查看TCP各個狀態的數量2)、lso...
    北辰青閱讀 9,473評論 0 11
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,718評論 25 708
  • 前言 兩個進程如果要進行通訊最基本的一個前提就是能夠唯一的標識一個進程,在本地進程通訊中我們可以使用 PID...
    米奇小林閱讀 3,681評論 3 19
  • 在四十歲之后,你會明白人的一生其實干不了幾樣事情,而且所干的事情都是在尋找自己的位置。性格為生命密碼排列了定數,所...
    清峰哥閱讀 176評論 0 0