TCP Socket的粘包和分包的處理


概述

   在進行TCP Socket開發時,都需要處理數據包粘包和分包的情況.實際上解決該問題很簡單,在應用層下,定義一個協議:消息頭部+消息長度+消息正文即可。

分包和粘包

         分包:發送方發送字符串”helloworld”,接收方卻接收到了兩個字符串”hello”和”world”

         粘包:發送方發送兩個字符串”hello”+”world”,接收方卻一次性接收到了”helloworld”

   socket環境有以上問題,但是TCP傳輸數據能保證幾點:

        1. 順序不變,例如發送方發送hello,接收方也一定順序接收到hello,這個是TCP協議承諾的,因此這點成為我們解決分包,黏包問題的關鍵.

        2. 分割的包中間不會插入其他數據

  因此如果要使用socket通信,就一定要自己定義一份協議.目前最常用的協議標准是:消息頭部(包頭)+消息長度+消息正文

 

TCP分包的原理 

    TCP是以段(Segment)為單位發送數據的,建立TCP鏈接后,有一個最大消息長度(MSS).如果應用層數據包超過MSS,就會把應用層數據包拆分,分成兩個段來發送.

    這個時候接收端的應用層就要拼接這兩個TCP包,才能正確處理數據。

    相關的,路由器有一個MTU( 最大傳輸單元)一般是1500字節,除去IP頭部20字節,留給TCP的就只有MTU-20字節。所以一般TCP的MSS為MTU-20=1460字節

    當應用層數據超過1460字節時,TCP會分多個數據包來發送。

 

TCP粘包的原理

      TCP為了提高網絡的利用率,會使用一個叫做Nagle的算法.該算法是指,發送端即使有要發送的數據,如果很少的話,會延遲發送.

       如果應用層給TCP傳送數據很快的話,就會把兩個應用層數據包“粘”在一起,TCP最后只發一個TCP數據包給接收端.

    

自定義消息頭部

    消息頭部不一定只能是一個字節比如0xAA什么的,也可以包含協議版本號,指令等,當然也可以把消息長度合並到消息頭部里,

    唯一的要求是包頭長度要固定的,包體則可變長。下面是我自定義的一個包頭:

   

    版本號,消息長度,指令數據類型 都是無符號32位整型變量,於是這個消息長度固定為4×3=12字節    每個整數類型變量占用4個字節的長度

 1 import struct
 2 import json
 3 
 4 version = 1
 5 body =json.dumps(dict(hello="world"))
 6 cmd =101
 7 
 8 header = [version,len(body),cmd]
 9 #struct中:!代表Network order,3I代表3個unsigned int數據
10 headPack = struct.pack("3i",*header)
11 print(headPack)
自定義包頭
 1 import socket
 2 import time
 3 import struct
 4 import json
 5 
 6 host='localhost'
 7 port=1234
 8 
 9 ADDR=(host,port)
10 
11 if __name__ == "__main__":
12     client = socket.socket()
13     client.connect(ADDR)
14 
15     version=1
16     body =json.dumps(dict(hello="world",myhello="aaaaaabbbbbb"))
17     #print(body)
18     cmd =110
19     header =[version,len(body),cmd]
20     #headPack=str(header).encode()
21     headPack=struct.pack('!3i',*header)
22     print(headPack,type(headPack))
23     sendmsg1 = headPack + body.encode()
24 
25     client.send(sendmsg1)
26 
27     print("******分包測試***********")
28     version2 =2
29     body2=json.dumps(dict(hello="world2"))
30     cmd2 =102
31     header2 = [version2,len(body2),cmd2]
32     headPack2=struct.pack("!3i",*header2)
33     sendmsg2_1 = headPack2 + body2[:2].encode()
34     sendmsg2_2 = body2[2:].encode()
35 
36     client.send(sendmsg2_1)
37     time.sleep(0.2)
38     client.send(sendmsg2_2)
39     time.sleep(3)
40 
41     print("******粘包測試***********")
42     version3=3
43     body3 = json.dumps(dict(hello="world3"))
44     cmd3 = 103
45     header3 = [version3,len(body3),cmd3]
46     headPack3 = struct.pack("!3I",*header3)
47 
48     version4 = 4
49     body4 = json.dumps(dict(hello="world4444"))
50     cmd4 = 104
51     header4 = [version4,len(body4),cmd4]
52     headPack4 = struct.pack("!3i",*header4)
53 
54     sendmsg3 = headPack3+body3.encode()+headPack4+body4.encode()
55     client.send(sendmsg3)
56     time.sleep(3)
57     client.close()
客戶端封裝發送包
import socket
import struct
import json

HOST='localhost'
PORT =1234

dataBuffer = bytes()
headerSize =12

sn = 0
def dataHandler(headPack,body):
    global  sn
    sn +=1
    print("第%s個數據包:" %sn)
    print("version:%s,bodysize:%s,cmd:%s" % headPack)
    print(json.loads(body.decode()))
    print("結束....")

if __name__ =="__main__":
    with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
        s.bind((HOST,PORT))
        s.listen()
        conn,addr = s.accept()
        with conn:
            print("connted by",addr)
            while True:
                data = conn.recv(1024)
                if data:
                    dataBuffer +=data
                    while True:
                        if len(dataBuffer) < headerSize:
                            print("數據包(%s Byte) 小於消息頭部長度,跳出小循環" %len(dataBuffer))
                            break

                        headPack = struct.unpack("!3i",dataBuffer[:headerSize])

                        # print(headPack)
                        # print(dataBuffer)
                        # print(dataBuffer[:headerSize])

                        bodySize = headPack[1]
                        print(dataBuffer[headerSize:headerSize + bodySize])


                        #分包
                        if len(dataBuffer) < headerSize+bodySize:
                            print("數據包(%s byte)不完整(總共 %s byte),跳出循環" %(len(dataBuffer),headerSize+bodySize))
                            break

                        body = dataBuffer[headerSize:headerSize+bodySize]
                        dataHandler(headPack,body)

                        dataBuffer = dataBuffer[headerSize+bodySize:]
服務端解析數據包   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM