Socket通信

前言

socket是套接字,是一個(gè)對(duì) TCP / IP協(xié)議進(jìn)行封裝的編程調(diào)用接口普气,嗯谜疤。。现诀。比較官方規(guī)范的介紹方式夷磕,但如果你剛接觸網(wǎng)絡(luò)編程時(shí),看到這個(gè)解釋可能會(huì)想說(shuō)仔沿,誰(shuí)他媽知道套接字是個(gè)什么鬼坐桩。感覺(jué)就像用原版的英語(yǔ)詞典查一個(gè)生詞,單詞的解釋是一句英語(yǔ)封锉,有幾個(gè)詞不認(rèn)識(shí)绵跷,得遞歸查,內(nèi)心神獸奔騰而過(guò)~-~成福。

簡(jiǎn)單介紹一下socket相關(guān)知識(shí)點(diǎn):


1.協(xié)議

可以先簡(jiǎn)單理解為約定,比如usb接口,定義了usb的各種標(biāo)準(zhǔn),包括它的基本外觀,尺寸,和其他什么參數(shù),然后所有需要usb接口的產(chǎn)品都按照約定來(lái)做,這樣子,隨便買根數(shù)據(jù)線,在哪里都能傳輸數(shù)據(jù),充電,正常工作碾局。那么就可以理解,TCP和UDP作為一種網(wǎng)絡(luò)運(yùn)輸層的協(xié)議奴艾,對(duì)于兩個(gè)網(wǎng)絡(luò)設(shè)備净当,如果都實(shí)現(xiàn)了該協(xié)議,那么在該協(xié)議的基礎(chǔ)上蕴潦,設(shè)備之間的網(wǎng)路運(yùn)輸層就可以正常交互像啼。

2.Socket和Http

網(wǎng)絡(luò)分為5層,最上面兩層分別是應(yīng)用層和運(yùn)輸層潭苞,TCP和UDP是實(shí)現(xiàn)了運(yùn)輸層的協(xié)議忽冻,而Socket是對(duì)TCP和UDP協(xié)議進(jìn)行了封裝,方便上層調(diào)用此疹,HTTP屬于應(yīng)用層甚颂,他們不在一個(gè)層級(jí),本不具備可比性秀菱,運(yùn)輸層協(xié)議是為了約定數(shù)據(jù)傳輸?shù)姆绞秸裎埽瑧?yīng)用層協(xié)議是為了解決數(shù)據(jù)的包裝方式。

舉個(gè)栗子:

從學(xué)校前門到學(xué)校后門衍菱,我們需要送一千本書過(guò)去赶么。

  • 這里書就是要傳輸?shù)臄?shù)據(jù),前門和后門相當(dāng)于需要通信的客戶端和服務(wù)端脊串。
  • 出于運(yùn)輸速度和人力資源分配的考慮辫呻,我們約定書要分成最多100本一捆的格式去運(yùn)輸比較高效清钥,這一千本書要按順序分成1~100,101~200放闺。祟昭。。901~1000怖侦,這樣子依次送篡悟,這就是運(yùn)輸?shù)膮f(xié)議。
    • 那么在學(xué)校后門這里匾寝,因?yàn)槲抑缽那伴T送過(guò)來(lái)的書是每捆100本按順序依次送過(guò)來(lái)搬葬,那么我把這些書按照收到的順序依次拼接起來(lái),那么就保證了些書原本的順序艳悔,即數(shù)據(jù)的完整性急凰,有序性沒(méi)有被破環(huán)。前門運(yùn)輸之前猜年,書是什么樣子抡锈,后門這里拿到后還是什么樣子。
  • 而如果我們約定這些書是用紙箱裝還是用車拖乔外,學(xué)校的后門的地理位置在哪企孩,送到后門后如果沒(méi)有被及時(shí)領(lǐng)走的話,應(yīng)該保存多久袁稽,到期了是扔掉還是怎么處理勿璃。這些除數(shù)據(jù)內(nèi)容本身以外的約定,Http協(xié)議在解決的問(wèn)題推汽。
  • 如果一定要把Socket和Http拿來(lái)比較补疑,差別就是在工作方式上,Http通信中只有客戶端向服務(wù)端請(qǐng)求了數(shù)據(jù)歹撒,服務(wù)端才能響應(yīng)并發(fā)數(shù)據(jù)給客戶端莲组,而Socket通信中,只要客戶端和服務(wù)端之間建立了連接暖夭,那么在客戶端沒(méi)有請(qǐng)求數(shù)據(jù)的情況下锹杈,服務(wù)端可以主動(dòng)向客戶端發(fā)送數(shù)據(jù)。

3.TCP和UDP

其實(shí)在網(wǎng)絡(luò)編程中迈着,TCP的更為常用一些竭望,這里簡(jiǎn)單聊一下區(qū)別
TCP 3次握手舉例:

Client:“Service,我要連”
Service: “好裕菠,我知道你要連咬清,同意Client連接”
Client:“哦,我知道你知道我要連”

  • TCP:面向連接、面向字節(jié)流旧烧、雙向通信影钉、 可靠
    • 面向連接:TCP在客戶端和服務(wù)端數(shù)據(jù)交互之前,有一個(gè)3次握手確認(rèn)連接的過(guò)程掘剪,只有客戶端和服務(wù)端都確認(rèn)了彼此的連接狀態(tài)平委,才會(huì)開(kāi)始傳輸數(shù)據(jù)。
      • 以上夺谁,就可以建立連接通道了廉赔,這樣的好處是避免網(wǎng)絡(luò)延時(shí)等情況下,Client發(fā)送連接請(qǐng)求后予权,Service沒(méi)收到,而等到Client已經(jīng)不需要和Service通信后浪册,Service才收到連接請(qǐng)求扫腺,如果直接就這樣子建立通道了,會(huì)造成資源浪費(fèi)村象。
      • 即然說(shuō)到連接的3次握手笆环,那就有必要提一下TCP斷開(kāi)連接的4次回收,即任一方發(fā)送“我要斷開(kāi)連接的通知”厚者,接收方回復(fù)“已經(jīng)知曉你斷開(kāi)連接了”躁劣,4次是因?yàn)槿我环蕉伎梢园l(fā)送斷開(kāi)連接的消息。
    • 面向字節(jié)流:流是字符序列库菲。對(duì)于TCP而言账忘,傳輸?shù)膱?bào)文長(zhǎng)度有最大限制,對(duì)于更大的數(shù)據(jù)而言熙宇,就必須把該數(shù)據(jù)分割成一塊塊的數(shù)據(jù)塊鳖擒,全部傳輸完成后,在拼接成原始數(shù)據(jù)烫止。
    • 可靠:按順序傳輸數(shù)據(jù)蒋荚、不丟失、不重復(fù)
  • UDP:無(wú)連接的馆蠕、面向報(bào)文期升、不可靠
    • 無(wú)連接、不可靠:不需要像TCP那樣互躬,建立連接后通訊播赁,它只需要數(shù)據(jù)要送到哪里去,就可以開(kāi)始傳輸數(shù)據(jù)吼渡,至于數(shù)據(jù)是否丟失行拢,一概不管
    • 面向報(bào)文:數(shù)據(jù)有多大,UDP就一次性傳輸多大,不做切割數(shù)據(jù)的動(dòng)作

使用方式:


  • 客戶端實(shí)現(xiàn)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_client_tcp);
    ButterKnife.bind(this);
    //線程池
    pools = Executors.newCachedThreadPool();
    //啟動(dòng)服務(wù)端
    startService(new Intent(this, ServiceTcp.class));
}

這里我們創(chuàng)建一個(gè)Activity命名為ClientTcp左為客戶端
方便起見(jiàn)舟奠,用線程池替代創(chuàng)建線程執(zhí)行任務(wù)

pools.execute(new Runnable() {
                @Override
                public void run() {
              //在連接成功以前,會(huì)循環(huán)嘗試連接知道成功為止
                    while (mSocket == null || !mSocket.isConnected()) {
                        try {
                          //這里構(gòu)造socket傳入ip和端口號(hào),拿到socket對(duì)象
                            mSocket = new Socket("localhost", 2000);
                        } catch (final IOException e) {
                            e.printStackTrace();
                          //toast要切換到主線程中執(zhí)行
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(ClientTcp.this, "連接失敗" + "\r\n" + "一秒后重連" + "\r\n" + e.getMessage(), Toast.LENGTH_SHORT).show();
                                }
                            });
                            try {
                                Thread.sleep(1000);     //延時(shí)1秒重連
                            } catch (InterruptedException e1) {
                                e1.printStackTrace();
                            }
                        }
                    }
                    try {
                    //注意,在上面連接成功以后代碼才會(huì)走到這里,否則在while循環(huán)那里是阻塞的狀態(tài)
                        br = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
                        os = mSocket.getOutputStream();//初始化輸入輸出流
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(ClientTcp.this, "連接成功", Toast.LENGTH_SHORT).show();
                        }
                    });
                    //死循環(huán)接受消息
                    acceptMessage(br);
                }
            });

在子線程我,我們嘗試連接Service段socket,做了連接失敗后重新連接的處理以及輸入輸出流的初始化,接下來(lái),我們看看acceptMessage(br)方法做了在接收數(shù)據(jù)時(shí)做了怎樣的處理

private void acceptMessage(BufferedReader br) {
    while (mSocket.isConnected()){
        try {
            while (!br.ready()){} //當(dāng)流中沒(méi)有數(shù)據(jù)的時(shí)候,會(huì)一直阻塞在這里
            final String response = br.readLine();
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //將用以標(biāo)記換行的符號(hào)"~~"還原,并切換到主線程中顯示
                    String[] split = response.split("~~");
                    mShowMessage.setText(split[0] + "\n" + split[1]);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

當(dāng)socket中獲取到的輸入流中沒(méi)有數(shù)據(jù)時(shí),下面的代碼不會(huì)執(zhí)行,一旦有數(shù)據(jù),就會(huì)調(diào)用readLine()方法讀出字符,這里做了一個(gè)小處理:服務(wù)端把接受的詳細(xì)acceptMessage和要回復(fù)的消息responseMessage以acceptMessage + "\n" + responseMessage憑借換行符后發(fā)給客戶端,但是客戶端在執(zhí)行readLinea()讀取數(shù)據(jù)時(shí),讀到"\n"便會(huì)停止讀取,如果沒(méi)有讀到取"\n",便會(huì)一直阻塞 在這里,這也是為什么客戶端和服務(wù)端在要發(fā)送的消息字符串的末尾都會(huì)添加"\n"竭缝。所以客戶端接收到服務(wù)端的消息后,將臨時(shí)定義的“~~”再替換回?fù)Q行符號(hào)沼瘫,讓textview顯示的時(shí)后能方便區(qū)分發(fā)送的和接受的消息

case R.id.sendMessage:
            String str = mEt.getText().toString();
            if (os != null && !TextUtils.isEmpty(str)){
                try {
                    //在字符串末尾拼接換行符,避免服務(wù)端socket讀取數(shù)據(jù)時(shí)阻塞線程
                    os.write((str + "\r\n").getBytes());
                    os.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            break;

發(fā)送很簡(jiǎn)單,需要注意一下flush()和close的區(qū)別,close()會(huì)關(guān)閉掉流,在關(guān)閉掉之前會(huì)刷新一次流中的數(shù)據(jù),那么這個(gè)流接下來(lái)就不能使用了,而flush刷新后,可以繼續(xù)使用

  • 服務(wù)端實(shí)現(xiàn)

@Override
public void onDestroy() {
    super.onDestroy();
    isServideTcpDestory = true;
}
private class TcpAcceptRunnable implements Runnable {
    protected ServerSocket mServerSocket;
    @Override
    public void run() {
        try {
            mServerSocket = new ServerSocket(2000);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (!isServideTcpDestory) {
            try {
                //這里是一個(gè)阻塞方法,如果沒(méi)有客戶端請(qǐng)求連接,線程會(huì)停在這里等待
                final Socket socket = mServerSocket.accept();
                mThreadPools.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            responseTcpClient(socket);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服務(wù)端,只要在服務(wù)開(kāi)啟時(shí),執(zhí)行這個(gè)Runnable就可以了,在服務(wù)ondestory時(shí),會(huì)將isServideTcpDestory值設(shè)置為true,從而終止while死循環(huán)抬纸。值得一提的是,new ServiceSocket(2000)會(huì)使該線程監(jiān)聽(tīng)自己的2000端口,這里的mServerSocket.accept()方法會(huì)產(chǎn)生一個(gè)socket,但知道有客戶端請(qǐng)求連接2000端口位置,線程會(huì)一直阻塞在這里。拿到和特定客戶端對(duì)應(yīng)的服務(wù)端Socket對(duì)象后耿戚,我們看看在子線程中responseTcpClient(socket)方法作了什么處理

 private void responseTcpClient(Socket client) throws IOException {
    //操作流
    BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
    String acceptResponse = "";
    while (!isServideTcpDestory) {
        while (!br.ready()) { }
        acceptResponse = br.readLine();
        String responseStr = mStrings[new Random().nextInt(mStrings.length)];
        //因?yàn)閞eadLine()方法在讀到換行符之前會(huì)一直等待,這里用"~~"代替換行,在clientTcp中拿到數(shù)據(jù)再替換成換行符設(shè)置給textview
        bw.write("sendMessage: " + acceptResponse + "~~" + "receiveResponse: " + responseStr + "\r\n");
        bw.flush();
    }
    br.close();
    bw.close();
    client.close();
}

看起來(lái)湿故,和在客戶端的acceptMessage(BufferedReader br)方法中并沒(méi)有什么差別

我們來(lái)看看最后的效果

SocketDemoGIF.jpg

如果需要Demo源碼,請(qǐng)移步GitHub: SocketDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膜蛔,一起剝皮案震驚了整個(gè)濱河市坛猪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌皂股,老刑警劉巖墅茉,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呜呐,居然都是意外死亡就斤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門蘑辑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洋机,“玉大人,你說(shuō)我怎么就攤上這事洋魂”疗欤” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵副砍,是天一觀的道長(zhǎng)刁标。 經(jīng)常有香客問(wèn)我,道長(zhǎng)址晕,這世上最難降的妖魔是什么膀懈? 我笑而不...
    開(kāi)封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮谨垃,結(jié)果婚禮上启搂,老公的妹妹穿的比我還像新娘。我一直安慰自己刘陶,他們只是感情好胳赌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著匙隔,像睡著了一般疑苫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天捍掺,我揣著相機(jī)與錄音撼短,去河邊找鬼。 笑死挺勿,一個(gè)胖子當(dāng)著我的面吹牛曲横,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播不瓶,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼禾嫉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蚊丐?” 一聲冷哼從身側(cè)響起熙参,我...
    開(kāi)封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎麦备,沒(méi)想到半個(gè)月后孽椰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泥兰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年弄屡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了题禀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞋诗。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖迈嘹,靈堂內(nèi)的尸體忽然破棺而出削彬,到底是詐尸還是另有隱情,我是刑警寧澤秀仲,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布融痛,位于F島的核電站,受9級(jí)特大地震影響神僵,放射性物質(zhì)發(fā)生泄漏雁刷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一保礼、第九天 我趴在偏房一處隱蔽的房頂上張望沛励。 院中可真熱鬧,春花似錦炮障、人聲如沸目派。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)企蹭。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谅摄,已是汗流浹背徒河。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留螟凭,地道東北人虚青。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像螺男,于是被迫代替她去往敵國(guó)和親棒厘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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

  • 1)OSI與TCP/IP各層的結(jié)構(gòu)與功能,都有哪些協(xié)議拷淘。 OSI分層 (7層):物理層各墨、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層启涯、傳輸層...
    ldlywt閱讀 2,308評(píng)論 0 26
  • socket通信原理 socket又被叫做套接字,它就像連接到兩端的插座孔一樣,通過(guò)建立管道贬堵,將兩個(gè)不同的進(jìn)程之間...
    jiodg45閱讀 1,126評(píng)論 0 1
  • 前言 我們深諳信息交流的價(jià)值,那網(wǎng)絡(luò)中進(jìn)程之間如何通信结洼,如我們每天打開(kāi)瀏覽器瀏覽網(wǎng)頁(yè)時(shí)黎做,瀏覽器的進(jìn)程怎么與web服...
    Chars閱讀 2,981評(píng)論 2 117
  • 一、網(wǎng)絡(luò)各個(gè)協(xié)議:TCP/IP松忍、SOCKET蒸殿、HTTP等## 網(wǎng)絡(luò)七層由下往上分別為物理層、數(shù)據(jù)鏈路層鸣峭、網(wǎng)絡(luò)層宏所、傳...
    Mighty_man閱讀 443評(píng)論 0 2
  • 本文參考:1. socket 百度百科 2.博主:張明偉 要了解Socket,我們就必須要和http一起來(lái)了解,看...
    DXSmile閱讀 8,068評(píng)論 3 30