Python----socket 解決粘包
粘包出現的原因:
UDP沒有粘包是因為UDP是面向訊息的,TCP出現是因為TCP的工作原理出現的粘包現象。TCP是面向流的,沒有起點和結尾,不知道一條訊息有多少位元組,UDP傳送訊息會發送訊息的長度,這也就是為什麼UDP傳送訊息為空時不會阻塞,只有緩衝區為空時才會阻塞recv()函式。面向流的訊息(TCP)是無邊界的,面向報文的訊息(UDP)是有邊界的。只有TCP有粘包現象(nagle演算法的存在)。TCP套接字沒有一收一發的規矩,而UDP有這個規則。為什麼說tcp可靠是因為每次發訊息都會從客戶端返回一個ack,才會刪除自己快取的資訊。
粘包出現的兩種方式:
1、服務端出現粘包:當傳送的訊息資料的間隔短,會將幾次的分開的訊息一次性
服務端:一次性收到‘hai’。
import socket ip_port = ('127.0.0.1', 8080) back_log = 5 buffer_size = 1024 ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ser.bind(ip_port) ser.listen(back_log) while 1: con, address = ser.accept() res = con.recv(1024) print(res) >>> b'hai'
客戶端:分三次傳送'h','a','i'.
import socket
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
p.send('h'.encode('utf-8'))
p.send('a'.encode('utf-8'))
p.send('i'.encode('utf-8'))
2、客戶端出現粘包:資料無法一次性讀完
客戶端:兩次接收都是上次的資料。
import socket p = socket.socket(socket.AF_INET, socket.SOCK_STREAM) p.connect(('127.0.0.1', 8080)) print(p.recv(2)) print(p.recv(2)) >>> b'12' >>> b'34'
服務端:一次性發送大量的資料
import socket
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
con, address = ser.accept()
con.send('123456987845641321545645641654564165132'.encode('utf-8'))
解決粘包的方案:
服務端:
import socket
import subprocess
# 配置資訊
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
# 宣告套接字型別
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
# 連線迴圈
while 1:
con, address = ser.accept()
# 通訊迴圈
while 1:
try:
msg = con.recv(buffer_size)
print('伺服器收到訊息', msg.decode('utf-8'))
if msg.decode('utf-8') == '1':
con.close()
# 子程序Popen的方式去執行一個命令,輸出的結果放到管道中
res = subprocess.Popen(msg.decode('utf-8'), shell=True, stdout=subprocess.PIPE)
# 從管道中讀取資訊
fin = res.stdout.read().decode('gbk')
# 傳送資料位元組長度,防止粘包
bit = fin.encode('utf-8').__sizeof__()
con.send(str(bit).encode('utf-8')) # 這兩次粘一起,第一個send用固定的位元組接受,就不會出現粘包
con.send(fin.encode('utf-8'))
except Exception as e:
break
客戶端:
import socket
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
msg = input('please input')
# 防止傳送空訊息
if not msg:
continue
# 輸入1退出
if msg == '1':
break
# 傳送訊息時編碼
p.send(msg.encode('utf-8'))
# 接收需要正式接受多少位元組長度的資料長度,防止粘包
bit = p.recv(24)
# 接收資料
data = p.recv(int(bit.decode('utf-8')))
print(data.decode('utf-8'))
p.close()
TIPS:
✳:result = subprocess.Popen('字串的命令',shell=True,stdout=subprocess.Pipe,stdin=subprocess.Pipe,stderr=subprocess.Pipe)
shell=True 是否執行cmd,stdout、stdin、stderr=subprocess.Pipe 標準輸出、標準輸入、標準錯誤都輸出到管道內,預設丟給螢幕。
stdout.out.read() 讀出資料。
✳:傳送長度時也可以用struct模組:pack('i',資料) 壓包 , unpack('i',資料) 解包
‘ i ’表示int型,‘ch’表示字元等等。。。。
✳:s.connect_ex()函式是connect拓展出錯時不會報異常,返回異常.
s.getsockname() 獲取當前套接字的地址.
s.getpeername()連線到當前套接字的遠端地址.
✳:兩個send中間加一個rece就可以解決粘包
✳:sendall方法就是反覆呼叫send方法直到檔案傳輸完畢,如果中間發生錯誤就不會返回穿了多少資料.
✳:一次send最大8k,當傳送一個較大的檔案時,不可能一次性發過去因為記憶體不夠用,所以每次傳送8k的資料8192個位元組。
偏函式:
只能繫結第一個引數的成為固定值
from functools import partial
def add(x,y):
return x+y
func = partial(add,1) # 繫結一個引數預設值為一
print(func(1))