006---粘包現象分析以及解決粘包問題
阿新 • • 發佈:2019-02-03
size 字節流 inpu 問題 字節數 con sub while 丟失
粘包
什麽是粘包?
須知:只有TCP有粘包現象、UDP永遠不會粘包。
socket收發消息的原理
模擬ssh遠程執行的命令
# 服務端 import subprocess, socket sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1', 8011)) sk.listen(5) print('starting...1') while True: conn, addr = sk.accept() while True: try: # 收命令 cmd = conn.recv(1024) if not cmd: break print('客戶端發來的數據', cmd.decode('utf-8')) # 執行命令,拿到結果 obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) res = obj.stdout.read() res_error = obj.stderr.read() if not res and not res_error: conn.send('呵呵呵'.encode('gbk')) # 返回執行命令的結果給客戶端 conn.send(res + res_error) # 可以優化 print(len(res) + len(res_error)) except ConnectionResetError: break conn.close() sk.close() # 客戶端 import socket sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) sk.connect(('127.0.0.1', 8011)) while True: # 發命令 cmd = input('輸入你的cmd:').strip() if not cmd: continue sk.send(cmd.encode('utf-8')) # 接收執行命令的結果 data = sk.recv(1024) print(data.decode('gbk')) sk.close()
分析
正常的cmd命令ipconfig
應該顯示完全,可我們的tcp協議模擬的服務器和客戶端並沒有顯示完全。
發送端可以是1k1k的發送數據,而接收端可以2k2k的提取數據,甚至10k10k的提取。也就是說應用程序看到的數據是一個流。
只有tcp有粘包現象,udp永遠不會有粘包現象。
udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節的數據就算完成,若是y>x數據就丟失,這意味著udp根本不會粘包,但是會丟數據,不可靠
tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。
分類:
- 發送端粘包:需要等到緩沖區滿了一定字節的數據才發給服務端。造成粘包。發送時間的間隔很短,數據很小,合到一起就粘包了。
# 服務端 import socket sk = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) sk.bind(('127.0.0.1',8011)) sk.listen(5) conn,addr = sk.accept() data1 = conn.recv(1024) print('第一次',data1) data2 = conn.recv(1024) print('第二次',data2) # 客戶端 import socket client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) client.connect(('127.0.0.1',8011)) client.send('hello'.encode('utf-8')) client.send('p1111'.encode('utf-8')) client.send('p1111'.encode('utf-8')) client.send('p1111'.encode('utf-8'))
- 接收方粘包(之前的模擬的cmd命令):接收的字節數小於發送方發送的字節數。所以,接收方的緩沖區有積壓數據,下次接收就是直接取已殘留的數據。
解決粘包問題
為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然後再取真實數據。
struct模塊
該模塊可以把一個類型,如數組轉化為固定長度的bytes。
服務端
import subprocess
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(('127.0.0.1', 8005))
sk.listen(5)
print('starting...1')
while True:
conn, addr = sk.accept()
while True:
try:
cmd = conn.recv(1024)
if not cmd:
break
print('客戶端發來的命令:', cmd.decode('utf-8'))
obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = obj.stdout.read()
res_error = obj.stderr.read()
if not res and not res_error:
conn.send('呵呵呵'.encode('gbk'))
length = len(res + res_error)
print('給客戶端發送的執行命令結果的長度:', length)
import struct
data_length = struct.pack('i', length)
conn.send(data_length)
conn.send(res + res_error)
except ConnectionResetError:
break
conn.close()
sk.close()
客戶端
import socket
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sk.connect(('127.0.0.1', 8005))
while True:
# 發命令
cmd = input('輸入你的cmd:').strip()
if not cmd: continue
sk.send(cmd.encode('utf-8'))
import struct
data_length = sk.recv(4)
length = struct.unpack('i', data_length)[0]
print('客戶端接收服務端發來的數據長度:', length)
recv_size = 0
recv_msg = b''
while recv_size < length:
recv_msg += sk.recv(1024)
recv_size = len(recv_msg)
print('執行結果為:', recv_msg.decode('gbk'))
sk.close()
006---粘包現象分析以及解決粘包問題