Java實現(xiàn)Socket網(wǎng)絡(luò)編程(五)

在看到本文之前,如果讀者沒看過筆者的前文 Java實現(xiàn)Socket網(wǎng)絡(luò)編程(四),請先翻閱。

接下來,筆者對幾個核心點進行剖析:
1、如果讀者是初次進行Socket網(wǎng)絡(luò)編程開發(fā),起初可能會因為端口的使用不當(dāng),而導(dǎo)致Socket無法連接。所以讀者可以在編程前進行測試,這里筆者提供了一個方法:

    /**
     * 用于檢測能連接到的端口號
     */
    public static void scan(String host) {
        Socket socket = null;

        for (int port = 1024; port < 10055; port++) {
            try {
                socket = new Socket(host, port);
                System.out.println(socket);
            } catch (IOException e) {
                continue;
            } finally {
                try {
                    if (socket != null) {
                        socket.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

2、當(dāng)發(fā)現(xiàn)程序只能在本機運行,而在其他機器上不能運行時,是因為讀者所使用的ip地址是本機ip地址,如果采用 loop back 地址 "127.0.0.1",則可以在任意機器上運行。(本案例實際上是本機服務(wù)器與本機客戶端的Socket通信)

3、筆者在測試過程中,發(fā)現(xiàn)了這樣一個現(xiàn)象:服務(wù)器檢測客戶端斷開,零延時;而客戶端檢測服務(wù)器斷開,有明顯延時(延時長短和運行機器的速度有關(guān))。

這是什么原因?qū)е碌哪兀?strong>經(jīng)測試發(fā)現(xiàn),服務(wù)器檢測客戶端斷開,心跳包異常的捕獲速度快于讀寫錯誤的捕獲,從而零延時。而客戶端檢測服務(wù)器斷開,讀寫錯誤的捕獲速度快于心跳包異常的捕獲速度,導(dǎo)致有延時。

這種現(xiàn)象的產(chǎn)生,是由于Java底層對”服務(wù)器監(jiān)聽客戶端斷開“及”客戶端監(jiān)聽服務(wù)器斷開“的實現(xiàn)機制不一樣,導(dǎo)致了兩者之間的速度差異。

既然是實現(xiàn)機制的原因,那我們是否就沒有解決的方法呢?顯然不屈不饒的程序員不會就此善罷甘休。

服務(wù)器檢測客戶端斷開,零延時,我們不需要再過多干預(yù);我們要干預(yù)客戶端檢測服務(wù)器斷開,以使得比捕獲讀寫異常的速度更快地發(fā)現(xiàn)服務(wù)器斷開。

筆者經(jīng)過多次嘗試:
①首先是想到自定義一個結(jié)束符,例如"bye",希望通過服務(wù)器傳遞”bye“給客戶端,客戶端就”意識到“服務(wù)器關(guān)閉。這可以做到,但是存在一個漏動,如果”bye“是由使用者在服務(wù)器對話框發(fā)送過去的呢?那樣客戶端就會“以為”服務(wù)器斷開。

既然這樣,筆者就想著改進,把結(jié)束符“bye”改成轉(zhuǎn)義字符,如"\\u0025",讓用戶不容易輸入,經(jīng)過數(shù)次測試發(fā)現(xiàn),用戶毫無一例會成功輸入"\\u0025"結(jié)束符,看起來毫無Bug,但這顯然還是一個漏動,哪怕只有萬分之一的可能?

②為了進一步改進,筆者想著自定義一種數(shù)據(jù)協(xié)議格式:
【服務(wù)器是否啟動標(biāo)志位】【要接收的數(shù)據(jù)】

這看起來不錯,但前面已經(jīng)提到,客服端讀取數(shù)據(jù)要在for循環(huán)里,如果用String類的startsWith("啟動標(biāo)志位")方法判斷,則客戶端顯示數(shù)據(jù)由于無法判斷服務(wù)器一次性發(fā)送內(nèi)容的結(jié)束,會導(dǎo)致如下輸出結(jié)果:

s
sa
say
say h
say he
say hel
say hell
say hello
say hello!

③既然如此,也就是要為數(shù)據(jù)提供結(jié)束符,以判斷數(shù)據(jù)長度,筆者再一次修改數(shù)據(jù)協(xié)議格式:【要接收的數(shù)據(jù)】【服務(wù)器是否啟動標(biāo)志位】

通過String類的endsWith("啟動標(biāo)志位")進行檢測

然而,這又產(chǎn)生了一個新問題,當(dāng)用戶直接輸入”服務(wù)器斷開標(biāo)志位“,客戶端又再一次”認(rèn)為“服務(wù)器已斷開。

④筆者進一步考慮,如果加上長度判斷,當(dāng)輸入內(nèi)容s.length()>"標(biāo)志為長度"且endsWith("啟動標(biāo)志位")進行檢測。

心想理應(yīng)成功,沒料到又出現(xiàn)這樣的一種情況:當(dāng)用戶輸入任意長度字符+”服務(wù)器斷開標(biāo)志位“時,客戶端又再一次”認(rèn)為“服務(wù)器已斷開。

⑤歷經(jīng)多次挫折,筆者最后對比使用C#的Socket網(wǎng)絡(luò)編程實現(xiàn),研究了其 Send()方法和 Receive()方法(Send方法用于發(fā)送數(shù)據(jù),Receive方法用于接收數(shù)據(jù),C#封裝了底層輸入輸出流的實現(xiàn),而Java沒有,所以需要進行輸入輸出流的操作)

總結(jié)出一種方法:模擬C#的Receive函數(shù)(該函數(shù)具有檢測服務(wù)器是否斷開,且能返回接收數(shù)據(jù)長度)

筆者定義了以下的數(shù)據(jù)協(xié)議格式,徹底解決了”客戶端延時發(fā)現(xiàn)服務(wù)器斷開“、”發(fā)送數(shù)據(jù)無邊界“的問題:【服務(wù)器是否啟動標(biāo)志位】【接收數(shù)據(jù)的長度】【要接收的數(shù)據(jù)】

在發(fā)送數(shù)據(jù)前進行包裝

   String message = Common.OK; // 代表服務(wù)器正常連接
   String t = "server " + Common.IP + ":" + Common.PORT + " "
                        + jtaSendMessage.getText();
   /**
    *  封裝發(fā)送數(shù)據(jù)的長度
    */
   String c = "" + t.length();
   if (c.length() < 2) {
       c = "000" + c;
   } else if (c.length() < 3) {
       c = "00" + c;
   } else if (c.length() < 4) {
       c = "0" + c;
   }

   message += c + t;

在收數(shù)據(jù)時進行解封

   // 使用"GBK"編碼讀取中文
   brIn = new BufferedReader(new InputStreamReader(
                        mSocket.getInputStream(), "GBK"));

   String s = "";// 記錄每次讀取的內(nèi)容
   int count = -10;// 記錄每次讀取內(nèi)容的長度
   // 接收內(nèi)容并把內(nèi)容添加到信息接收區(qū)
   for (int c = brIn.read(); c != -1; c = brIn.read()) {
        s += (char) c + "";
        count++;// 讀取的長度
        // 如果服務(wù)器連接且一次數(shù)據(jù)接收完成
        if (s.startsWith(Common.OK) && s.length() > 10
                && count == Integer.parseInt((s.substring(6, 10)))) {
                        ClientMain.jtaReceivedMessage.append(s.substring(10)
                                + "\n");
                        count = -10;
                        s = "";
        }// 服務(wù)器斷開且一次數(shù)據(jù)接收完成
        else if (s.startsWith(Common.ERROR) && s.length() > 10
                && count == Integer.parseInt((s.substring(6, 10)))) {
                        ClientMain.jtaReceivedMessage.append(s.substring(10)
                                + "\n");
                        count = -10;
                        s = "";
                        ClientMain.jlConnect.setText("Out Of Connect.");
        }
        // 滾動到底端
        ClientMain.jtaReceivedMessage
                            .setCaretPosition(ClientMain.jtaReceivedMessage
                                    .getText().length());
    }

以上為本次案例的全部內(nèi)容,最后,筆者在github上給出了這個案例的完整源碼和可運行文件Socket網(wǎng)絡(luò)編程,供讀者學(xué)習(xí)思考。

謝謝支持!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,391評論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 1、TCP狀態(tài)linux查看tcp的狀態(tài)命令:1)、netstat -nat 查看TCP各個狀態(tài)的數(shù)量2)、lso...
    北辰青閱讀 9,715評論 0 11
  • 緣起 上節(jié)氣課老師推薦的,以前看《金字塔原理》沒注意有這本書。從圖書館借后,書要到期了,看掉還了。 內(nèi)容 導(dǎo)論:何...
    im天行閱讀 1,280評論 0 4
  • 濕畫法:水分比例多,而顏料的比例少。利用水分使顏色充分融合。 作畫時,畫筆上的水分較多。趁前一筆還沒干時,接入下一...
    魚的微記憶閱讀 1,587評論 1 6

友情鏈接更多精彩內(nèi)容