《Python》網絡編程之黏包
黏包
一、黏包現象
同時執行多條命令之後,得到的結果很可能只有一部分,在執行其他命令的時候又接收到之前執行的另外一部分結果,這種顯現就是黏包。
server端
import socket sk = socket.socket() sk.bind((‘127.0.0.1‘, 9000)) sk.listen() conn, addr = sk.accept() conn.send(b‘hello,‘) conn.send(b‘world‘) conn.close()
client端
import socket sk = socket.socket() sk.connect((‘127.0.0.1‘, 9000)) ret1 = sk.recv(1024) print(ret1, len(ret1)) # b‘hello,world‘ 11 ret2 = sk.recv(1024) print(ret2, len(ret2)) # b‘‘ 0 sk.close()
註意:只有TCP有粘包現象,UDP永遠不會粘包
二、黏包成因
1、合包現象:
數據很短
時間間隔短
2、拆包現象:
大數據會發生拆分
不會一次性的全部發送到對方
對方在接收的時候很可能沒用辦法一次性接收到所有的信息
那麽沒有接收完的信息很可能和後面的信息黏在一起
3、黏包現象只發生在tcp協議
tcp協議的傳輸是:流式傳輸
每一條信息與信息之間是沒有邊界
4、udp協議中是不會發生黏包現象的
適合短數據的發送
不建議發送過長的數據
會增大數據丟失的幾率
會發生黏包的兩種情況:
1、發送方的緩存機制
發送端需要等緩沖區滿才發送出去,造成粘包(發送數據時間間隔很短,數據了很小,會合到一起,產生粘包)
2、接收方的緩存機制
接收方不及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據,產生粘包)
總結:
黏包現象只發生在tcp協議中:
1.從表面上看,黏包問題主要是因為發送方和接收方的緩存機制、tcp協議面向流通信的特點。
2.實際上,主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的
三、黏包的解決方案
1、解決方案:
問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的字節流總大小讓接收端知曉,然後接收端來一個死循環接收完所有數據。
server端:
import socket sk = socket.socket() sk.bind((‘127.0.0.1‘, 9000)) sk.listen() conn, addr = sk.accept() inp = input(‘>>>‘) s = str(len(inp)).zfill(4) + inp # 在輸入的內容前面拼上它的長度(4位數,不足在前面用0補齊) conn.send(s.encode(‘utf-8‘)) conn.close() ‘‘‘ 兩個弊端: 復雜 最多也只能一次性傳遞9999個字節 ‘‘‘
client端:
import socket sk = socket.socket() sk.connect((‘127.0.0.1‘, 9000)) ret1 = sk.recv(4) # 先接收前面的長度 num1 = int(ret1.decode(‘utf-8‘)) ret = sk.recv(num1) # 再以長度接收 print(ret.decode(‘utf-8‘)) sk.close()
存在的問題: 程序的運行速度遠快於網絡傳輸速度,所以在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗
2、解決方案進階
我們可以借助一個模塊,這個模塊可以把要發送的數據長度轉換成固定長度的字節。這樣客戶端每次接收消息之前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,那麽最終接受的數據只要達到這個值就停止,就能剛好不多不少的接收完整的數據了。
struct模塊:
該模塊可以把一個類型,如數字,轉成固定長度的bytes
print(struct.pack(‘i‘,1111111111111)) struct.error: ‘i‘ format requires -2147483648 <= number <= 2147483647 #這個是範圍
借助struct模塊,我們知道長度數字可以被轉換成一個標準大小的4字節數字。因此可以利用這個特點來預先發送數據長度。
發送時 | 接收時 |
先發送struct轉換好的數據長度4字節 | 先接受4個字節使用struct轉換成數字來獲取要接收的數據長度 |
再發送數據 | 再按照長度接收數據 |
server端:
import struct import socket sk = socket.socket() sk.bind((‘127.0.0.1‘, 9000)) sk.listen() conn, addr = sk.accept() while 1: s = input(‘>>>‘).encode(‘utf-8‘) pack_num = struct.pack(‘i‘, len(s)) conn.send(pack_num) conn.send(s) conn.close()
client端:
import socket import struct sk = socket.socket() sk.connect((‘127.0.0.1‘, 9000)) while 1: pack_num = sk.recv(4) num = struct.unpack(‘i‘, pack_num)[0] ret = sk.recv(num) print(ret.decode(‘utf-8‘)) sk.close()
《Python》網絡編程之黏包