1 什麼是粘包

只有TCP有粘包現象,UDP永遠不會粘包

應用程式所看到的資料是一個整體,或說是一個流(stream),一條訊息有多少位元組對應用程式是不可見的,因此TCP協議是面向連線的,面向流的,收發兩端都要有一一成對的socket,因此,傳送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法,將多次間隔較小且資料量小的資料,合併成一個大的資料塊,然後進行封包。這也是容易出現粘包問題的原因;

而UDP面向訊息的協議,每個UDP段都是一條訊息,應用程式必須以訊息為單位提取資料,不能一次提取任意位元組的資料。

粘包問題主要還是因為接收方不知道訊息之間的界限,不知道一次性提取多少位元組的資料所造成的。

兩種情況下會發生粘包

(1.)傳送端需要等緩衝區滿才傳送出去,造成粘包。傳送資料時間間隔很短,資料很小會合到一起,產生粘包

(2)接收方不及時收取緩衝區的包,造成多個包接收,客戶端傳送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包

#服務端
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.0.0.1",8080)
phone.bind(ip_port)
phone.listen(5)
conn,addr=phone.accept()
data1=conn.recv(1024)
data2=conn.recv(1024)
print("第1個包",data1)
print("第2個包",data2) #客戶端
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.0.0.1",8080)
phone.connect(ip_port)
phone.send("helloworld".encode("utf8"))
phone.send("SB".encode("utf8")) >>
第1個包 b'helloworldSB'#產生粘包現象
第2個包 b''

使用時間模組,並不能夠徹底解決問題

#服務端
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.0.0.1",)
phone.bind(ip_port)
phone.listen()
conn,addr=phone.accept()
data1=conn.recv()
data2=conn.recv()
print("第1個包",data1)
print("第2個包",data2) #客戶端
import socket,time
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.0.0.1",)
phone.connect(ip_port)
phone.send("helloworld".encode("utf8"))
time.sleep()
phone.send("SB".encode("utf8")) >>
第1個包 b'helloworld'#send先執行一次後,傳送至客戶端記憶體
第2個包 b'SB'#延遲三秒後,,再執行傳送
#服務端
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.0.0.1",8080)
phone.bind(ip_port)
phone.listen(5)
conn,addr=phone.accept()
data1=conn.recv(1)
data2=conn.recv(1024)
print("第1個包",data1)
print("第2個包",data2) #客戶端
import socket,time
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.0.0.1",8080)
phone.connect(ip_port) phone.send("helloworld".encode("utf8"))
time.sleep(3)
phone.send("SB".encode("utf8"))
>>
第1個包 b'h' #recv第一次只取一個位元組
第2個包 b'elloworld' #第二次再次執行 ,”SB”還在伺服器的記憶體中

2 自定製報頭解決粘包問題

資料封裝報頭:

固定長度

包含對將要傳送資料的描述資訊

struct模組
import struct
print(struct.pack("i",111))
>>
b'o\x00\x00\x00' #轉成位元組模式 import struct
res=struct.pack("i",111)#struct.pack 打包
print(len(res))
>>
4 #轉成位元組長度為固定4 import struct
res=struct.pack("i",111)
# print(len(res))
print(struct.unpack("i",res))#解包
(111,)#獲得以元組形式的結果

簡單實現:

#服務端
import socket
import struct
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.168.0.1",8080)
phone.bind(ip_port)
phone.listen(5)
#連線迴圈
while True:
conn,addr=phone.accept()
print("cline addr",addr)
#通訊迴圈
while True:
try:
cmd=conn.recv(1024)
res=subprocess.Popen(cmd.decode("utf8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out_res=res.stdout.read()
err_res=res.stderr.read()
data_size=len(out_res)+len(err_res)
#傳送報頭
conn.send(struct.pack("i",data_size))
#傳送資料部分
conn.send(out_res)
conn.send(err_res)
except Exception:
break
conn.close()
phone.close() #客戶端
import socket
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.168.0.1",8080)
phone.connect(ip_port)
#通訊迴圈
while True:
#發訊息
cmd=input(">>").strip()
if not cmd:continue
phone.send(bytes(cmd,encoding="utf8"))
#收報頭
baotou=phone.recv(4)
data_size=struct.unpack("i",baotou)[0]
#收資料
recv_size=0
recv_data=b""
while recv_size<data_size:
data=phone.recv(1024)
recv_size+=len(data)
recv_data+=data
print(recv_data.decode("gbk"))
phone.close()

完全解決:

#服務端
import socket
import struct
import subprocess
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.168.0.1",8080)
phone.bind(ip_port)
phone.listen(5)
#連線迴圈
while True:
conn,addr=phone.accept()
print("cline addr",addr)
#通訊迴圈
while True:
try:
cmd=conn.recv(1024)
res=subprocess.Popen(cmd.decode("utf8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out_res=res.stdout.read()
err_res=res.stderr.read()
data_size=len(out_res)+len(err_res)
head_dic={"data_size":data_size}
head_json=json.dumps(head_dic)
head_bytes=head_json.encode("utf8")
#傳送報頭
head_len=len(head_bytes)
conn.send(struct.pack("i",data_size))
conn.send(head_bytes)
#傳送資料部分
conn.send(out_res)
conn.send(err_res)
except Exception:
break
conn.close()
phone.close() #客戶端
import socket
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=("127.168.0.1",8080)
phone.connect(ip_port)
#通訊迴圈
while True:
#發訊息
cmd=input(">>").strip()
if not cmd:continue
phone.send(bytes(cmd,encoding="utf8"))
#收報頭的長度
head_struct=phone.recv(4)
head_len=struct.unpack("i",head_len)[0] #再收報頭
head_bytes=phone.recv(head_len)
head_json=head_bytes.decode("utf8")
head_dic=json.loads(head_json)
print(head_dic)
data_size=head_dic["data_size"] #收資料
recv_size=0
recv_data=b""
while recv_size<data_size:
data=phone.recv(1024)
recv_size+=len(data)
recv_data+=data
print(recv_data.decode("gbk"))
phone.close()