1. 程式人生 > >day 28 黏包及黏包解決方案

day 28 黏包及黏包解決方案

1.緩衝區 每個socket被建立以後,都會分配兩個緩衝區,輸入緩衝區和輸出緩衝區,預設大小都是8k,可以通過getsocket()獲取,暫時存放傳輸資料,防止程式在傳送的時候卡阻,提高程式碼執行效率. 首先看python的系統互動subprocess: import subprocess   sub_obj = subprocess.Popen(     'ls',  #系統命令     shell = True,  #固定格式
    stdout=subprocess.PIPE,  #標準輸出  PIPE管道,儲存著指令的執行結果     stderr=subprocess.PIPE   #標準錯誤輸出 )   print('正確輸出',sub_obj.stdout.read().decode('gbk')) print('錯誤輸出',sub_obj.stderr.read().decode('gbk'))
    #測試byte長度 # print(len(b'hello')) # print(bytes(str(2),encoding='utf-8')) 結果編碼是以但錢所在系統為準的,諾為windo,則用GBK解碼,且只能從管道里讀取一次結果 2.黏包現象    1.tcp兩種黏包現象:    a. 傳送端需要等緩衝區滿了才傳送出去,造成黏包(傳送時間的間隔很短,資料也很小,會被底層優化演算法河道一起,產生黏包現象)
server端的程式碼示例額如下: from socket import * ip_port = ('127.0.0.1',8080)   tcp_socket_server =socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen() conn,addr = tcp_socket_server.accept() #服務端連線接收兩個資訊 data1=conn.recv(10) data2=conn.recv(10)   print('------>',data1.decode('utf-8')) print('------>',data2.decode('utf-8'))   conn.close() client端的例項如下: import socket   BUFSIZE= 1024 ip_port = ('127.0.0.1',8080) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)   res = s.connect(ip_port)   s.send('hi'.encode('utf-8')) s.send('meinv'.encode('utf-8'))  b. 接受方沒有及時接受緩衝區的包,導致多個包接受,(客戶端傳送一段資料,服務端只收了一小部分,服務區 下次接受的時候還是從緩衝區拿上次遺留的資料,產生黏包)第一次如果傳送的資料大小2000B,接受端一次性接受大小為1024,這樣就導致剩下的內容會被下一次recv接收到,導致結果的錯亂. server端的程式碼示例額如下: import socket import subprocess     server = socket.socket() ip_port = ('127.0.0.1',8001)   server.bind(ip_port) server.listen()   conn, addr = server.accept()   while 1:     from_client_cmd = conn.recv(1024)     print(from_client_cmd.decode('utf-8'))       sub_obj = subprocess.Popen(         from_client_cmd.decode('utf-8'),         shell=True,         stdout=subprocess.PIPE,         stderr=subprocess.PIPE     )       std_msg = sub_obj.stdout.read()     print('指令的執行結果長度>>>>',len(std_msg))       conn.send(std_msg) client端的例項如下: import socket   client = socket.socket() client.connect(('127.0.0.1',8001))   while 1:     cmd = input("請輸入指令:")       client.send(cmd.encode('utf-8'))     server_cmd_result = client.recv(1024)       print(server_cmd_result.decode('gbk')) 解決tcp黏包的方案有兩種,第一種是瞭解一下,第二種是必須記住. 1.方案一:由於接受方不知道傳送端將要傳送的自接力的長度,導致接收的尾喉,可能接收不全,或者多接收另外一次傳送的內容,把自己將要送的位元組流總大小讓對方知曉,然後接收方發一個確認訊息給傳送端,然後傳送端在傳送過來後面的真實資料,接收方在來呢接收完 server端的程式碼示例額如下: import socket import subprocess   server = socket.socket( ) ip_port = ('127.0.0.1',8001)   server.bind(ip_port) server.listen()   conn,addr = server.accept()   while 1:     from_client_cmd = conn.recv(1024)       print(from_client_cmd.decode('utf-8'))#接收到客戶端傳送來的系統指令,我服務端通過subprocess模組到服務端自己的系統裡面執行這條指令       sub_obj = subprocess.Popen(         from_client_cmd.decode('utf-8'),         shell=True,         stdout=subprocess.PIPE, #正確結果的存放的位置         stderr=subprocess.PIPE  #錯誤結果的存放的位置     )     # 從管道里面拿結果,通過subprocess.Popen的例項化物件.stdout.read()方法來獲取管道中的結果     std_msg = sub_obj.stdout.read()     #為了解決黏包現象,我們統計一下訊息的長度,現將訊息的長度傳送給客戶端,客戶端通過這個長度來接收後面我們要傳送的真實資料     std_msg_len = len(std_msg)   #首先將資料長度的資料型別裝換成bytes型別     std_bytes_len = str(len(std_msg)).encode('utf-8')     print('指令的執行結果長度>>>>>:',len(std_msg))       conn.send(std_bytes_len)     status = conn.recv(1024)     if status.decode('utf-8') == 'ok':           conn.send(std_msg)     else:         pass client端的例項如下: import socket   client = socket.socket() client.connect(('127.0.0.1',8001))   while 1:     cmd = input('請輸入指令:')       client.send(cmd.encode('utf-8'))       server_res_len = client.recv(1024).decode('utf-8')       print('來自服務端的訊息長度',server_res_len)       client.send(b'ok')       server_cmd_result = client.recv(int(server_res_len))       print(server_cmd_result.decode('gbk'))` 2.方案二: 通過struck模組將需要傳送的內容長度進行打包,打包成一個4位元組長度的資料傳送到對端,對端只要取出前4個位元組,然後對前4個位元組的資料進行捷豹,拿到傳送的長度,然後通過這個長度來繼續接受我們要傳送的內容. struck模組的使用:struck模組中最重的兩個函式就是pack()打包,unpack()解包 pack(): 我們這裡只介紹'i'這個int型別,上面的途中列舉了處理可以打包的所有的資料型別,並且struck處理pack和unpack兩個方法之外還有好多別的方法和用法. import struct num = 100 # num太大的話會報錯, # struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是範圍 # 打包,將int型別的資料打包成4個長度的bytes型別的資料 byt = struct.pack('i',num) print(byt) # 解包,將bytes型別的資料,轉換為對應的那個int型別的資料 # 注:unpack返回的是truple int_num = struct.unpack('i',byt)[0] print(int_num) 解決黏包現象的第二種方案: 服務端: import socket import subprocess import struct   server = socket.socket() ip_port = ('127.0.0.1',8001)   server.bind(ip_port) server.listen()   conn,addr = server.accept()   while 1:     from_client_cmd = conn.recv(1024)     print(from_client_cmd.decode('utf-8'))     #接收到客戶端傳送過來的系統指令,我服務端通過subprocess模組到服務端自己的系統裡面執行這條指令     sub_obj = subprocess.Popen(         from_client_cmd.decode('utf-8'),         shell=True,         stdout=subprocess.PIPE, #正確結果的存放位置         stderr=subprocess.PIPE  #錯誤結果的存放位置     )     # 從管道里面拿出結果,通過subprocess.Popen的例項化物件.stdout.read()方法來獲取管道中的結果     std_msg = sub_obj.stdout.read()     #為了解決黏包現象,我們統計了一下訊息的長度,先將訊息的長度傳送給客戶端,客戶端通過這個長度來接收後面我們要傳送的真實的資料     std_msg_len = len(std_msg)     print('指令的執行結果長度>>>>>>',len(std_msg))       msg_lenint_struct =struct.pack('i',std_msg_len)       conn.send(msg_lenint_struct+std_msg) 客戶端: import socket import struct client = socket.socket() client.connect(('127.0.0.1',8001))   while 1:     cmd = input('請輸入指令:')     #傳送指令     client.send(cmd.encode('utf-8'))     #接收資料長度,首先接收4個位元組長度的資料,因為這個4個位元組是長度     server_res_len=client.recv(4)     msg_len= struct.unpack('i',server_res_len)[0]       print('來自服務端的訊息長度',msg_len)     #通過解包出來的長度,來接收後面的真實資料     server_cmd_result =client.recv(msg_len)       print(server_cmd_result.decode('gbk')) 檢視自己的快取的大小 # print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF)) # print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)) 3. udp是面向包的,所以udp是不存在黏包的。 在udp程式碼中,我們在server端接受返回訊息的時候,我們設定的recvfrom(1024),那麼當我們輸入的執行指令為'dir'的時候,若dir在當前資料夾下輸出的內容大於1024,然後就報錯了, 解釋原因:因為udp是面向報文的,每個訊息是一個包,接收端設定接受大小的時候,必須要比你發的這個 包要大,不然一次接受不了就會報錯,而tcp是不會報錯的,這也是為什麼udp會丟包的原因  列印進度條(簡易)

 

import time   for i in range(20):       print('\r' + i*'*',end='')     time.sleep(0.2) 1.緩衝區 每個socket被建立以後,都會分配兩個緩衝區,輸入緩衝區和輸出緩衝區,預設大小都是8k,可以通過getsocket()獲取,暫時存放傳輸資料,防止程式在傳送的時候卡阻,提高程式碼執行效率. 首先看python的系統互動subprocess: import subprocess   sub_obj = subprocess.Popen(     'ls',  #系統命令     shell = True,  #固定格式     stdout=subprocess.PIPE,  #標準輸出  PIPE管道,儲存著指令的執行結果     stderr=subprocess.PIPE   #標準錯誤輸出 )   print('正確輸出',sub_obj.stdout.read().decode('gbk')) print('錯誤輸出',sub_obj.stderr.read().decode('gbk'))     #測試byte長度 # print(len(b'hello')) # print(bytes(str(2),encoding='utf-8')) 結果編碼是以但錢所在系統為準的,諾為windo,則用GBK解碼,且只能從管道里讀取一次結果 2.黏包現象    1.tcp兩種黏包現象:    a. 傳送端需要等緩衝區滿了才傳送出去,造成黏包(傳送時間的間隔很短,資料也很小,會被底層優化演算法河道一起,產生黏包現象) server端的程式碼示例額如下: from socket import * ip_port = ('127.0.0.1',8080)   tcp_socket_server =socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen() conn,addr = tcp_socket_server.accept() #服務端連線接收兩個資訊 data1=conn.recv(10) data2=conn.recv(10)   print('------>',data1.decode('utf-8')) print('------>',data2.decode('utf-8'))   conn.close() client端的例項如下: import socket   BUFSIZE= 1024 ip_port = ('127.0.0.1',8080) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)   res = s.connect(ip_port)   s.send('hi'.encode('utf-8')) s.send('meinv'.encode('utf-8'))  b. 接受方沒有及時接受緩衝區的包,導致多個包接受,(客戶端傳送一段資料,服務端只收了一小部分,服務區 下次接受的時候還是從緩衝區拿上次遺留的資料,產生黏包)第一次如果傳送的資料大小2000B,接受端一次性接受大小為1024,這樣就導致剩下的內容會被下一次recv接收到,導致結果的錯亂. server端的程式碼示例額如下: import socket import subprocess     server = socket.socket() ip_port = ('127.0.0.1',8001)   server.bind(ip_port) server.listen()   conn, addr = server.accept()   while 1:     from_client_cmd = conn.recv(1024)     print(from_client_cmd.decode('utf-8'))       sub_obj = subprocess.Popen(         from_client_cmd.decode('utf-8'),         shell=True,         stdout=subprocess.PIPE,         stderr=subprocess.PIPE     )       std_msg = sub_obj.stdout.read()     print('指令的執行結果長度>>>>',len(std_msg))       conn.send(std_msg) client端的例項如下: import socket   client = socket.socket() client.connect(('127.0.0.1',8001))   while 1:     cmd = input("請輸入指令:")       client.send(cmd.encode('utf-8'))     server_cmd_result = client.recv(1024)       print(server_cmd_result.decode('gbk')) 解決tcp黏包的方案有兩種,第一種是瞭解一下,第二種是必須記住. 1.方案一:由於接受方不知道傳送端將要傳送的自接力的長度,導致接收的尾喉,可能接收不全,或者多接收另外一次傳送的內容,把自己將要送的位元組流總大小讓對方知曉,然後接收方發一個確認訊息給傳送端,然後傳送端在傳送過來後面的真實資料,接收方在來呢接收完 server端的程式碼示例額如下: import socket import subprocess   server = socket.socket( ) ip_port = ('127.0.0.1',8001)   server.bind(ip_port) server.listen()   conn,addr = server.accept()   while 1:     from_client_cmd = conn.recv(1024)       print(from_client_cmd.decode('utf-8'))#接收到客戶端傳送來的系統指令,我服務端通過subprocess模組到服務端自己的系統裡面執行這條指令       sub_obj = subprocess.Popen(         from_client_cmd.decode('utf-8'),         shell=True,         stdout=subprocess.PIPE, #正確結果的存放的位置         stderr=subprocess.PIPE  #錯誤結果的存放的位置     )     # 從管道里面拿結果,通過subprocess.Popen的例項化物件.stdout.read()方法來獲取管道中的結果     std_msg = sub_obj.stdout.read()     #為了解決黏包現象,我們統計一下訊息的長度,現將訊息的長度傳送給客戶端,客戶端通過這個長度來接收後面我們要傳送的真實資料     std_msg_len = len(std_msg)   #首先將資料長度的資料型別裝換成bytes型別     std_bytes_len = str(len(std_msg)).encode('utf-8')     print('指令的執行結果長度>>>>>:',len(std_msg))       conn.send(std_bytes_len)     status = conn.recv(1024)     if status.decode('utf-8') == 'ok':           conn.send(std_msg)     else:         pass client端的例項如下: import socket   client = socket.socket() client.connect(('127.0.0.1',8001))   while 1:     cmd = input('請輸入指令:')       client.send(cmd.encode('utf-8'))       server_res_len = client.recv(1024).decode('utf-8')       print('來自服務端的訊息長度',server_res_len)       client.send(b'ok')       server_cmd_result = client.recv(int(server_res_len))       print(server_cmd_result.decode('gbk'))` 2.方案二: 通過struck模組將需要傳送的內容長度進行打包,打包成一個4位元組長度的資料傳送到對端,對端只要取出前4個位元組,然後對前4個位元組的資料進行捷豹,拿到傳送的長度,然後通過這個長度來繼續接受我們要傳送的內容. struck模組的使用:struck模組中最重的兩個函式就是pack()打包,unpack()解包 pack(): 我們這裡只介紹'i'這個int型別,上面的途中列舉了處理可以打包的所有的資料型別,並且struck處理pack和unpack兩個方法之外還有好多別的方法和用法. import struct num = 100 # num太大的話會報錯, # struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是範圍 # 打包,將int型別的資料打包成4個長度的bytes型別的資料 byt = struct.pack('i',num) print(byt) # 解包,將bytes型別的資料,轉換為對應的那個int型別的資料 # 注:unpack返回的是truple int_num = struct.unpack('i',byt)[0] print(int_num) 解決黏包現象的第二種方案: 服務端: import socket import subprocess import struct   server = socket.socket() ip_port = ('127.0.0.1',8001)   server.bind(ip_port) server.listen()   conn,addr = server.accept()   while 1:     from_client_cmd = conn.recv(1024)     print(from_client_cmd.decode('utf-8'))     #接收到客戶端傳送過來的系統指令,我服務端通過subprocess模組到服務端自己的系統裡面執行這條指令     sub_obj = subprocess.Popen(         from_client_cmd.decode('utf-8'),         shell=True,         stdout=subprocess.PIPE, #正確結果的存放位置         stderr=subprocess.PIPE  #錯誤結果的存放位置     )     # 從管道里面拿出結果,通過subprocess.Popen的例項化物件.stdout.read()方法來獲取管道中的結果     std_msg = sub_obj.stdout.read()     #為了解決黏包現象,我們統計了一下訊息的長度,先將訊息的長度傳送給客戶端,客戶端通過這個長度來接收後面我們要傳送的真實的資料     std_msg_len = len(std_msg)     print('指令的執行結果長度>>>>>>',len(std_msg))       msg_lenint_struct =struct.pack('i',std_msg_len)       conn.send(msg_lenint_struct+std_msg) 客戶端: import socket import struct client = socket.socket() client.connect(('127.0.0.1',8001))   while 1:     cmd = input('請輸入指令:')     #傳送指令     client.send(cmd.encode('utf-8'))     #接收資料長度,首先接收4個位元組長度的資料,因為這個4個位元組是長度     server_res_len=client.recv(4)     msg_len= struct.unpack('i',server_res_len)[0]       print('來自服務端的訊息長度',msg_len)     #通過解包出來的長度,來接收後面的真實資料     server_cmd_result =client.recv(msg_len)       print(server_cmd_result.decode('gbk')) 檢視自己的快取的大小 # print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF)) # print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)) 3. udp是面向包的,所以udp是不存在黏包的。 在udp程式碼中,我們在server端接受返回訊息的時候,我們設定的recvfrom(1024),那麼當我們輸入的執行指令為'dir'的時候,若dir在當前資料夾下輸出的內容大於1024,然後就報錯了, 解釋原因:因為udp是面向報文的,每個訊息是一個包,接收端設定接受大小的時候,必須要比你發的這個 包要大,不然一次接受不了就會報錯,而tcp是不會報錯的,這也是為什麼udp會丟包的原因  列印進度條(簡易) import time   for i in range(20):       print('\r' + i*'*',end='')     time.sleep(0.2)