Socket中粘包的坑的處理
經常在網上看到很多類似於粘包的問題以及處理,但是因為這個跟自己本身的工作內容關係不大,所以也就沒怎麼了解。結果今天就被坑了一大把。
起因
最近由於有個投屏的軟體需要模擬多個客戶端進行連線投屏,所以用java寫了個客戶端模擬多個同時進行併發同屏的情況。而他的協議過程是這樣子的。當你傳送開始投屏的命令一樣,接收端也就是我們所說的服務端會回覆幾條socket的資訊(這個是重點),但是因為我們是模擬客戶端的,所以並不是所有的資料都是我關心的。只有其中一條關於回覆的埠的資訊是需要接收處理。所以就有了坑的問題了。
問題
在測試的過程中發現,經常是有概率的出現模擬了4個客戶端進行併發時服務端只回復了3條關於埠的資料,總會有概率的少了一兩條。那問題總得定位到底是程式碼處理的問題,還是說是機器本身就沒有收到對應的包呢。
以下是wireshark抓包的結果
實際證明是有收到這個回覆包的, 那麼就證明確實是程式碼的問題了。那程式碼問題在哪 我們看看出錯的程式碼
byte srcArray[] = new byte[1024];
int count = socket.getInputStream().read(srcArray);
byte[] msgLenArray = new byte[4];
System.arraycopy(srcArray, 0, msgLenArray, 0, 4);
int msgLen = (MsgUtils.bytesToInt2(msgLenArray));
...
if (temp[9] == 1 ) {
switch (temp[10]) {
case 101:
Msg.CMsgConnectResp cMsgConnectResp = Msg.CMsgConnectResp.parseFrom(msg);
log.append("收到指令型別:" + (temp[9]) + ", 指令號:" + temp[10] + "連線結果:" + cMsgConnectResp.getResultType() + ", 接收端版本:" + cMsgConnectResp.getReceiverVersion() + ", 支援最大視窗數量:" + cMsgConnectResp.getMaxWindowCount() + "\n");
...
程式碼實際很簡單就是宣告一個byte的陣列,每次讀1024個位元組。再來將讀取到的資料進行解析,表面上看起來沒什麼問題。
可是細想一個問題。就是剛才我們提到的,當我們進行投屏的時候,服務端是會連續回覆幾個socket的資料的,然而我們一下子讀了那麼1024個位元組的資料,就會把幾個資料包都讀到一起,那如果幸運的話,回覆埠的資料先到了,嗯 沒問題,那如果是其他資料型別先到了,會發生什麼事情呢。。很悲劇。資料包會被丟棄掉。
解決
既然遇到這個問題,就肯定有解決的方法。下來看看解決的方式
byte srcArray[] = new byte[11];
socket.getInputStream().read(srcArray);
byte[] msgLenArray = new byte[4];
System.arraycopy(srcArray, 0, msgLenArray, 0, 4);
int msgLen = (MsgUtils.bytesToInt2(msgLenArray));
if (msgLen > 0) {
byte contentByte[] = new byte[msgLen];
socket.getInputStream().read(contentByte);
System.out.println(len[9]);
System.out.println(len[10]);
if (len[9] == 1) {
}
以上就能夠完美的解決問題了,我們每次讀取都讀11個位元組。因為協議頭的長度就是11個位元組。再來根據協議頭裡面的body的長度我們再去讀對應的長度。這樣子就避免了讀取的時候盲目的讀取1024個長度的問題了。