1. 程式人生 > >Socket:半包及粘包的一種處理方法

Socket:半包及粘包的一種處理方法

  1. 先說下思路:
  • 當出現半包情況時,原本一整段的訊息被分成兩部分或多部分,導致用來判斷訊息是否完整的函式無法判斷,所以這時候就將先到達的內容儲存起來,用於與後到達的內容連線在一起
  • 當出現粘包情況時,訊息A和訊息B緊密的連線在一起,這就導致處理訊息的函式如果不將訊息分開,就很容易導致訊息丟失或者程式卡在這裡。所以就根據資料頭中的長度,判斷收到的訊息的長度是否一致,如果訊息長度比資料頭中記錄的長度大,就將粘包的兩個訊息分開
  1. 首先是建立客戶端監聽服務端訊息的函式:
private void getDataFormServer(){
        isRun = true;
        while(isRun){
            try{
                // 建立輸入流物件InputStream
                is = socket.getInputStream();
                int length = is.available();	//獲取訊息長度
                
                // 建立一個數組,將is中的位元組流輸入到這個陣列中
                int[] arr = new int[length];
                for(int i=0; i<length; i++){
                    arr[i] = is.read();
                }

                if(length > 0){
	                 //半包情況處理:將這次的資料和儲存的資料加在一起
	                if(arr_storage != null){
	                    arr = add2intArray2oneArray(arr_storage, arr);
	                }
                    //對資訊進行判斷及處理
                    detectionDataHead(arr);
                }
            }catch (Exception e){
                isRun = false;
                //writeLogToFile(String string):測試的時候將資訊輸出到一個日誌檔案中
                writeLogToFile("get Data is fail! " + e.toString());
            }
        }
    }
  1. 對資料頭進行判斷。(PS:我設定的資料頭:兩位識別符號+4位CRC+4位資料長度)
private void detectionDataHead(int[] arr){

        //識別符號檢查:請看第4點
        if(!detectionDataSign(arr)){
            writeLogToFile("識別符號錯誤");
            return;
        }

        //資料長度檢查:請看第5點
        if(!detectionDataLength(arr)){
            return;
        }
        //CRC檢查:請看第6點
        if(!detectionDataCrc(arr)){
            return;
        }
        //資料無誤
        arr_storage = null;
        //接下來就是對正確的訊息進行處理。比如我的:
        JSONObject jsonObject = serverData2JsonObject(arr);	//將訊息轉換成json物件
        detectionJsonObject(jsonObject);	//這是對json物件中的資料進行解析處理
    }
  1. 標識頭檢查:
private boolean detectionDataSign(int[] arr){
        if(arr[0] != 67 && arr[1] != 66){
            arr_storage = null;
            return false;
        }
        return true;
    }
  1. 資料長度檢查:
private boolean detectionDataLength(int[] arr){
        int length = 0;
        
        //計算資料長度
        for(int i=6; i<10; i++){
            length *= 256;    /*因為記錄長度使用十六進位制記錄的,但是int中變成了十進位制,所以要轉換*/
            length += arr[i];
        }
        
        //如果 訊息長度 == 資料長度 + 10,代表沒問題
        if(arr.length == length+10){
            return true;
        }
        
        //出現 半包問題
        else if(arr.length < length+10){
            arr_storage = arr;	//arr_storage 是設定的用來儲存訊息的int陣列
            return false;
        }
        
        //出現 粘包問題
        else if(arr.length > length+10){
        	//將資料一分為二,分別進行訊息判斷,看看是否是完整的資料。
            int[] arr1 = new int[length + 10];
            int[] arr2 = new int[length - arr.length];
            for(int i = 0; i < length + 10; i++){
                arr1[i] = arr[i];
            }
            for(int j = length; j<arr.length; j++){
                arr2[j] = arr[j];
            }
            detectionDataHead(arr1);
            detectionDataHead(arr2);
        }
        return false;
    }
  1. CRC檢查:
private boolean detectionDataCrc(int[] arr){
        int crc = 0;
        /*因為訊息中記錄CRC時使用十六進位制記錄的,但是int中變成了十進位制,所以要轉換。*/
        for(int i=2; i<6; i++){
            crc *= 256;
            crc += arr[i];
        }
        for(int j = 10; j < arr.length; j++){
            crc -= arr[j];
        }
        if(crc == 0)
            return true;
        writeLogToFile("CRC 錯誤");
        return false;
    }```