前言:
本文是針對socket長連接(涉及到服務器主動推數據),每個包頭的拼接算法和長度都不一樣,具體的包頭小伙伴們問自己公司的開發吧,本文只是提供思路。再啰嗦一句:recv到的包頭中數字進行某種運算后得到的就是開發定義的返回code。
粘包問題解決思路:
python中的socket recv()是阻塞接收的,所以不存在同時發送多個數據接收錯亂的情況。網上對於粘包有各種各樣的解決辦法,什么每次recv前sleep幾秒,保證緩存中有足夠數據在接收。還有說在recv()中加個wait的參數,保證每次都接受滿1024個數據長度。我覺得網上唯一正確的辦法是:先知道自己想要接收的數據的大小,然后用一個死循環接收完所有數據。
作者一開始是使用靜態拆包的方式,就是每次send后都recv一次,然后把每次recv的結果都拼在一起,然后再根據包頭中的包體長度拆出一個個完整包。這種方式有個缺點:如果后面的請求需要用到前面請求返回值做參數,那么就涼涼了。
這時候就需要用到動態拆包,send一個請求recv一個返回結果,然后解析出完整數據。但是實現的過程中發現幾個問題:
(1)返回數據的長度跟我從包頭里得到的長度不一致,我以為是粘包導致的接收不全,因為我后來加了個一樣的請求,就能接收全了。
(2)后來我在每個請求發送前都加了sleep(10),第16個請求接受全了,但是后面的數據直接報錯了。
(3)后來偶然一次,我發現能運行成功不報錯,但是在大部分時運行會報錯。
(4)然后發現send一個請求,返回了兩個完整的包。
結合上面的四個問題,我開始了漫長的尋求解答的過程,后來得高人解答,才發現了問題——服務器主動推了數據。現在想想上面4點可不就是服務器主動推數據的表現嘛。
下面直接上方法,同時解決粘包、服務器主動推數據的問題。
思路:send之后,先recv(8)包頭,得到包體大小,然后用死循環recv全。然后要用包頭算法對得到的返回數據進行判斷,進一步保證了這個包就是我需要的數據包。
1 def sendData(self, data, t, respHeader): 2 self.data = data 3 self.t = t 4 self.respHeader=respHeader 5 self.s.send(data) # 傳輸包裝好的請求 6 total_data = bytes() 7 header = 1 8 9 for i in range(5): #每次send,我規定它最多去recv5次,以防浪費時間 10 header_bytes = self.s.recv(8) # 先接收包頭 11 body_len = struct.unpack("!2I", header_bytes)[0] #這是包體長度 12 header = struct.unpack("!2I", header_bytes)[1] #開發定義的用來做運算 13 print("應該接收的長度:", body_len) 14 while len(total_data) < body_len: 15 total_data += self.s.recv(body_len) 16 print("實際接收的長度:", len(total_data)) 17 18 header_code = header & 0x3xxxxxxx #做運算得到返回請求的code 19 print("header_code: " + str(header_code)) 20 #如果算出來等於這個返回請求的頭或者返回error,跳出for循環,不再繼續recv 21 if header_code == int(respHeader) or header_code == 1: 22 print("over") 23 break 24 else: 25 #如果接收的不是我需要的包頭,清空total_data,重新for循環從緩存中recv數據 26 total_data = bytes() 27 if i >= 5: 28 print("返回數據有誤")
