day 28 黏包及黏包解決方案
阿新 • • 發佈:2019-01-14
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)