python粘包的解決方案
什么是粘包
粘包就是在數(shù)據(jù)傳輸過程中有多個(gè)數(shù)據(jù)包被粘連在一起被發(fā)送或接受
服務(wù)端:
import socket
import struct
# 創(chuàng)建Socket
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定服務(wù)器和端口號
servers_addr = ('127.0.0.1', 8081)
Socket.bind(servers_addr)
# 監(jiān)聽客戶端請求 最大連接數(shù)為5
Socket.listen(5)
print('服務(wù)器啟動成功,等待客戶端連接...')
# 接受數(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('關(guān)閉服務(wù)')
break
# 接受客戶器端傳來的數(shù)據(jù)
print(data.decode())
# 向客戶端返回?cái)?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, # 管道一:輸出結(jié)果
stderr=subprocess.PIPE # 管道二:輸出錯(cuò)誤信息
)
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)
# 服務(wù)器地址和端口
server_address = ('localhost', 8081)
# 連接服務(wù)器
client_socket.connect(server_address)
print('已連接到服務(wù)器:', server_address)
while True:
# 發(fā)送數(shù)據(jù)
# message = input('>>>>')
client_socket.sendall(msg.encode())
# 接收響應(yīng)
response = client_socket.recv(1024)
print('服務(wù)器響應(yīng):', response.decode())
break
client_socket.close()
案例中使用了subprocess模塊輸出了ip信息,在服務(wù)端打印的數(shù)據(jù)中可以看到內(nèi)容是能夠正常輸出的

但是根據(jù)客戶端的控制臺顯示數(shù)據(jù)在返回時(shí)被截?cái)嗔?/p>

其實(shí)原因很簡單:
response = client_socket.recv(1024)
數(shù)據(jù)在服務(wù)端中能一次性的接收,但由于客戶端只能接受1024,所以就不會從緩存中一下取完大于1024的那部分?jǐn)?shù)據(jù),其實(shí)不管是客戶端還是服務(wù)端,recv()的緩存區(qū)大小都是可控的,但是發(fā)送方發(fā)送了一個(gè) 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)
# 服務(wù)器地址和端口
server_address = ('localhost', 8081)
# 連接服務(wù)器
client_socket.connect(server_address)
print('已連接到服務(wù)器:', server_address)
# 發(fā)送數(shù)據(jù)包
message1 = 'Hello'
message2 = 'World'
# 連續(xù)發(fā)送兩個(gè)數(shù)據(jù)包
client_socket.sendall(message1.encode())
client_socket.sendall(message2.encode())
# 關(guān)閉連接
client_socket.close()
服務(wù)端
import socket
# 創(chuàng)建 Socket 對象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定服務(wù)器地址和端口
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
實(shí)際情況:
等待客戶端連接...
與客戶端建立連接: ('127.0.0.1', 61127)
接收到數(shù)據(jù): HelloWorld
導(dǎo)致粘包的原因
1.緩沖區(qū)大小限制:在TCP傳輸中,由于數(shù)據(jù)過大,超出緩存區(qū)大小限制,導(dǎo)致接收方不能接收到所有的數(shù)據(jù)包,造成了數(shù)據(jù)包的截?cái)嗷騺G失
2.底層協(xié)議特性:底層傳輸協(xié)議如 TCP 是面向流的,不保留消息邊界。TCP 協(xié)議會將數(shù)據(jù)流切分為適當(dāng)大小的數(shù)據(jù)塊進(jìn)行傳輸,因此無法保證每個(gè)數(shù)據(jù)包的邊界
3.數(shù)據(jù)發(fā)送速度過快:發(fā)送方連續(xù)發(fā)送數(shù)據(jù)包,而接收方無法及時(shí)處理,導(dǎo)致多個(gè)數(shù)據(jù)包在接收緩沖區(qū)中堆積
解決方案:struct模塊
利用pack()方法將任意長度的 數(shù)字 打包成新的數(shù)據(jù)
再用unpack()方法將固定長度的 數(shù)字 解包成打包前數(shù)據(jù)真實(shí)的長度
pack()方法 第一個(gè)參數(shù)是格式,第二個(gè)參數(shù)是整數(shù)(數(shù)據(jù)的長度),返回值是一個(gè)新的數(shù)據(jù)unpack()方法 第一個(gè)參數(shù)是格式,第二個(gè)參數(shù)是pack()方法打包后生成的新數(shù)據(jù),返回值是一個(gè)元組,元組中放著打包前數(shù)據(jù)真實(shí)的長度
import struct
msg_one = '你好'
msg_two = ('struct 是 Python 標(biāo)準(zhǔn)庫中的一個(gè)模塊,用于進(jìn)行字節(jié)與數(shù)據(jù)類型之間的相互轉(zhuǎn)換。它提供了'
'一組函數(shù)來打包(pack)和解包(unpack)數(shù)據(jù),使得數(shù)據(jù)在網(wǎng)絡(luò)傳輸或文件存儲時(shí)能夠以二進(jìn)制形式進(jìn)行處理。')
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é)流總大小讓接收端知曉,然后接收端來一個(gè)死循環(huán)接收完所有數(shù)據(jù)
客戶端
import socket
import struct
# 創(chuàng)建 Socket 對象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服務(wù)器地址和端口
server_address = ('localhost', 8081)
# 連接服務(wù)器
client_socket.connect(server_address)
print('已連接到服務(wù)器:', server_address)
# 發(fā)送數(shù)據(jù)包
msg = b'helloworld'
data = struct.pack('i', len(msg))
# 先發(fā)送報(bào)頭
client_socket.send(data)
# 發(fā)送真實(shí)數(shù)據(jù)
client_socket.send(msg)
服務(wù)端
import socket
import struct
# 創(chuàng)建 Socket 對象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定服務(wù)器地址和端口
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ù)該原理改進(jìn)案例代碼
客戶端
import socket
import struct
# 創(chuàng)建Socket
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定服務(wù)器和端口號
servers_addr = ('127.0.0.1', 8082)
Socket.bind(servers_addr)
# 監(jiān)聽客戶端請求 最大連接數(shù)為5
Socket.listen(5)
print('服務(wù)器啟動成功,等待客戶端連接...')
# 接受數(shù)據(jù)
client_socket, client_addr = Socket.accept()
print('與客戶端建立連接', client_addr)
# client_socket.setblocking(False)
# 數(shù)據(jù)交換
while True:
# 接受報(bào)頭
header = client_socket.recv(4) # 最大1024字節(jié)
if len(header) < 1:
print('關(guān)閉服務(wù)')
break
data_len = struct.unpack('i', header)[0]
print(data_len)
# 接受真實(shí)數(shù)據(jù)
real_data = client_socket.recv(data_len)
print(real_data.decode('gbk'))
# 向客戶端返回?cái)?shù)據(jù)
client_socket.send(real_data)
服務(wù)端
import socket
import struct
import subprocess
# 獲取cmd指令
cmd_from_client = 'ipconfig'
cmd_msg = subprocess.Popen(cmd_from_client,
shell=True, # 使用shell命令
stdout=subprocess.PIPE, # 管道一:輸出結(jié)果
stderr=subprocess.PIPE # 管道二:輸出錯(cuò)誤信息
)
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)
# 服務(wù)器地址和端口
server_address = ('localhost', 8082)
# 連接服務(wù)器
client_socket.connect(server_address)
print('已連接到服務(wù)器:', server_address)
while True:
# 先發(fā)報(bào)頭
data_len = struct.pack('i', len(msg))
client_socket.send(data_len)
# 發(fā)送數(shù)據(jù)
client_socket.send(msg.encode('gbk'))
# 接收響應(yīng)
response = client_socket.recv(data_len[0])
print('服務(wù)器響應(yīng):', response.decode('gbk'))到此這篇關(guān)于python粘包的解決方案的文章就介紹到這了,更多相關(guān)python 粘包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何利用python給微信公眾號發(fā)消息實(shí)例代碼
使用過微信公眾號的小伙伴應(yīng)該知道微信公眾號有時(shí)候會給你推一些文章,當(dāng)你選擇它的某個(gè)功能時(shí),它還會返回一些信息,下面這篇文章主要給大家介紹了關(guān)于如何利用python給微信公眾號發(fā)消息的相關(guān)資料,需要的朋友可以參考下2022-03-03
python讀取圖片顏色值并生成excel像素畫的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于python讀取圖片顏色值并生成excel像素畫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
python操作redis數(shù)據(jù)庫的三種方法
這篇文章主要介紹了python操作redis數(shù)據(jù)庫的三種方法,幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-09-09
python實(shí)現(xiàn)飛機(jī)大戰(zhàn)(面向過程)
這篇文章主要為大家詳細(xì)介紹了python面向過程實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Python3基礎(chǔ)之基本數(shù)據(jù)類型概述
這篇文章主要介紹了Python3的基本數(shù)據(jù)類型,需要的朋友可以參考下2014-08-08

