第一部分:簡介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"))
上述兩種方式均可以解決粘包問題。