python粘包的解決方案
什么是粘包
粘包就是在數(shù)據(jù)傳輸過程中有多個數(shù)據(jù)包被粘連在一起被發(fā)送或接受
服務端:
import socket import struct # 創(chuàng)建Socket Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定服務器和端口號 servers_addr = ('127.0.0.1', 8081) Socket.bind(servers_addr) # 監(jiān)聽客戶端請求 最大連接數(shù)為5 Socket.listen(5) print('服務器啟動成功,等待客戶端連接...') # 接受數(shù)據(jù) client_socket, client_addr = Socket.accept() print('與客戶端建立連接', client_addr) client_socket.setblocking(False) # 數(shù)據(jù)交換 while True: data = client_socket.recv(10880) # 最大1024字節(jié) if len(data) < 1: print('關閉服務') break # 接受客戶器端傳來的數(shù)據(jù) print(data.decode()) # 向客戶端返回數(shù)據(jù) client_socket.sendall(data) break Socket.close()
客戶端:
import socket import subprocess # 獲取cmd指令 cmd_from_client = 'ipconfig' cmd_msg = subprocess.Popen(cmd_from_client, shell=True, # 使用shell命令 stdout=subprocess.PIPE, # 管道一:輸出結果 stderr=subprocess.PIPE # 管道二:輸出錯誤信息 ) msg_one = cmd_msg.stdout.read().decode('gbk') msg_two = cmd_msg.stderr.read().decode('gbk') msg = msg_one + msg_two # 創(chuàng)建Socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 服務器地址和端口 server_address = ('localhost', 8081) # 連接服務器 client_socket.connect(server_address) print('已連接到服務器:', server_address) while True: # 發(fā)送數(shù)據(jù) # message = input('>>>>') client_socket.sendall(msg.encode()) # 接收響應 response = client_socket.recv(1024) print('服務器響應:', response.decode()) break client_socket.close()
案例中使用了subprocess
模塊輸出了ip信息,在服務端打印的數(shù)據(jù)中可以看到內(nèi)容是能夠正常輸出的
但是根據(jù)客戶端的控制臺顯示數(shù)據(jù)在返回時被截斷了
其實原因很簡單:
response = client_socket.recv(1024)
數(shù)據(jù)在服務端中能一次性的接收,但由于客戶端只能接受1024,所以就不會從緩存中一下取完大于1024的那部分數(shù)據(jù),其實不管是客戶端還是服務端,recv()
的緩存區(qū)大小都是可控的,但是發(fā)送方發(fā)送了一個 10KB 的數(shù)據(jù)包,而接收方使用 recv(1024)
只能一次接收最多 1KB 的數(shù)據(jù),這樣就需要多次調(diào)用 recv()
來接收完整的數(shù)據(jù),可能會引發(fā)粘包問題
客戶端 import socket # 創(chuàng)建 Socket 對象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 服務器地址和端口 server_address = ('localhost', 8081) # 連接服務器 client_socket.connect(server_address) print('已連接到服務器:', server_address) # 發(fā)送數(shù)據(jù)包 message1 = 'Hello' message2 = 'World' # 連續(xù)發(fā)送兩個數(shù)據(jù)包 client_socket.sendall(message1.encode()) client_socket.sendall(message2.encode()) # 關閉連接 client_socket.close()
服務端 import socket # 創(chuàng)建 Socket 對象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定服務器地址和端口 server_address = ('localhost', 8081) server_socket.bind(server_address) # 監(jiān)聽客戶端請求 server_socket.listen(1) print('等待客戶端連接...') while True: # 接受連接 client_socket, client_addr = server_socket.accept() print('與客戶端建立連接:', client_addr) # 接收數(shù)據(jù) data = client_socket.recv(1024) # 接收數(shù)據(jù)包 received_data = data.decode() # 處理接收到的數(shù)據(jù) print('接收到數(shù)據(jù):', received_data)
理想情況:
等待客戶端連接...
與客戶端建立連接: ('127.0.0.1', 61127)
接收到數(shù)據(jù): Hello
接收到數(shù)據(jù): World
實際情況:
等待客戶端連接...
與客戶端建立連接: ('127.0.0.1', 61127)
接收到數(shù)據(jù): HelloWorld
導致粘包的原因
1.緩沖區(qū)大小限制:在TCP傳輸中,由于數(shù)據(jù)過大,超出緩存區(qū)大小限制,導致接收方不能接收到所有的數(shù)據(jù)包,造成了數(shù)據(jù)包的截斷或丟失
2.底層協(xié)議特性:底層傳輸協(xié)議如 TCP 是面向流的,不保留消息邊界。TCP 協(xié)議會將數(shù)據(jù)流切分為適當大小的數(shù)據(jù)塊進行傳輸,因此無法保證每個數(shù)據(jù)包的邊界
3.數(shù)據(jù)發(fā)送速度過快:發(fā)送方連續(xù)發(fā)送數(shù)據(jù)包,而接收方無法及時處理,導致多個數(shù)據(jù)包在接收緩沖區(qū)中堆積
解決方案:struct模塊
利用pack()
方法將任意長度的 數(shù)字 打包成新的數(shù)據(jù)
再用unpack()
方法將固定長度的 數(shù)字 解包成打包前數(shù)據(jù)真實的長度
pack()
方法 第一個參數(shù)是格式,第二個參數(shù)是整數(shù)(數(shù)據(jù)的長度),返回值是一個新的數(shù)據(jù)unpack()
方法 第一個參數(shù)是格式,第二個參數(shù)是pack()
方法打包后生成的新數(shù)據(jù),返回值是一個元組,元組中放著打包前數(shù)據(jù)真實的長度
import struct msg_one = '你好' msg_two = ('struct 是 Python 標準庫中的一個模塊,用于進行字節(jié)與數(shù)據(jù)類型之間的相互轉(zhuǎn)換。它提供了' '一組函數(shù)來打包(pack)和解包(unpack)數(shù)據(jù),使得數(shù)據(jù)在網(wǎng)絡傳輸或文件存儲時能夠以二進制形式進行處理。') total = len(msg_one) + len(msg_two) # 106 # 將數(shù)據(jù)打包 res = struct.pack('i', total) # 解包數(shù)據(jù) un_res = struct.unpack('i', res) print(len(res)) # 4 print(res) # bytes類型: b'j\x00\x00\x00' print(un_res) # 元組類型: (106,)
粘包問題的根源在于,接收端不知道發(fā)送端將要傳送的字節(jié)流的長度,所以解決粘包的方法就是圍繞,如何讓發(fā)送端在發(fā)送數(shù)據(jù)前,把自己將要發(fā)送的字節(jié)流總大小讓接收端知曉,然后接收端來一個死循環(huán)接收完所有數(shù)據(jù)
客戶端 import socket import struct # 創(chuàng)建 Socket 對象 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 服務器地址和端口 server_address = ('localhost', 8081) # 連接服務器 client_socket.connect(server_address) print('已連接到服務器:', server_address) # 發(fā)送數(shù)據(jù)包 msg = b'helloworld' data = struct.pack('i', len(msg)) # 先發(fā)送報頭 client_socket.send(data) # 發(fā)送真實數(shù)據(jù) client_socket.send(msg)
服務端 import socket import struct # 創(chuàng)建 Socket 對象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定服務器地址和端口 server_address = ('localhost', 8081) server_socket.bind(server_address) # 監(jiān)聽客戶端請求 server_socket.listen(1) print('等待客戶端連接...') while True: # 接受連接 client_socket, client_addr = server_socket.accept() print('與客戶端建立連接:', client_addr) # 接收數(shù)據(jù) data = client_socket.recv(1024) # 接收數(shù)據(jù)包 received_data = struct.unpack('i', data) data_len = received_data[0] real_data = client_socket.recv(data_len) # 處理接收到的數(shù)據(jù) print('接收到數(shù)據(jù):', real_data.decode('utf8'))
根據(jù)該原理改進案例代碼
客戶端 import socket import struct # 創(chuàng)建Socket Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定服務器和端口號 servers_addr = ('127.0.0.1', 8082) Socket.bind(servers_addr) # 監(jiān)聽客戶端請求 最大連接數(shù)為5 Socket.listen(5) print('服務器啟動成功,等待客戶端連接...') # 接受數(shù)據(jù) client_socket, client_addr = Socket.accept() print('與客戶端建立連接', client_addr) # client_socket.setblocking(False) # 數(shù)據(jù)交換 while True: # 接受報頭 header = client_socket.recv(4) # 最大1024字節(jié) if len(header) < 1: print('關閉服務') break data_len = struct.unpack('i', header)[0] print(data_len) # 接受真實數(shù)據(jù) real_data = client_socket.recv(data_len) print(real_data.decode('gbk')) # 向客戶端返回數(shù)據(jù) client_socket.send(real_data)
服務端 import socket import struct import subprocess # 獲取cmd指令 cmd_from_client = 'ipconfig' cmd_msg = subprocess.Popen(cmd_from_client, shell=True, # 使用shell命令 stdout=subprocess.PIPE, # 管道一:輸出結果 stderr=subprocess.PIPE # 管道二:輸出錯誤信息 ) msg_one = cmd_msg.stdout.read().decode('gbk') msg_two = cmd_msg.stderr.read().decode('gbk') msg = msg_one + msg_two # 創(chuàng)建Socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 服務器地址和端口 server_address = ('localhost', 8082) # 連接服務器 client_socket.connect(server_address) print('已連接到服務器:', server_address) while True: # 先發(fā)報頭 data_len = struct.pack('i', len(msg)) client_socket.send(data_len) # 發(fā)送數(shù)據(jù) client_socket.send(msg.encode('gbk')) # 接收響應 response = client_socket.recv(data_len[0]) print('服務器響應:', response.decode('gbk'))
到此這篇關于python粘包的解決方案的文章就介紹到這了,更多相關python 粘包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python操作redis數(shù)據(jù)庫的三種方法
這篇文章主要介紹了python操作redis數(shù)據(jù)庫的三種方法,幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-09-09python實現(xiàn)飛機大戰(zhàn)(面向過程)
這篇文章主要為大家詳細介紹了python面向過程實現(xiàn)飛機大戰(zhàn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05