android socket 通信實戰(zhàn)

目錄

  • socket 簡介
  • 創(chuàng)建連接
  • 接收消息
  • 發(fā)送消息
  • 斷開連接
  • 進(jìn)度灰色毖渖荩活
  • IPC
  • 自定義權(quán)限廣播
  • 重試機制
  • 進(jìn)程異逞档玻恢復(fù)
    前段時間公司項目有個大版本準(zhǔn)備對IM(消息通信)模塊升級们拙。雖然需求緊急但server同事任堅持自定義消息協(xié)議來實現(xiàn)一套通信框架腐泻。這里對Android端實現(xiàn)做下總結(jié)桐经,僅供交流拓哟。

socket 簡介

socket就是我們常說的套接字几缭。網(wǎng)絡(luò)上具有唯一標(biāo)識的IP地址和端口組合在一起才能構(gòu)成唯一能識別的標(biāo)識符套接字河泳。根據(jù)不同的的底層協(xié)議,Socket的實現(xiàn)是多樣化的年栓。常見的Socket類型為流套接字(streamsocket)和數(shù)據(jù)報套接字(datagramsocket)拆挥;數(shù)據(jù)報套接字使用UDP協(xié)議,提供數(shù)據(jù)打包發(fā)送服務(wù)某抓。流套接字將TCP作為其端對端協(xié)議竿刁,提供了一個可信賴的字節(jié)流服務(wù),本文將介紹流套接字的簡單應(yīng)用搪缨。

創(chuàng)建連接

這里給出一個簡單通信模型食拜,圖片來源于網(wǎng)絡(luò)。

image.png
  • sample code 這里只貼出了client端代碼
try {
            LogUtil.d(TAG, " connecting  ip=%s , port = %d", ip, port);
            while (true) {
                try {
                    mSocket = new Socket();
                    mSocket.setKeepAlive(true);
                    mSocket.setSoTimeout(2 * 3 * 60 * 1000);//inputStream read 超時時間
                    mSocket.setTcpNoDelay(true);
                    mSocket.connect(new InetSocketAddress(ip, port));
                    if (mSocket.isConnected()) {
                        dataIS = new DataInputStream(mSocket.getInputStream());
                        dataOS = new DataOutputStream(mSocket.getOutputStream());
                        connectState = true;
                    }
                    this.mCallback.onConnect(this);
                    break;//connect sucess
                } catch (IOException e) {
                    mRetryPolicy.retry(e);
                    //間隔5秒副编,重連负甸。
                    Thread.sleep(5000);
                    LogUtil.e(TAG, " connect IOException =%s , and retry count = %d", e.getMessage(), mRetryPolicy.getCurrentRetryCount());
                }
            }
        } catch (Exception e) {
            //重試后,仍然失敗了∩氪回調(diào)失敗打月。
            connectState = false;
            e.printStackTrace();
            LogUtil.e(TAG, " connect IOException = " + e.getMessage());
            mCallback.onConnectFailed(e);
        }

接收消息

創(chuàng)建成功后就開始接收處理消息。

while (isConnected()) {
            try {
                int type = dataIS.readByte();//讀取1位
                int length = dataIS.readChar();//讀取2位標(biāo)記第三段數(shù)據(jù)長度
                byte[] data = new byte[length];
                LogUtil.i(TAG, " receiveData connected receiveData type = %d, ", type);
                dataIS.readFully(data);
                mCallback.onReceive(type, data);
            } catch (SocketTimeoutException e) {
                LogUtil.e(TAG, " receiveData SocketTimeoutException = " + e.getMessage());
                e.printStackTrace();
                break;
            } catch (IOException e) {
                LogUtil.e(TAG, " receiveData IOException = " + e.getMessage());
                e.printStackTrace();
                break;//異常后蚕捉,退出循環(huán)
            }
        }
        
        //通知異常奏篙,并重連。

發(fā)送消息

在連接狀態(tài)下向服務(wù)器發(fā)送字節(jié)流消息迫淹。

// 1.同步處理 2.異趁赝ǎ或未連接狀態(tài)下,則回調(diào)并通知重連
final byte[] bytes = {1,2,3,4,5}; //test data
synchronized (TcpClient.class) {
            if (isConnected()) {
                try {
                    byte type = 1;
                    dataOS.writeByte(type);
                    dataOS.writeChar(bytes.length);
                    dataOS.write(bytes);
                    dataOS.flush();
                    LogUtil.i(TAG, "send success msg : %s", Arrays.toString(bytes));
                } catch (final IOException e) {
                    callback.onFailed(e);
                    disConnect(true);
                    e.printStackTrace();
                }
            } else {
                callback.onFailed(new Exception("socket is not connected"));
                disConnect(true);
                LogUtil.i(TAG, "socket is not connected !");
            }
        }

斷開連接

//關(guān)閉流
        closeInputStream(dataIS);
        closeOutputStream(dataOS);
        if (mSocket != null) {
            try {
                mSocket.shutdownInput();
                mSocket.shutdownOutput();
                mSocket.close();
                mSocket = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (mCallback != null && needRec) {
            mCallback.onDisconnect();
        }
        mState = STATE_DISCONNECT;

進(jìn)度灰色绷舶荆活

由于項目中通信模塊運行在獨立的進(jìn)程中肺稀,為避免進(jìn)程被意外干掉,這里將其優(yōu)先級提高已達(dá)到報活效果应民。

if (Build.VERSION.SDK_INT < 18) {//18以下话原,可直接設(shè)置為前臺service
            startForeground(GRAY_SERVICE_ID, new Notification());
        } else if (Build.VERSION.SDK_INT <= 24) {
            Intent innerIntent = new Intent(this, GrayInnerService.class);
            startService(innerIntent);
            startForeground(GRAY_SERVICE_ID, new Notification());
        }

在看看GrayInnerService實現(xiàn)

/**
     * 測試結(jié)果:API<=24可行。25/7.1.1版本诲锹,通知欄正常狀態(tài)下看不到icon繁仁,但滑下來就看得到icon。
     * 給 API >= 18 的平臺上用的灰色惫樵埃活手段
     */
    public static class GrayInnerService extends Service {

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(GRAY_SERVICE_ID, new Notification());
            stopSelf();
            return super.onStartCommand(intent, flags, startId);
        }

        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }

    }

代碼中可以看出黄虱,啟動一個相同id的前臺GrayInnerService然后立即stopSelf,再以同樣方式啟動主service蔓倍,這樣運行起來的service就已經(jīng)是達(dá)到前臺服務(wù)優(yōu)先級了悬钳,一般情況都?xì)⒉涣怂?/p>

IPC(進(jìn)程間通信)

這里IPC比較簡單盐捷,就是使用bundle實現(xiàn)service到broadcast之間的跨進(jìn)程通信偶翅。此處略過,詳細(xì)請查看代碼碉渡。

自定義廣播權(quán)限

出于安全考慮或者其它特殊需求聚谁,可能會限制廣播接收者,所以這里就用到了自定義權(quán)限滞诺。

  • manifest.xml中定義并使用
<permission
        android:name="android.intent.permission.im.receiver_permission"
        android:protectionLevel="normal" />

    <uses-permission android:name="android.intent.permission.im.receiver_permission" />
    
  • 發(fā)送帶權(quán)限的廣播
contexts.sendBroadcast(intent, "android.intent.permission.im.receiver_permission");

這樣只有申明了此權(quán)限的廣播才能接收到消息形导。

重試機制

這里參考volley框架的重試機制,在創(chuàng)建連接時若失敗5次之內(nèi)會嘗試重連习霹。具體實現(xiàn)請參考DefaultRetryPolicy類朵耕。

<h3 id="catch_crash">進(jìn)程異常恢復(fù)</h3>

實現(xiàn)Thread.UncaughtExceptionHandler接口淋叶,處理異常并恢復(fù)阎曹。

@Override
    public void uncaughtException(Thread thread, Throwable ex) {
        // crash統(tǒng)計打點
        if (ex != null) {
            ex.printStackTrace();
            // 保存錯誤報告文件
            saveCrashInfoToFile(ex);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LogUtil.i(TAG, "uncaughtException crash recovering");
        // 檢查socket狀態(tài)并重連
        ImServiceHelper.getInstance(mContext).reConnect();
    }

代碼地址

補充

  • 弱網(wǎng)情況。
    目前弱網(wǎng)情況未做處理,超時異常會捕獲处嫌,然后進(jìn)入重連機制栅贴。
    給個處理建議:將所有未發(fā)送成功的請求緩存到隊列,待網(wǎng)絡(luò)恢復(fù)后自動發(fā)送熏迹。
  • 連接異常斷開檐薯,如何恢復(fù)。
    除了上面提到了連接重試注暗,及進(jìn)程異程陈疲恢復(fù);代碼中捕獲了任何socket異常友存,并進(jìn)入重連機制祷膳。
  • 健壯的心跳策略。
    心跳策略最好是雙向的屡立,即服務(wù)器和客戶端都應(yīng)有心跳直晨。
    由于demo代碼這塊沒完善,這里簡單說下實現(xiàn)思路: 空閑狀態(tài)膨俐,每三分鐘發(fā)送一個心跳包勇皇。
    server每三分鐘發(fā)一次心跳"ping"到client,client收到后回發(fā)一
    個"pong"給server焚刺,這就完成了一次心跳檢測敛摘。但如果在這三分鐘之
    內(nèi)收到client發(fā)來的數(shù)據(jù),則證明長連接處于正常狀態(tài)乳愉,需要重新記
    時三分鐘再發(fā)送心跳兄淫。 client處理策略可以和server一致。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捕虽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坡脐,更是在濱河造成了極大的恐慌泄私,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件备闲,死亡現(xiàn)場離奇詭異晌端,居然都是意外死亡,警方通過查閱死者的電腦和手機恬砂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門咧纠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泻骤,你說我怎么就攤上這事漆羔∪樾遥” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵钧椰,是天一觀的道長粹断。 經(jīng)常有香客問我,道長嫡霞,這世上最難降的妖魔是什么瓶埋? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮诊沪,結(jié)果婚禮上养筒,老公的妹妹穿的比我還像新娘。我一直安慰自己端姚,他們只是感情好晕粪,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著渐裸,像睡著了一般巫湘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上昏鹃,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天尚氛,我揣著相機與錄音,去河邊找鬼洞渤。 笑死阅嘶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的载迄。 我是一名探鬼主播讯柔,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼护昧!你這毒婦竟也來了魂迄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤捏卓,失蹤者是張志新(化名)和其女友劉穎极祸,沒想到半個月后慈格,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怠晴,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年浴捆,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒜田。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡选泻,死狀恐怖冲粤,靈堂內(nèi)的尸體忽然破棺而出美莫,到底是詐尸還是另有隱情,我是刑警寧澤梯捕,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布厢呵,位于F島的核電站,受9級特大地震影響傀顾,放射性物質(zhì)發(fā)生泄漏襟铭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一短曾、第九天 我趴在偏房一處隱蔽的房頂上張望寒砖。 院中可真熱鬧,春花似錦嫉拐、人聲如沸哩都。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漠嵌。三九已至,卻和暖如春盖呼,著一層夾襖步出監(jiān)牢的瞬間献雅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工塌计, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挺身,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓锌仅,卻偏偏與公主長得像章钾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子热芹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理贱傀,服務(wù)發(fā)現(xiàn),斷路器伊脓,智...
    卡卡羅2017閱讀 134,665評論 18 139
  • 1府寒、TCP狀態(tài)linux查看tcp的狀態(tài)命令:1)、netstat -nat 查看TCP各個狀態(tài)的數(shù)量2)报腔、lso...
    北辰青閱讀 9,427評論 0 11
  • 《UNIX 網(wǎng)絡(luò)編程卷一:套接字聯(lián)網(wǎng)API》筆記 套接字 套接字編程接口株搔,是在 TCP/IP 協(xié)議族中,應(yīng)用層進(jìn)入...
    超net閱讀 5,810評論 2 13
  • 本文前言 微信作為移動互聯(lián)網(wǎng)時代的一款裝機必備的產(chǎn)品纯蛾,承載著拉近人與人之間纤房、人與世界之間距離的使命,已經(jīng)越來越占據(jù)...
    亦楓閱讀 2,017評論 0 1
  • 都說學(xué)生時代是最美好的翻诉,這真的不假炮姨,最佳的黃金時期便是大學(xué)了捌刮,可國家的教育往往會使人產(chǎn)生一種 “我都考上大學(xué)了,已...
    大鳴白閱讀 6,510評論 167 180