當發送端緩衝區的長度大於網卡的MTU時,tcp會將此次發送的數據拆成幾個數據包發送出去。
MTU是Maximum Transmission Unit的縮寫。意思是網絡上傳送的最大數據包。MTU的單位是字節。大部分網絡設備的MTU都是1500。
若是本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,這樣會產生不少數據包碎片,增長丟包率,下降網絡速度
TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。
收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。
這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
對於空消息:tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),也能夠被髮送,udp協議會幫你封裝上消息頭髮送過去。
可靠黏包的tcp協議:tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。
總結:算法
黏包有兩種:shell
一種是由於發送數據包時,每次發送的包小,由於系統進行優化算法,就將兩次的包放在一塊兒發送,減小了資源的重複佔用。屢次發送會經歷屢次網絡延遲,一塊兒發送會減小網絡延遲的次數。所以在發送小數據時會將兩次數據一塊兒發送,而客戶端接收時,則會一併接收。#即出現屢次send會出現黏包json
第二種是由於接收數據時,又屢次接收,第一次接收的數據量小,致使數據還沒接收完,就停下了,剩餘的數據會緩存在內存中,而後等到下次接收時和下一波數據一塊兒接收。windows
1,問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據。緩存
#_*_coding:utf-8_*_ import socket,subprocess ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(ip_port) s.listen(5) while True: conn,addr=s.accept() print('客戶端',addr) while True: msg=conn.recv(1024) if not msg:break res=subprocess.Popen(msg.decode('utf-8'),shell=True,\ stdin=subprocess.PIPE,\ stderr=subprocess.PIPE,\ stdout=subprocess.PIPE) err=res.stderr.read() if err: ret=err else: ret=res.stdout.read() data_length=len(ret) conn.send(str(data_length).encode('utf-8')) data=conn.recv(1024).decode('utf-8') if data == 'recv_ready': conn.sendall(ret) conn.close() 服務端
#_*_coding:utf-8_*_ import socket,time s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if len(msg) == 0:continue if msg == 'quit':break s.send(msg.encode('utf-8')) length=int(s.recv(1024).decode('utf-8')) s.send('recv_ready'.encode('utf-8')) send_size=0 recv_size=0 data=b'' while recv_size < length: data+=s.recv(1024) recv_size+=len(data) print(data.decode('utf-8')) 客戶端
2.使用time模塊,在每次send的時候加入一個time.sleep(0.01),這種方法能夠有效地隔開兩次send,斷開系統的優化,此種方法雖然能夠解決黏包問題,可是會形成發送數據時間長服務器
import socket sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() conn,addr = sk.accept() ret1 = conn.recv(12) print(ret1) ret2 = conn.recv(12) # ret3 = conn.recv(12) # print(ret2) print(ret3) conn.close() sk.close()
import socket sk = socket.socket() sk.connect(('127.0.0.1',8090)) sk.send(b'hello') import time time.sleep(0.01) sk.send(b'egg') sk.close()
3,先讀取文件的大小,而後將文件的大小發送給接收端,這樣接收端就能夠以文件大小來寫入數據。網絡
import json import socket import struct sk =socket.socket()#建立一個socket對象 sk.bind(('127.0.0.1',8080))#綁定本地ip地址與端口 sk.listen()#開啓監聽 buffer =1024 #設置buffer值大小 conn,addr =sk.accept()#等待客戶端鏈接服務端,獲得地址與雙共工通道 head_len=conn.recv(4)#接收用struck將數字轉長度爲4的bytes head_len =struct.unpack('i',head_len)[0]#調用struct模塊來解包,獲得原來的數字(數字爲報頭的長度) json_head =conn.recv(head_len).decode('utf-8')#接收json序列化的報頭進行解碼 head =json.loads(json_head)#將json序列化的報頭進行反序列化 filesize =head['filesize']#拿到head字典中鍵filesize所對應的值 print(filesize)#打印filesize with open(r'dir\%s'%head['filename'],'wb')as f:#dir\文件名,拿到文件的路徑,以wb模式打開 while filesize:#當filesize(文件內剩餘內容的大小)有值時 if filesize >=buffer:#若是filesize>= buffer值,buffer值是設定的一次接收多少字節的內容 print(filesize) #打印filesize大小 content =conn.recv(buffer)#接收buffer值大小的內容 f.write(content)#寫入文件 filesize -=buffer#原來的文件大小減去接收的內容,等於剩餘文件的大小 else:#若是文件剩餘的內容大小<buffer設定的大小,就所有接收 content =conn.recv(filesize) f.write(content) filesize =0 print('=====>',len(content)) print(filesize) print('服務器端') conn.close() sk.close()
import struct import os import json import socket sk =socket.socket sk.connect(('127.0.0.1',8090)) buffer =1024 head ={'filepath':r'D:\Documents\oCam', 'filename':r'test.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)#將head字典序列化 bytes_head =json_head.encode('utf-8')#將序列化以後的字典進行解碼 head_len =len(bytes_head)#計算轉碼以後字典的長度 pack_len =struct.pack('i',head_len)#調用struct模塊將長度轉換成長度爲4的bytes類型 sk.send(pack_len)#發送pack_len sk.send(bytes_head)#發送bytes_head with open(file_path,'rb')as f: while filesize: print(filesize) if filesize>=buffer: content =f.read(buffer) print('====>',len(content)) sk.send(content) filesize-=buffer else: content =f.read(filesize) sk.send(content) filesize=0 sk.close()
首先只有在TCP協議中才會出現黏包現象socket
是由於TCP協議是面向流的協議tcp
在發送的數據傳輸的過程當中海油緩存機制來避免數據丟失ide
由於在連續發送小數據的時候、以及接收大小不符的時候都容易出現黏包現象
本質仍是由於咱們在接收數據的時候不知道發送的數據的長短
解決黏包問題
在傳輸大量數據以前先告訴數據量的大小。
4,使用struct解決黏包
import socket,struct,json import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080)) phone.listen(5) while True: conn,addr=phone.accept() while True: cmd=conn.recv(1024) if not cmd:break print('cmd: %s' %cmd) res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err=res.stderr.read() print(err) if err: back_msg=err else: back_msg=res.stdout.read() conn.send(struct.pack('i',len(back_msg))) #先發back_msg的長度 conn.sendall(back_msg) #在發真實的內容 conn.close()
#_*_coding:utf-8_*_ import socket,time,struct s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if len(msg) == 0:continue if msg == 'quit':break s.send(msg.encode('utf-8')) l=s.recv(4) x=struct.unpack('i',l)[0] print(type(x),x) # print(struct.unpack('I',l)) r_s=0 data=b'' while r_s < x: r_d=s.recv(1024) data+=r_d r_s+=len(r_d) # print(data.decode('utf-8')) print(data.decode('gbk')) #windows默認gbk編碼