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ù)器和端口號(hào) servers_addr = ('127.0.0.1', 8081) Socket.bind(servers_addr) # 監(jiān)聽客戶端請(qǐng)求 最大連接數(shù)為5 Socket.listen(5) print('服務(wù)器啟動(dòng)成功,等待客戶端連接...') # 接受數(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ù)客戶端的控制臺(tái)顯示數(shù)據(jù)在返回時(shí)被截?cái)嗔?/p>
其實(shí)原因很簡單:
response = client_socket.recv(1024)
數(shù)據(jù)在服務(wù)端中能一次性的接收,但由于客戶端只能接受1024,所以就不會(huì)從緩存中一下取完大于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ù),可能會(huì)引發(fā)粘包問題
客戶端 import socket # 創(chuàng)建 Socket 對(duì)象 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 對(duì)象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定服務(wù)器地址和端口 server_address = ('localhost', 8081) server_socket.bind(server_address) # 監(jiān)聽客戶端請(qǐng)求 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é)議會(huì)將數(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ò)傳輸或文件存儲(chǔ)時(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 對(duì)象 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 對(duì)象 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定服務(wù)器地址和端口 server_address = ('localhost', 8081) server_socket.bind(server_address) # 監(jiān)聽客戶端請(qǐng)求 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ù)器和端口號(hào) servers_addr = ('127.0.0.1', 8082) Socket.bind(servers_addr) # 監(jiān)聽客戶端請(qǐng)求 最大連接數(shù)為5 Socket.listen(5) print('服務(wù)器啟動(dòng)成功,等待客戶端連接...') # 接受數(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何利用python給微信公眾號(hào)發(fā)消息實(shí)例代碼
使用過微信公眾號(hào)的小伙伴應(yīng)該知道微信公眾號(hào)有時(shí)候會(huì)給你推一些文章,當(dāng)你選擇它的某個(gè)功能時(shí),它還會(huì)返回一些信息,下面這篇文章主要給大家介紹了關(guān)于如何利用python給微信公眾號(hào)發(fā)消息的相關(guān)資料,需要的朋友可以參考下2022-03-03python讀取圖片顏色值并生成excel像素畫的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于python讀取圖片顏色值并生成excel像素畫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02python操作redis數(shù)據(jù)庫的三種方法
這篇文章主要介紹了python操作redis數(shù)據(jù)庫的三種方法,幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-09-09python實(shí)現(xiàn)飛機(jī)大戰(zhàn)(面向過程)
這篇文章主要為大家詳細(xì)介紹了python面向過程實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05對(duì)python中各個(gè)response的使用說明
今天小編就為大家分享一篇對(duì)python中各個(gè)response的使用說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03Python3基礎(chǔ)之基本數(shù)據(jù)類型概述
這篇文章主要介紹了Python3的基本數(shù)據(jù)類型,需要的朋友可以參考下2014-08-08