粘包問題
TCP協(xié)議作為流式協(xié)議奏寨,只有TCP協(xié)議存在粘包問題鲸沮。
發(fā)送端可以是一K一K地發(fā)送數(shù)據(jù),而接收端的應(yīng)用程序可以兩K兩K地提走數(shù)據(jù)弯淘,當(dāng)然也有可能一次提走3K或6K數(shù)據(jù)榆综,或者一次只提走幾個字節(jié)的數(shù)據(jù)妙痹,也就是說,應(yīng)用程序所看到的數(shù)據(jù)是一個整體鼻疮,或說是一個流(stream)怯伊,一條消息有多少字節(jié)對應(yīng)用程序是不可見的,因此TCP協(xié)議是面向流的協(xié)議判沟,這也是容易出現(xiàn)粘包問題的原因耿芹。而UDP是面向消息的協(xié)議,每個UDP段都是一條消息挪哄,應(yīng)用程序必須以消息為單位提取數(shù)據(jù)吧秕,不能一次提取任意字節(jié)的數(shù)據(jù),這一點和TCP是很不同的迹炼。怎樣定義消息呢砸彬?可以認(rèn)為對方一次性write/send的數(shù)據(jù)為一個消息,需要明白的是當(dāng)對方send一條信息的時候斯入,無論底層怎樣分段分片砂碉,TCP協(xié)議層會把構(gòu)成整條消息的數(shù)據(jù)段排序完成后才呈現(xiàn)在內(nèi)核緩沖區(qū)。
例如基于tcp的套接字客戶端往服務(wù)端上傳文件刻两,發(fā)送時文件內(nèi)容是按照一段一段的字節(jié)流發(fā)送的增蹭,在接收方看了,根本不知道該文件的字節(jié)流從何處開始闹伪,在何處結(jié)束
所謂粘包問題主要還是因為接收方不知道消息之間的界限沪铭,不知道一次性提取多少字節(jié)的數(shù)據(jù)所造成的壮池。
此外偏瓤,發(fā)送方引起的粘包是由TCP協(xié)議本身造成的杀怠,TCP為提高傳輸效率,發(fā)送方往往要收集到足夠多的數(shù)據(jù)后才發(fā)送一個TCP段厅克。若連續(xù)幾次需要send的數(shù)據(jù)都很少赔退,通常TCP會根據(jù)優(yōu)化算法把這些數(shù)據(jù)合成一個TCP段后一次發(fā)送出去,這樣接收方就收到了粘包數(shù)據(jù)证舟。
- TCP(transport control protocol硕旗,傳輸控制協(xié)議)是面向連接的,面向流的女责,提供高可靠性服務(wù)漆枚。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket,因此抵知,發(fā)送端為了將多個發(fā)往接收端的包墙基,更有效的發(fā)到對方,使用了優(yōu)化方法(Nagle算法)刷喜,將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)残制,合并成一個大的數(shù)據(jù)塊,然后進(jìn)行封包掖疮。這樣初茶,接收端,就難于分辨出來了浊闪,必須提供科學(xué)的拆包機(jī)制恼布。 即面向流的通信是無消息保護(hù)邊界的。
- UDP(user datagram protocol搁宾,用戶數(shù)據(jù)報協(xié)議)是無連接的桥氏,面向消息的,提供高效率服務(wù)猛铅。不會使用塊的合并優(yōu)化算法字支,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個到達(dá)的UDP包奸忽,在每個UDP包中就有了消息頭(消息來源地址堕伪,端口等信息),這樣栗菜,對于接收端來說欠雌,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的疙筹。
- tcp是基于數(shù)據(jù)流的富俄,于是收發(fā)的消息不能為空禁炒,這就需要在客戶端和服務(wù)端都添加空消息的處理機(jī)制,防止程序卡住霍比,而udp是基于數(shù)據(jù)報的幕袱,即便是你輸入的是空內(nèi)容(直接回車),那也不是空消息悠瞬,udp協(xié)議會幫你封裝上消息頭们豌,實驗略
udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節(jié)的數(shù)據(jù)就算完成,若是y>x數(shù)據(jù)就丟失浅妆,這意味著udp根本不會粘包望迎,但是會丟數(shù)據(jù),不可靠
tcp的協(xié)議數(shù)據(jù)不會丟凌外,沒有收完包辩尊,下次接收,會繼續(xù)上次繼續(xù)接收康辑,己端總是在收到ack時才會清除緩沖區(qū)內(nèi)容摄欲。數(shù)據(jù)是可靠的,但是會粘包晾捏。
一蒿涎、遠(yuǎn)程執(zhí)行命令程序解決粘包問題
server端
import subprocess
import struct
from socket import *
server = socket(AF_INET, SOCK_STREAM)
# print(server)
server.bind(('127.0.0.1', 8082))
server.listen(5)
while True:
conn, client_addr = server.accept()
print(conn)
print(client_addr)
while True:
try:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout = obj.stdout.read()
stderr = obj.stdout.read()
total_size = len(stdout) + len(stderr)
# 先發(fā)送數(shù)據(jù)的長度
conn.send(struct.pack('i',total_size))
# 發(fā)送真正的數(shù)據(jù)
conn.send(stdout)
conn.send(stderr)
except Exception:
break
conn.close()
server.close()
client端
import struct
from socket import *
client = socket(AF_INET, SOCK_STREAM)
# print(client)
client.connect(('127.0.0.1', 8082))
while True:
cmd = input(">>: ").strip()
if len(cmd) == 0:
continue
client.send(cmd.encode('utf-8'))
# 先收數(shù)據(jù)的長度
n = 0
header = b''
while n < 4:
data = client.recv(1)
header += data
n += len(data)
total_size = struct.unpack('i', header)[0]
# 收真正的數(shù)據(jù)
recv_size = 0
res = b''
while recv_size < total_size:
data = client.recv(1024)
res += data
recv_size += len(data)
print(res.decode('gbk'))
client.close()
二、定制復(fù)雜的報頭
server.py端
import subprocess
import os
import struct
import json
from socket import *
server = socket(AF_INET, SOCK_STREAM)
# print(server)
server.bind(('127.0.0.1', 8082))
server.listen(5)
while True:
conn, client_addr = server.accept()
print(conn)
print(client_addr)
while True:
try:
msg = conn.recv(1024).decode('utf-8')
cmd,file_path=msg.split()
if cmd == "get":
# 一惦辛、制作報頭
header_dic={
"total_size":os.path.getsize(file_path),
"filename":os.path.basename(file_path),
"md5":"1231231231232132131232311" # 加密
}
header_json=json.dumps(header_dic)
header_json_bytes=header_json.encode('utf-8')
# 二劳秋、發(fā)送數(shù)據(jù)
# 1、先發(fā)送報頭的長度
header_size=len(header_json_bytes)
conn.send(struct.pack('i',header_size))
# 2胖齐、再發(fā)送報頭
conn.send(header_json_bytes)
# 3玻淑、最后發(fā)送真實的數(shù)據(jù)
with open(r'%s' %file_path,mode='rb') as f:
for line in f:
conn.send(line)
except Exception:
break
conn.close()
server.close()
client端
import struct
import json
from socket import *
client = socket(AF_INET, SOCK_STREAM)
# print(client)
client.connect(('127.0.0.1', 8082))
while True:
cmd = input(">>: ").strip() # get 文件路徑
if len(cmd) == 0:
continue
client.send(cmd.encode('utf-8'))
# 1、先接收報頭的長度
res=client.recv(4)
header_size=struct.unpack('i',res)[0]
# 2呀伙、再接收報頭
header_json_bytes=client.recv(header_size)
header_json=header_json_bytes.decode('utf-8')
header_dic=json.loads(header_json)
print(header_dic)
# 3补履、最后接收真實的數(shù)據(jù)
total_size=header_dic['total_size']
filename=header_dic['filename']
recv_size = 0
with open(r"D:\%s" %filename, mode='wb') as f:
while recv_size < total_size:
data = client.recv(1024)
f.write(data)
recv_size += len(data)
client.close()