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

day028 黏包及黏包解決方案

一.關於cmd黑窗口裡面的快捷方法:

 輸入檔案的前幾個關鍵字,加tab就可以出現神奇的效果!

二.粘包現象.(1,緩衝區.2.關於subprocess模組)

關於緩衝區:

如何檢視輸入和輸出緩衝區 server.getsockopt():

 

關於subprocess.Popen():

在一些複雜場景中,我們需要將一個程序的執行輸出作為另一個程序的輸入。在另一些場景中,我們需要先進入到某個輸入環境,然後再執行一系列的指令等。這個時候我們就需要使用到suprocess的Popen()方法。該方法有以下引數:

args:shell命令,可以是字串,或者序列型別,如list,tuple。

bufsize:緩衝區大小,可不用關心

stdin,stdout,stderr:分別表示程式的標準輸入,標準輸出及標準錯誤

shell:與上面方法中用法相同

cwd:用於設定子程序的當前目錄

env:用於指定子程序的環境變數。如果env=None,則預設從父程序繼承環境變數

universal_newlines:不同系統的的換行符不同,當該引數設定為true時,則表示使用\n作為換行符

import subprocess
cmd = input('請輸入指令>>>')
res = subprocess.Popen(
    cmd,                     
#字串指令:'dir','ipconfig',等等 shell=True, #使用shell,就相當於使用cmd視窗 stderr=subprocess.PIPE, #標準錯誤輸出,凡是輸入錯誤指令,錯誤指令輸出的報錯資訊就會被它拿到 stdout=subprocess.PIPE, #標準輸出,正確指令的輸出結果被它拿到 ) print(res.stdout.read().decode('gbk')) print(res.stderr.read().decode('gbk'))

 stderr:標準錯誤

stdout:標準正確

關於粘包現象:

tcp粘包現象中的第一種:接收方沒有及時接收緩衝區的包,造成多個包接收(客戶端傳送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包)發生資料錯誤.
tcp粘包現象第二種:傳送資料時間間隔很短,資料也很小,會合到一起,產生粘包,發生資料錯誤.

 

 粘包的原因:主要還是因為接收方不知道訊息之間的界限,不知道一次性提取多少位元組的資料所造成的

 

接收端不知道傳送端將要傳送的位元組流的長度,所以解決粘包的方法就是圍繞,如何讓傳送端在傳送資料前,把自己將要傳送的位元組流總大小讓接收端知曉,
然後接收端發一個確認訊息給傳送端,然後傳送端再發送過來後面的真實內容,接收端再來一個死迴圈接收完所有資料。
解決方案(一):
服務端:
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) # std_bytes_len = bytes(str(len(std_msg)),encoding='utf-8') #首先將資料長度的資料型別轉換為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
客戶端:
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'))

 

 解決方案(二) struct模組 : (呼叫struct模組,少一步對方回覆確認的過程,打包內容的長度,傳送過去,對方也用struct解包長度,然後對方根據你傳送的長度,調整每次緩衝區擷取的長度,從而解決資料錯誤)

服務端:

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) #封裝的資料為4個位元組
    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) #由於統一用的是struct模組,所以知道對方發過來的第一組資料一定是4個位元組,所以填入4
    msg_len = struct.unpack('i',server_res_len)[0] #因為打包的是個元組,所以索引是0
    print('來自服務端的訊息長度',msg_len)
    #通過解包出來的長度,來接收後面的真實資料
    server_cmd_result = client.recv(msg_len)
    print(server_cmd_result.decode('gbk'))

 

關於struck的介紹:(作用:該模組可以把(-2147483648 <= number <= 2147483647)之間的數字轉換成長度為4個位元組的資料,進行傳送.

目的就是,解決對方不知道自己傳送的資料大小,而導致一次獲取的過大的粘包現象,或錯誤現象.)

就是定義了一種結構,裡面包含不同型別的資料(int,char,bool等等),方便對某一結構物件進行處理。
而在網路通訊當中,大多傳遞的資料是以二進位制流(binary data)存在的。當傳遞字串時,不必擔心太多的問題,而當傳遞諸如int、char之類的基本資料的時候,
就需要有一種機制將某些特定的結構體型別打包成二進位制流的字串然後再網路傳輸,而接收端也應該可以通過某種機制進行解包還原出原始的結構體資料。
通過struck模組將需要傳送的內容的長度進行打包,打包成一個4位元組長度的資料傳送到對端,對端只要取出前4個位元組,
然後對這四個位元組的資料進行解包,拿到你要傳送的內容的長度,然後通過這個長度來繼續接收我們實際要傳送的內容

 

pack():#我在這裡只介紹一下'i'這個int型別,上面的圖中列舉除了可以打包的所有的資料型別,並且struck除了pack和uppack兩個方法之外還有好多別的方法和用法.

import struct
a=12
# 將a變為二進位制
bytes=struct.pack('i',a) 
-------------------------------------------------------------------------------
struct.pack('i',1111111111111) 如果int型別資料太大會報錯struck.error
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是範圍
# 注意,unpack返回的是tuple !!
a = struct.unpack('i',bytes)[0] #將bytes型別的資料解包後,得到一個元組,加個索引0,拿到int型別資料