********黏包******** ****黏包現象**** 基於tcp先製做一個遠程執行命令的程序(命令 ls -l ; |||; pwd) res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 的結果的編碼是以當前所在的系統爲準的,若是是windows,那麼res.stdout.read()讀出 的就是GBK編碼的,在接收端須要用GBK解碼。 且只能從管道里讀一次結果 r = subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # subprocess.Popen(cmd,shell=True,subprocess.stdout,subprocess.stderr) # cmd : 表明系統命令 # shell = True 表明這條命令是 系統命令,告訴操做系統,將cmd當成系統命令去執行 # stdout 是執行完系統命令以後,用於保存結果的一個管道 # stderr 是執行完系統命令以後,用於保存錯誤結果的一個管道 print(r.stdout.read().decode('gbk')) print(r.stderr.read().decode('gbk')) #由於只能從管道中讀一次結果,若是我咱們想屢次使用,能夠拿一個變量接收一下。 what? 黏包 同時執行多條命令以後,獲得的結果極可能只有一部分,在執行其餘命令的時候又接收到 以前執行的另外一部分結果,這種現象就是黏包。 **基於tcp協議實現的黏包 #server端 import socket import subprocess ip_port = ('127.0.0.1', 8080) BUFSIZE = 1024 tcp_socket_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) tcp_socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(4) while 1: conn, addr = tcp_socket_server.accept() print('客戶端', addr) while 1: cmd = conn.recv(BUFSIZE) res = subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) stdeer = res.stderr.read() stdout = res.stdout.read() conn.send(stdeer) conn.send(stdout) #client端 import socket BUFSIZZE = 1024 ip_port = ('127.0.0.1', 8080) s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) res = s.connect_ex(ip_port) while 1: msg = input('>>>:').strip() if len(msg)==0:continue if msg =='q':break s.send(msg.encode('utf-8')) act_res = s.recv(BUFSIZZE) print(act_res.decode('utf-8')) **基於udp協議實現以上代碼---(發現:只有TCP有黏包現象,UDP永遠不會黏包) #server端 import socket import subprocess ip_port = ('127.0.0.1', 8080) BUFSIZE = 1024 udp_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) udp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) udp_server.bind(ip_port) while 1: cmd, addr = udp_server.recvfrom(BUFSIZE) print('命令--->', cmd) res = subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE) stdeer = res.stderr.read() stdout = res.stdout.read() udp_server.sendto(stdeer, addr) udp_server.sendto(stdout, addr) udp_server.close() #client端 import socket ip_port = ('127.0.0.1', 8080) BUFSIZE = 1024 udp_client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) while 1: msg = input(">>>>").strip() udp_client.sendto(msg.encode('utf-8'), ip_port) err, addr = udp_client.recvfrom(BUFSIZE) out, addr = udp_client.recvfrom(BUFSIZE) if err: print('error: %s ' % (err.decode('utf-8')), end='') if out: print(out.decode('utf-8'), end='') **************只有TCP有黏包現象,UDP永遠不會黏包 why? 黏包
****TCP協議中的數據傳輸 **tcp協議中拆包機制 當發送端緩衝區的長度大於網卡的MTU時間,tcp會將此次發送的數據拆成幾個數據包發送出去。 MTU是Maximum Transmission Unit的縮寫,意識是網絡上傳送的最大數據包。 MTU的單位是字節。大部分網絡設備的MTU都是1500.若是本機的MTU不網關的MTU大, 大的數據包就會被拆開來傳送,這樣會產生不少數據包碎片,增長丟包率,下降網絡速度。 **面向流的通訊特色和Nagle算法 TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。 收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包, 更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大 的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通 信是無消息保護邊界的。 對於空消息:tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶 端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車), 也能夠被髮送,udp協議會幫你封裝上消息頭髮送過去。 可靠黏包的tcp協議:tcp的協議數據不會丟,沒有收完包, 下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包 **基於tcp協議特色的黏包現象成因 1.發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據, 固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據。也就是說, 應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少 字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。 2.而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據, 不能一次提取任意字節的數據,這一點和TCP是很不一樣的。 3.怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對 方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序 完成後才呈如今內核緩衝區。 例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的, 在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束此外,發送方引發的粘包是由 TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。 若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送 出去,這樣接收方就收到了粘包數據。 ****UDP不會發生黏包 **UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。 不會使用塊的合併優化算法,, 因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區) 採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息), 這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的。 **對於空消息:tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的 處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),也能夠被髮送, udp協議會幫你封裝上消息頭髮送過去。 **不可靠不黏包的udp協議:udp的recvfrom是阻塞的,一個recvfrom(x)必須對惟一一個sendinto(y), 收完了x個字節的數據就算完成,如果y;x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠。 補充: **用UDP協議發送時,用sendto函數最大能發送數據的長度爲:65535- IP頭(20) – UDP頭(8)=65507字節。 用sendto函數發送數據時,若是發送數據長度大於該值,則函數會返回錯誤。(丟棄這個包,不進行發送) **用TCP協議發送時,因爲TCP是數據流協議,所以不存在包大小的限制(暫不考慮緩衝區的大小),這是指在 用send函數時,數據長度參數不受限制。而實際上,所指定的這段數據並不必定會一次性發送出去,若是這 段數據比較長,會被分段發送,若是比較短,可能會等待和下一次數據一塊兒發送 ******會發生黏包的兩種狀況****** ****狀況一 發送方的緩衝機制 發送端須要等緩衝區滿才發送出去,形成黏包()發送數據時間間隔很短,數據很小,會合到一塊兒,產生黏包 例子: #server端 import socket sk = socket.socket() sk.bind(('127.0.0.1', 8888)) sk.listen() conn, addr = sk.accept() conn.send(b'hello') conn.send(b'world') conn.close() sk.close() #client端 import socket sk = socket.socket() sk.connect_ex(('127.0.0.1', 8888)) msg1 = sk.recv(1024) print('msg1:', msg1) msg2 = sk.recv(1024) print('msg2:', msg2) sk.close() ****狀況二 接收方的緩衝機制 接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務 端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生黏包) 例子: #server端 import socket sk = socket.socket() sk.bind(('127.0.0.1', 8888)) sk.listen() conn, addr = sk.accept() data1 = conn.recv(2) data2 = conn.recv(10) print(data1) print(data2) conn.close() sk.close() #client端 import socket sk = socket.socket() sk.connect_ex(('127.0.0.1', 8888)) sk.send('hello word'.encode('utf-8')) sk.close() 總結: 黏包現象只發生在tcp協議中: 1.從表面上看,黏包問題主要是由於發送方和接收方的緩存機制、tcp協議面向流通訊的特色 2.實際上,主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。 ********黏包解決方案*******