欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2021年10月27日 11:46:08   作者:此非夢亦非幻  
這篇文章主要介紹了Java 使用Socket正確讀取數據姿勢,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

前言

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

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

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

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

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

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

Socket使用流程

1、創(chuàng)建socket

2、連接socket

3、獲取輸入輸出流

字節(jié)流:

   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);

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

4、讀寫數據

5、關閉socket

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

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

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

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

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循環(huán)體里面不會執(zhí)行

日常寫法2:

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

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

或者對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();//再判斷一下,是否有可用字節(jié)數或者根據實際情況驗證報文完整性
        if (a > 0) {
            LogUtils.w("========還有剩余:" + a + "個字節(jié)數據沒讀");
            readStreamWithRecursion(output, inStream);
        }
    }
    /**
     * 讀取字節(jié)
     *
     * @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("本次讀取字節(jié)總數:" + size);
        return output.toByteArray();
    }

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

 /**
     * 遞歸讀取流
     *
     * @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 + "個字節(jié)數據沒讀");
                break;
            }
        }
        if (checkExist) {
            if (!checkMessage(buffer, read)) {
                readStreamWithRecursion(output, inStream, timeout);
            }
        }        
    }
    
 /**
     * 讀取等待時間,單位毫秒
     */
    protected int readWait() {
        return 100;
    }
    
    /**
     * 讀取字節(jié)
     *
     * @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("本次讀取字節(jié)總數:" + size);
        return output.toByteArray();
    }

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

使用readreadLine()讀取阻塞問題

日常寫法:

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

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

特別注意:

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

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

使用readLine()注意事項:

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

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

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

  • 2、沒有數據時會阻塞,在數據流異?;驍嚅_時才會返回null
  • 3、使用socket之類的數據流時,要避免使用readLine(),以免為了等待一個換行/回車符而一直阻塞

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

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

總結

實際開發(fā)中,長連接比較復雜,還要考慮心跳,丟包,斷開重連等問題。使用長連接時,要特別注意報文結束符問題,結束符只是用來告訴客戶端或服務端數據已經發(fā)送完畢,客戶端或服務端可以讀取數據了,否則客戶端或服務端會一直阻塞在read()或者readLine()方法。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Java中seata框架的XA模式詳解

    Java中seata框架的XA模式詳解

    這篇文章主要介紹了Java中seata框架的XA模式詳解,Seata?是一款開源的分布式事務解決方案,致力于提供高性能和簡單易用的分布式事務服務,Seata?將為用戶提供了?AT、TCC、SAGA?和?XA?事務模式,為用戶打造一站式的分布式解決方案,需要的朋友可以參考下
    2023-08-08
  • 解析Spring RestTemplate必須搭配MultiValueMap的理由

    解析Spring RestTemplate必須搭配MultiValueMap的理由

    本文給大家介紹Spring RestTemplate必須搭配MultiValueMap的理由,本文通過實例圖文相結合給大家介紹的非常詳細,需要的朋友參考下吧
    2021-11-11
  • 聊聊springboot靜態(tài)資源加載的規(guī)則

    聊聊springboot靜態(tài)資源加載的規(guī)則

    這篇文章主要介紹了springboot靜態(tài)資源加載的規(guī)則,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • spring boot整合RabbitMQ(Direct模式)

    spring boot整合RabbitMQ(Direct模式)

    springboot集成RabbitMQ非常簡單,如果只是簡單的使用配置非常少,springboot提供了spring-boot-starter-amqp項目對消息各種支持。下面通過本文給大家介紹下spring boot整合RabbitMQ(Direct模式),需要的朋友可以參考下
    2017-04-04
  • java解析XML幾種方式小結

    java解析XML幾種方式小結

    本文給大家匯總了4種java解析XML的方法,結合具體的示例,非常的詳細,有需要的小伙伴可以參考下
    2016-01-01
  • Java開發(fā)必備的三大修飾符

    Java開發(fā)必備的三大修飾符

    JAVA的三個修飾:static,final,abstract,在JAVA語言里無處不在,但是它們都能修飾什么組件,修飾組件的含義又有什么限制,總是混淆.所以來總結一下,需要的朋友可以參考下
    2021-06-06
  • Mybatis傳遞多個參數的三種實現方法

    Mybatis傳遞多個參數的三種實現方法

    這篇文章主要介紹了Mybatis傳遞多個參數的三種實現方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • springboot的@Value中#和$區(qū)別詳解

    springboot的@Value中#和$區(qū)別詳解

    這篇文章主要介紹了springboot的@Value中#和$區(qū)別詳解,@Value注解的作用主要可以給屬性直接賦值、也可以讀取配置文件中的值給屬性賦值,需要的朋友可以參考下
    2023-11-11
  • 詳述IntelliJ IDEA提交代碼前的 Code Analysis 機制(小結)

    詳述IntelliJ IDEA提交代碼前的 Code Analysis 機制(小結)

    本篇文章主要介紹了詳述IntelliJ IDEA提交代碼前的 Code Analysis 機制(小結),具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-11-11
  • 通過實例解析synchronized和lock區(qū)別

    通過實例解析synchronized和lock區(qū)別

    這篇文章主要介紹了通過實例解析synchronized和lock區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-12-12

最新評論