一 .解決黏包TCP(一)shell
1.解決方案一json
問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,
如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據。
存在的問題:
程序的運行速度遠快於網絡傳輸速度,因此在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗
剛剛的方法,問題在於咱們咱們在發送
咱們能夠藉助一個模塊,這個模塊能夠把要發送的數據長度轉換成固定長度的字節。
這樣客戶端每次接收消息以前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,
那麼最終接受的數據只要達到這個值就中止,就能恰好很少很多的接收完整的數據了
server import socket server=socket.socket() server.bind(("192.168.59.1",8100)) server.listen(5) conn,addr=server.accept() while True: aa = input("請輸入命令:").strip() conn.send(aa.encode("gbk")) num = conn.recv(1024).decode("utf-8") conn.send(b"ok") res = conn.recv(int(num)).decode("gbk") print(res,"1111111111111111111111111111111111111111111111111") conn.close() server.close()
client import socket import subprocess client=socket.socket() while True: client.connect(("192.168.59.1", 8100)) cmd = client.recv(1024).decode("utf-8") res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) stdout = res.stdout.read() stderr = res.stderr.read() client.send(str(len(stderr) + len(stdout)).encode("utf-8"))
client.recv(1024) client.send(stdout) client.send(stderr) client.close()
上面代碼 好處: 肯定了咱們到底要接收多大的數據 要在文件中配置一個配置項 就是每一次 recv 的大小 bufer=4096 最大 當咱們要發生大數據時候 要明確知道發送的數據大小 告訴對方方便準確的接收因此數據 也就是這裏的 recv 應用場景: 多用於文件傳輸過程當中 大文件傳輸 必定是按照字節讀取 每一次讀固定字節 傳輸的過程當中 一邊讀 一邊傳 接收端一邊收 一邊寫 send 這個文件以前 35000字節 send(4096) 35000-4069-4039----->0 recv 這個文件 recv 35000字節 recv(2048) 35000-2048 ---->0 很差: 的地方 多了一次交互 send sendto 交互過多會致使 程序內存管理 佔用過多
二 .解決黏包TCP struct模塊(二)緩存
1.該模塊能夠把一個類型,如數字,轉成固定長度的bytes網絡
# struct 該模塊能夠把一個類型,如數字,轉成固定長度的bytes import struct # 藉助struct模塊,咱們知道長度數字能夠被轉換成一個標準大小的4字節數字。所以能夠利用這個特色來預先發送數據長度。 # 爲何 要轉換成固定長度的bytes # aa = struct.pack('i') i 表明int 就是即將要把一個數字轉換成固定長度bytes類型 #一次性發送4096 # 發送時 接收時 # 先發送struct轉換好的數據長度4字節 先接受4個字節使用struct轉換成數字來獲取要接收的數據長度 # 再發送數據 再按照長度接收數據 ret=struct.pack('i',4096) # i 表明int 就是即將要把一個數字轉換成固定長度bytes類型 打包 print(ret) # b'\x00\x10\x00\x00' num=struct.unpack('i',ret) # struct.unpack 解包 的結果必定是元組 print(num) # (4096,) print(num[0]) # 4096 # 若是發送數據時候 # 先發送長度 先接收長度
SERVER # TCP 黏包 # 連續 send 兩個小數據 # 兩個 recv 第一個特別小 # 本質: 你不知道到底接收多大的數據 # 解決問題 例如 # tcp 完美解決了黏包問題 import socket import struct server=socket.socket() server.bind(('127.0.0.1',8700)) server.listen(5) conn, addr =server.accept() while True: cmd=input("》》》") if cmd=="q": conn.send(b'q') break conn.send(cmd.encode("gbk")) num=conn.recv(4) # 4 nn=struct.unpack('i',num)[0] # 2048 res=conn.recv(int(nn)).decode("gbk") # 2048 print(res) conn.close() server.close()
CLIENT import socket import subprocess import struct s=socket.socket() s.connect(('127.0.0.1',8700)) while True: cmd=s.recv(1024).decode('gbk') if cmd=="q": break res = subprocess.Popen(cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) std_err = res.stderr.read() std_out = res.stdout.read() len_a=len(std_out)+len(std_err) num_by=struct.pack('i',len_a) ret=s.send( num_by) # 4 2048 s.send(std_err)# 1024 s.send(std_out) #1024 4+2048 print(ret) s.close()
2. 自定製報頭(使用struct)socket
協議 報文 報頭 文件傳輸 文件名字 文件大小 文件類型 存儲路徑 例如: head={'filemane':'tex.txt','filesize':60000,'filetype':'txt','filepath':r'\user\bin'} 報頭 長度 接收4個字節 send(head) 報頭 根據這4個字節獲取報頭 send(file)報文 從報頭中獲取filesize 而後根據filesize接收文件 協議就是一堆報文和報頭 ------字節
SERVER 視頻上傳 import socket import struct import json buffer=1024 # 4096 server=socket.socket() server.bind(('127.0.0.1',8600)) server.listen() conn,addr=server.accept() # 接收 head_len=conn.recv(4) 注意接收到的數據是元祖 datel=struct.unpack('i',head_len)[0] json_head=conn.recv(datel).decode("utf-8") head=json.loads(json_head) filesize=head['filesize'] with open(head['filename'],'wb')as f: while filesize: print(filesize) if filesize>=buffer: content=conn.recv(buffer) f.write(content) filesize-=buffer else: content=conn.recv(filesize) f.write(content) break conn.close() server.close()
CLIENT 視頻上傳 # 發送端 import os import socket import json import struct sk=socket.socket() sk.connect(('127.0.0.1',8600)) buffer=1024 # 4096 # 發送文件 head={'filepath':r'D:\Vidoe\vide', 'filename':r'aa.mp4', 'filesize':None } file_path=os.path.join(head['filepath'],head['filename']) filesize=os.path.getsize(file_path) head['filesize']=filesize json_head=json.dumps(head) # 把字典轉換成了str # print(json_head) # print(type(json_head)) bytes_head=json_head.encode("utf-8") # 字符串轉換成bytes # print(bytes_head) # print(type(bytes_head)) head_len=len(bytes_head) # 計算hend長度 # print(head_len) pack_len=struct.pack('i',head_len) # print(pack_len) sk.send(pack_len) # 先發送報頭的長度 注意發送過去是元祖類型數據 sk.send(bytes_head) # 再發送bytes類型的報頭 with open(file_path,'rb')as f: while filesize: print(filesize) if filesize>=buffer: content = f.read(buffer) # 每次讀出的內容 sk.send(content) filesize-=buffer else: content=f.read(filesize) sk.send(content) break sk.close() # 5.31 MB (5,575,110 字節) 原視頻 # 5.16 MB (5,415,366 字節) 上傳的視頻
文件上傳案例 tcp
server1 不黏包 多發送一次send 方法一 import socket import json import os server=socket.socket() server.bind(("192.168.59.1",8600)) server.listen(5) conn,addr=server.accept() ret_str=conn.recv(1024).decode("utf-8") ret=json.loads(ret_str) file_name=ret.get("filename") action=ret.get("action") file_size=ret.get("file_size") conn.sendall(b"200") with open(file_name,"wb") as f1: conn_len = 0 while file_size>conn_len: msg=conn.recv(1024) conn_len+=len(msg) f1.write(msg) print(f'文件大寫{file_size}---文件傳輸大小{conn_len}') print("讀取失敗了")
client1 不黏包 多發送接收次recv' 方法一 import socket import json import os client=socket.socket() client.connect(("192.168.59.1",8600)) while True: cmd=input("請輸入命令:") action,filename=cmd.strip().split(" ") flie_size=os.path.getsize(filename) ret={"action":action,"filename":filename,"file_size":flie_size} ret_str=json.dumps(ret).encode("utf-8") client.sendall(ret_str) code=client.recv(1024).decode("utf-8") if code=="200": with open(filename,"rb")as f1: for line in f1: client.sendall(line) else: print("接收失敗啦啦啦啦")
server使用 struct import json,socket,struct ser=socket.socket() ser.bind(("192.168.59.1",8600)) ser.listen(5) conn,addr=ser.accept() ret=conn.recv(4) pik=struct.unpack('i',ret)[0] ret_b=conn.recv(pik).decode("utf-8") ret_len=json.loads(ret_b) filename=ret_len.get("filename") action=ret_len.get("action") file_size=ret_len.get("file_size") buff=0 with open("tup/"+filename,"wb") as f1: while file_size>buff: cont=conn.recv(1024) buff+=len(cont) f1.write(cont)
client使用 struct
import socket cli=socket.socket() import os import json,struct cli.connect(("192.168.59.1",8600)) cmd=input("輸入指令和文件:") # act aa.jpg action,filename=cmd.strip().split(" ") file_size=os.path.getsize(filename) ret={ "action":action, "filename":filename, "file_size":file_size } ret_str=json.dumps(ret).encode("utf-8") ret_pik=struct.pack("i",len(ret_str)) cli.sendall(ret_pik) cli.sendall(ret_str) with open(filename,"rb")as f1: for line in f1: cli.sendall(line)
文件上傳(使用hashlib 驗證文件一次性)ide
server aa 11.txt import json,socket,struct,hashlib ser=socket.socket() ser.bind(("192.168.59.1",8600)) ser.listen(5) conn,addr=ser.accept() ret=conn.recv(4) pik=struct.unpack('i',ret)[0] ret_b=conn.recv(pik).decode("utf-8") ret_len=json.loads(ret_b) filename=ret_len.get("filename") action=ret_len.get("action") file_size=ret_len.get("file_size") buff=0 md=hashlib.md5() with open("tup/"+filename,"wb") as f1: while file_size>buff: cont=conn.recv(1024) buff+=len(cont) f1.write(cont) md.update(cont) print("接收成功") conn.sendall(b"ok") print(md.hexdigest()) val_md=md.hexdigest() cli_md5=conn.recv(1024).decode("utf-8") if cli_md5==val_md: conn.sendall(b"203") else: conn.sendall(b"208")
client aa 11.txt import hashlib import socket,json,os cli=socket.socket() import os import json,struct cli.connect(("192.168.59.1",8600)) cmd=input("輸入指令和文件:") # act aa.jpg action,filename=cmd.strip().split(" ") file_size=os.path.getsize(filename) ret={ "action":action, "filename":filename, "file_size":file_size } md5=hashlib.md5() ret_str=json.dumps(ret).encode("utf-8") ret_pik=struct.pack("i",len(ret_str)) cli.sendall(ret_pik) cli.sendall(ret_str) with open(filename,"rb")as f1: for line in f1: cli.sendall(line) md5.update(line) data=cli.recv(1024) print(data.decode("utf-8")) print(md5.hexdigest()) val_md=md5.hexdigest() cli.sendall(val_md.encode("utf-8")) is_val=cli.recv(1024).decode("utf-8") if is_val=="203": print("完整性") else: print("文件上次失敗")
總結性能
能夠經過struct模塊來定製協議
解決黏包 問題
爲何會出現黏包現象
首頁只有在tcp協議中才會黏包現象
只是由於tcp協議是面向流的協議
在發送的數據傳輸過程當中還有緩存機制來避免數據丟包
所以 在連續發送小數據的時候 以及接收大小不符的時候容易黏包 現象
本質 仍是由於咱們在接收數據的時候不知道發送的數據的長短
解決黏包問題
在傳輸大量數據以前先告訴接收端要發送的數據長短
能夠經過struct模塊來定製協議
struct 模塊
pack unpack
模式 i
pack以後的長度 4個字節
unpack 以後娜到的數據是一個元組 元組的第一個值是pack的值