粘包问题
一.什么是粘包?
粘包指的是數據與數據之間沒有明確的分界線,導致不能正確讀取數據!
TCP、UDP協議傳輸數據時的具體流程,TCP協議也稱之為流式協議,UDP稱之為數據報協議
?
發送方:
當應用程序調用send函數時,應用程序會將數據從應用程序拷貝到操作系統的緩存區,再由操作系統從緩存區讀取數據并發送出去.
接收方:
對方計算機操作系統先收到數據,先將數據存放到操作系統的緩沖區中,當應用程序調用recv時,實際上是從操作系統緩沖區將數據拷貝到應用程序的過程.
上述過程對于TCP和UDP都是相同的,不同之處在于:
UDP:
UDP在收發數據時是基于數據包的,即一個包一個包的發送,包與包之間有著明確的分界,到達操作系統緩存區后也是一個一個獨立的數據包,接收方從操作系統緩沖區中將數據包拷貝到應用程序.
這種方式存在的問題:
1.發送方發送的數據長度每個操作系統會有不同的限制,數據超過限制則無法發送
2.接收方接受數據時如果應用程序的提供的緩存容量小于數據包的長度將造成數據丟失,而緩存區大小不可能無限放大.
TCP:
當我們需要傳輸較大的數據,或需要保證數據的完整性時 ,最簡單的方式就是使用TCP協議
與UDP不同TCP會增加一套校驗的規則來保證數據的完整性,會將超過TCP最大長度的數據拆分成多個TCP包,并在傳輸數據時為每一個TCP數據包指定一個順序號,接收方在收到TCP數據包后按照順序將數據包進行重組,重組后的數據全都是二進制數據,且每次收到的二進制數據之間沒有明顯的分界.
1.當單個數據包較小時接收方可能一次性讀取多個包的數據
2.當整體數據較大時接收方可能一次僅讀取一個包的一部分內容
3.TCP為了提高效率,會將數據較小且發送間隔較短的數據合并發送,該機制也會導致發送方將兩個數據包粘在一起發送.
二.粘包的解決方案
服務器:
import socket,subprocess
server = socket.socket()
server.bind(('192.168.12.207',4396))
server.listen()
while True:
client,addr = server.accept()
while True:
try:
cmd = client.recv(1024).decode('utf-8')
p = subprocess.Popen(cmd,shell=True,\
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
data = p.stdout.read()
err_data = p.stderr.read()
print('數據長度:%s'%(len(data) + len(err_data)))
length = len(data) + len(err_data)
len_str = str(length).encode('utf-8')
#先發送長度數據再發送真實數據,長度數據可能和真實數據粘在一起,而接收方不知道長度數據的字節數,導致粘包
client.send(len_str)
client.send(data)
client.send(err_data)
except ConnectionResetError:
print('連接異常')
client.close()
break
客戶端:
import socket
client = socket.socket()
client.connect(('192.168.12.207',4396))
while True:
cmd = input('>>:').strip()
client.send(cmd.encode('utf-8'))
length = client.recv(1024)
len_data =int(length.decode('utf-8'))
print('數據長度為%s'%len_data)
data_all= b''
data_size = 0
while data_size < len_data:
data = client.recv(len_data)
data_size += len(data)
data_all += data
print('接受長度為%s'%data_size)
print(data_all.decode('GBK'))
為了解決長度數據和真實數據的粘包問題,我們要用到:struct結構體 可以將python中的數據類型轉換成C中的結構體(轉換成bytes)
import struct
num = 100
#該函數 將一個python中的數據類型轉換成bytes 第一個參數通常是i 其能轉換的數據范圍是C語言中的int范圍
#如果int不夠,那就使用q 表示的是long long型
res = struct.pack('i',num)
print(res)
print(len(res))
#該函數將bytes類型轉換
res2 = struct.unpack('i',res)
print(res2) #得到一個元組(100,)
print(res2[0]) #得到整型 100
修正版本:
服務器:
import socket,subprocess,struct
server = socket.socket()
server.bind(('192.168.12.207',4396))
server.listen()
while True:
client,addr = server.accept()
while True:
try:
cmd = client.recv(1024).decode('utf-8')
p = subprocess.Popen(cmd,shell=True,\
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
data = p.stdout.read()
err_data = p.stderr.read()
print('數據長度:%s'%(len(data) + len(err_data)))
length = len(data) + len(err_data)
len_data = struct.pack('i',length)
client.send(len_data)
client.send(data)
client.send(err_data)
except ConnectionResetError:
print('連接異常')
client.close()
break
客戶端:
import socket,struct
client = socket.socket()
client.connect(('192.168.12.207',4396))
while True:
cmd = input('>>:').strip()
client.send(cmd.encode('utf-8'))
#接受一個長度為4的固定字節
length = client.recv(4)
len_data = struct.unpack('i',length)[0]
print('數據長度為%s'%len_data)
data_all= b''
data_size = 0
while data_size < len_data:
data = client.recv(1024)
data_size += len(data)
data_all += data
print('接受長度為%s'%data_size)
print(data_all.decode('GBK'))
2.自定義報頭解決粘包
上述方案已經完美的解決了粘包的問題,但是擴展性不高,例如我們要實現文件上傳下載,不光要傳輸文件的數據,還需要傳輸文件名字,md5值等等,如何實現?
發送端:
1.先將所有的額外信息打包到一個頭中
2.然后先發送頭部數據
3.最后發送真實數據
接收端:
1.接受固定長度的頭部數據長度
2.根據長度數據獲取頭部數據
3,根據頭部數據獲取真實數據
服務器:
import socket,subprocess,struct,datetime,json
server = socket.socket()
server.bind(('192.168.12.207',4396))
server.listen()
while True:
client,addr = server.accept()
while True:
try:
cmd = client.recv(1024).decode('utf-8')
p = subprocess.Popen(cmd,shell=True,\
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
data = p.stdout.read()
err_data = p.stderr.read()
print('數據長度:%s'%(len(data) + len(err_data)))
length = len(data) + len(err_data)
#先發送額外數據,將要發送的真實數據長度先存到字典中
t = {}
t['time'] = str(datetime.datetime.now())
t['size'] = length
#將字典轉換成json格式
t_json = json.dumps(t)
#將字典轉換成字節
t_data = t_json.encode('utf-8')
t_length = struct.pack('i',len(t_data))
#1.先發送額外數據長度
client.send(t_length)
#2.發送額外信息
client.send(t_data)
#3.發送真實數據
client.send(data)
client.send(err_data)
except ConnectionResetError:
print('連接異常')
client.close()
break
客戶端:
import socket,struct,json
client = socket.socket()
client.connect(('192.168.12.207',4396))
while True:
cmd = input('>>:').strip()
if not cmd:
print('命令不能為空!')
continue
client.send(cmd.encode('utf-8'))
#1.先接受額外信息長度
length = client.recv(4)
len_data = struct.unpack('i',length)[0]
#2.接受額外信息
t_data = client.recv(len_data)
print(t_data.decode('utf-8'))
json_dic = json.loads(t_data.decode('utf-8'))
print('執行時間為%s'%json_dic['time'])
data_size = json_dic['size']
#3,接受真實信息
data_all= b''
rcv_size = 0
while rcv_size < data_size:
data = client.recv(1024)
rcv_size += len(data)
data_all += data
print('接受長度為%s'%rcv_size)
print(data_all.decode('GBK'))
?
轉載于:https://www.cnblogs.com/lizeqian1994/p/10191875.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
- 上一篇: [From 10.1~10.5] 对象和
- 下一篇: PE 学习之路 —— 区块表