中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java如何使用Socket正確讀取數據姿勢

發布時間:2021-10-27 13:36:54 來源:億速云 閱讀:665 作者:小新 欄目:開發技術

小編給大家分享一下Java如何使用Socket正確讀取數據姿勢,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

    前言

    平時日常開發用得最多是Http通訊,接口調試也比較簡單的,也有比較強大的框架支持(OkHttp)。

    個人平時用到socket通訊的地方是Android與外設通訊,Android與ssl服務通訊,這種都是基于TCP/IP通訊,而且服務端和設備端協議都是不能修改的,只能按照相關報文格式進行通信。

    但使用socket通訊問題不少,一般有兩個難點:

    1、socket通訊層要自己寫及IO流不正確使用,遇到讀取不到數據或者阻塞卡死現象或者數據讀取不完整

    2、請求和響應報文格式多變(json,xml,其它),解析麻煩,如果是前面兩種格式都簡單,有對應框架處理,其它格式一般都需要自己手動處理。

    本次基于第1點問題做了總結,歸根結底是使用read()或readLine()導致的問題

    Socket使用流程

    1、創建socket

    2、連接socket

    3、獲取輸入輸出流

    字節流:

       InputStream  mInputStream = mSocket.getInputStream();
       OutputStream  mOutputStream = mSocket.getOutputStream();

    字符流:

      BufferedReader mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "UTF-8"));
      PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "UTF-8")), true);

    至于實際使用字節流還是字符流,看實際情況使用。如果返回是字符串及讀寫與報文結束符(/r或/n或/r/n)有關,使用字符流讀取,否則字節流。

    4、讀寫數據

    5、關閉socket

    如果是Socket短連接,上面五個步驟都要走一遍;

    如果是Socket長連接,只需關注第4點即可,第4點使用不慎就會遇到上面出現的問題。

    實際開發中,長連接使用居多,一次連接,進行多次收發數據。

    特別注意:使用長連接不能讀完數據后立馬關閉輸入輸出流,必須再最后不使用的時候關閉

    Socket數據讀寫

    當socket阻塞時,必須設置讀取超時時間,防止調試時,socket讀取數據長期掛起。

    mSocket.setSoTimeout(10* 1000);  //設置客戶端讀取服務器數據超時時間

    使用read()讀取阻塞問題

    日常寫法1:

     mOutputStream.write(bytes);
     mOutputStream.flush();
    byte[] buffer = new byte[1024];
    int n = 0;
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    while (-1 != (n = mInputStream .read(buffer))) {
        output.write(buffer, 0, n);
    }
    //處理數據
      output.close();
    byte[] result = output.toByteArray();

    上面看似沒有什么問題,但有時候會出現mInputStream .read(buffer)阻塞,導致while循環體里面不會執行

    日常寫法2:

    mOutputStream.write(bytes);
    mOutputStream.flush();
    int  available = mInputStream.available();
    byte[] buffer = new byte[available];
    in.read(buffer);

    上面雖然不阻塞,但不一定能讀取到數據,available 可能為0,由于是網絡通訊,發送數據后不一定馬上返回。

    或者對mInputStream.available()修改為:

     int available = 0;
    while (available == 0) {
        available = mInputStream.available();
    }

    上面雖然能讀取到數據,但數據不一定完整。

    而且,available方法返回估計的當前流可用長度,不是當前通訊流的總長度,而且是估計值;read方法讀取流中數據到buffer中,但讀取長度為1至buffer.length,若流結束或遇到異常則返回-1。

    最終寫法(遞歸讀取):

     /**
         * 遞歸讀取流
         *
         * @param output
         * @param inStream
         * @return
         * @throws Exception
         */
        public void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {
            long start = System.currentTimeMillis();
            while (inStream.available() == 0) {
                if ((System.currentTimeMillis() - start) > 20* 1000) {//超時退出
                    throw new SocketTimeoutException("超時讀取");
                }
            }
            byte[] buffer = new byte[2048];
            int read = inStream.read(buffer);
            output.write(buffer, 0, read);
            SystemClock.sleep(100);//需要延時以下,不然還是有概率漏讀
            int a = inStream.available();//再判斷一下,是否有可用字節數或者根據實際情況驗證報文完整性
            if (a > 0) {
                LogUtils.w("========還有剩余:" + a + "個字節數據沒讀");
                readStreamWithRecursion(output, inStream);
            }
        }
        /**
         * 讀取字節
         *
         * @param inStream
         * @return
         * @throws Exception
         */
        private byte[] readStream(InputStream inStream) throws Exception {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            readStreamWithRecursion(output, inStream);
            output.close();
            int size = output.size();
            LogUtils.i("本次讀取字節總數:" + size);
            return output.toByteArray();
        }

    上面這種方法讀取完成一次后,固定等待時間,等待完不一定有數據,若沒有有數據,響應時間過長,會影響用戶體驗。我們可以再優化一下:

     /**
         * 遞歸讀取流
         *
         * @param output
         * @param inStream
         * @return
         * @throws Exception
         */
        public void readStreamWithRecursion(ByteArrayOutputStream output, InputStream inStream) throws Exception {
            long start = System.currentTimeMillis();
            int time =500;//毫秒,間看實際情況
            while (inStream.available() == 0) {
                if ((System.currentTimeMillis() - start) >time) {//超時退出
                    throw new SocketTimeoutException("超時讀取");
                }
            }
            byte[] buffer = new byte[2048];
            int read = inStream.read(buffer);
            output.write(buffer, 0, read);
           int wait = readWait();
            long startWait = System.currentTimeMillis();
            boolean checkExist = false;
            while (System.currentTimeMillis() - startWait <= wait) {
                int a = inStream.available();
                if (a > 0) {
                    checkExist = true;
                    //            LogUtils.w("========還有剩余:" + a + "個字節數據沒讀");
                    break;
                }
            }
            if (checkExist) {
                if (!checkMessage(buffer, read)) {
                    readStreamWithRecursion(output, inStream, timeout);
                }
            }        
        }
        
     /**
         * 讀取等待時間,單位毫秒
         */
        protected int readWait() {
            return 100;
        }
        
        /**
         * 讀取字節
         *
         * @param inStream
         * @return
         * @throws Exception
         */
        private byte[] readStream(InputStream inStream) throws Exception {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            readStreamWithRecursion(output, inStream);
            output.close();
            int size = output.size();
            LogUtils.i("本次讀取字節總數:" + size);
            return output.toByteArray();
        }

    上面這種延遲率大幅降低,目前正在使用該方法讀取,再也沒有出現數據讀取不完整和阻塞現象。不過這種,讀取也要注意報文結束符問題,何時讀取完畢問題。

    使用readreadLine()讀取阻塞問題

    日常寫法:

     mPrintWriter.print(sendData+ "\r\n");   
     mPrintWriter.flush();
     String msg = mBufferedReader.readLine();
     //處理數據

    細心的你發現,發送數據時添加了結束符,如果不加結束符,導致readLine()阻塞,讀不到任何數據,最終拋出SocketTimeoutException異常

    特別注意:

    報文結束符:根據實際服務器規定的來添加,必要時問后端開發人員或者看接口文檔是否有說明

    不然在接口調試上會浪費很多寶貴的時間,影響后期功能開發。

    使用readLine()注意事項:

    • 1、讀入的數據要注意有/r或/n或/r/n

    這句話意思是服務端寫完數據后,會打印報文結束符/r或/n或/r/n;

    同理,客戶端寫數據時也要打印報文結束符,這樣服務端才能讀取到數據。

    • 2、沒有數據時會阻塞,在數據流異常或斷開時才會返回null

    • 3、使用socket之類的數據流時,要避免使用readLine(),以免為了等待一個換行/回車符而一直阻塞

    上面長連接是發送一次數據和讀一次數據,保證了當次通訊的完整性,必須要時需要同步處理。

    也有長連接,客戶端開線程循環阻塞等待服務端數據發送數據過來,比如:消息推送。平時使用長連接都是分別使用不同的命令發送數據且接收數據,來完成不同的任務。

    看完了這篇文章,相信你對“Java如何使用Socket正確讀取數據姿勢”有了一定的了解,如果想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    聂拉木县| 城固县| 岳阳市| 嘉峪关市| 迁安市| 弥勒县| 光泽县| 化德县| 西宁市| 新宁县| 山西省| 泸溪县| 呼伦贝尔市| 永兴县| 昌吉市| 胶南市| 南宁市| 哈密市| 高碑店市| 宁城县| 乐清市| 涟源市| 资阳市| 灵武市| 秀山| 温泉县| 贞丰县| 旺苍县| 红安县| 漳浦县| 黑龙江省| 五寨县| 个旧市| 开江县| 瑞安市| 邯郸市| 会东县| 古交市| 汉川市| 托克托县| 常德市|