淺談tcp粘包問題


第一部分:簡介tcp socket通信的底層原理

原理解析圖:

 

1 socket通信過程如圖所示:首先客戶端將發送內容通過send()方法將內容發送到客戶端計算機的內核區,然后由操作系統將內容通過底層路徑發送到服務器端的內核區,然后由服務器程序通過recv()方法從服務器端計算機內核區取出數據。
2 因此我們可以了解到,send方法並不是直接將內容發送到服務器端,recv方法也並不是直接將從客戶端發來的內容接收到服務器程序內存中,而是操作自己機器的內核區。

 

第二部分:產生粘包的原因(只針對tcp)

產生粘包的情況有兩種:

1 1:當連續發送數據時,由於tcp協議的nagle算法,會將較小的內容拼接成大的內容,一次性發送到服務器端,因此造成粘包
2 
3 2:當發送內容較大時,由於服務器端的recv(buffer_size)方法中的buffer_size較小,不能一次性完全接收全部內容,因此在下一次請求到達時,接收的內容依然是上一次沒有完全接收完的內容,因此造成粘包現象。

也就是說:接收方不知道該接收多大的數據才算接收完畢,造成粘包。

 

第三部分:如何解決上述兩種粘包現象?

思路一:對於第一種粘包產生方式可以在兩次send()直接使用recv()來阻止連續發送的情況發生。代碼就不用展示了。

思路二:由於產生粘包的原因是接收方的無邊界接收,因此發送端可以在發送數據之前向接收端告知發送內容的大小即可。代碼示例如下:

  方式一:分兩次通訊分別傳遞內容大小和內容

  服務器端代碼:

 1 # __author__:Kelvin
 2 # date:2019/4/28 21:36
 3 from socket import *
 4 import subprocess
 5 
 6 server = socket(AF_INET, SOCK_STREAM)
 7 server.bind(("127.0.0.1", 8000))
 8 server.listen(5)
 9 
10 while True:
11     conn, addr = server.accept()
12     print("創建了一個新的連接!")
13     while True:
14         try:
15             data = conn.recv(1024)
16             if not data: break
17             res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
18                                    stderr=subprocess.PIPE)
19             err = res.stderr.read()
20             if err:
21                 cmd_msg = err
22             else:
23                 cmd_msg = res.stdout.read()
24             if not cmd_msg: cmd_msg = "action success!".encode("gbk")
25             length = len(cmd_msg)
26             conn.send(str(length).encode("utf-8"))
27             conn.recv(1024)
28             conn.send(cmd_msg)
29         except Exception as e:
30             print(e)
31             break

  客戶端代碼:

 1 # __author__:Kelvin
 2 # date:2019/4/28 21:36
 3 from socket import *
 4 
 5 client = socket(AF_INET, SOCK_STREAM)
 6 client.connect(("127.0.0.1", 8000))
 7 while True:
 8     inp = input(">>:")
 9     if not inp: continue
10     if inp == "quit": break
11     client.send(inp.encode("utf-8"))
12     length = int(client.recv(1024).decode("utf-8"))
13     client.send("ready!".encode("utf-8"))
14     lengthed = 0
15     cmd_msg = b""
16     while lengthed < length:
17         cmd_msg += client.recv(1024)
18         lengthed = len(cmd_msg)
19     print(cmd_msg.decode("gbk"))

  方式二:一次通訊直接傳遞內容大小和內容

  服務器端:

 1 # __author__:Kelvin
 2 # date:2019/4/28 21:36
 3 from socket import *
 4 import subprocess
 5 import struct
 6 
 7 server = socket(AF_INET, SOCK_STREAM)
 8 server.bind(("127.0.0.1", 8000))
 9 server.listen(5)
10 
11 while True:
12     conn, addr = server.accept()
13     print("創建了一個新的連接!")
14     while True:
15         try:
16             data = conn.recv(1024)
17             if not data: break
18             res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
19                                    stderr=subprocess.PIPE)
20             err = res.stderr.read()
21             if err:
22                 cmd_msg = err
23             else:
24                 cmd_msg = res.stdout.read()
25             if not cmd_msg: cmd_msg = "action success!".encode("gbk")
26             length = len(cmd_msg)
27             conn.send(struct.pack("i", length))
28             conn.send(cmd_msg)
29         except Exception as e:
30             print(e)
31             break

  客戶端:

 1 # __author__:Kelvin
 2 # date:2019/4/28 21:36
 3 from socket import *
 4 import struct
 5 
 6 client = socket(AF_INET, SOCK_STREAM)
 7 client.connect(("127.0.0.1", 8000))
 8 while True:
 9     inp = input(">>:")
10     if not inp: continue
11     if inp == "quit": break
12     client.send(inp.encode("utf-8"))
13     length = struct.unpack("i",client.recv(4))[0]
14     lengthed = 0
15     cmd_msg = b""
16     while lengthed < length:
17         cmd_msg += client.recv(1024)
18         lengthed = len(cmd_msg)
19     print(cmd_msg.decode("gbk"))

上述兩種方式均可以解決粘包問題。

 


免責聲明!

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



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