python 網絡編程(遠程執行命令與粘包)


遠程執行命令

先來學習一個新模塊 , 一會用到的..

新模塊: subprocess
執行系統命令
r = subprocess.Popen('ls',shell=True,stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)
subprocess.Popen(a,b,c,d)
a: 要執行的系統命令(str)
b: shell = True  表示確定我當前執行的命令為系統命令
c: 表示正確信息的輸出管道
d: 表示錯誤信息的輸出管道

下邊直接上代碼,一看就懂.  TCP的

import socket
import subprocess
sk = socket.socket()
sk.bind(('127.0.0.1',9090))
sk.listen()
conn,addr = sk.accept()
while 1:
    cmd = conn.recv(1024).decode('utf-8')
    res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
    std_out = res.stdout.read()# 讀取正確的返回信息
    std_err = res.stderr.read()# 讀取錯誤的返回信息

    if std_out:
        conn.send(std_out)
    else:
        conn.send(std_err)

conn.close()
sk.close()
遠程服務端
import socket
sk = socket.socket()
sk.connect_ex(('127.0.0.1',9090))
while 1:
    cmd = input('請輸入一個命令>>>')
    sk.send(cmd.encode('utf-8'))
    print(sk.recv(204800000).decode('gbk'))

sk.close()
遠程客戶端

就是引用了一個模塊的功能,其他的還是簡單的收發功能.

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

的結果的編碼是以當前所在的系統為准的,如果是windows,那么res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼

且只能從管道里讀一次結果

注意

 

 

 

 

 

 

--------------------粘包---------------------------

 

 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函數時,數據長度參數不受限制。
而實際上,所指定的這段數據並不一定會一次性發送出去,如果這段數據比較長,會被分段發送,如果比較短,可能會等待和下一次數據一起發送。

 

 

總結

黏包現象只發生在tcp協議中:

1.從表面上看,黏包問題主要是因為發送方和接收方的緩存機制、tcp協議面向流通信的特點。

2.實際上,主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的

解決方案------------

我們可以借助一個模塊,這個模塊可以把要發送的數據長度轉換成固定長度的字節。這樣客戶端每次接收消息之前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,那么最終接受的數據只要達到這個值就停止,就能剛好不多不少的接收完整的數據了。

 

 

文件上傳解決粘包 的代碼:  利用字典先傳過去文件大小即可....

 

import socket
import json
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',9090))
sk.listen()
conn,addr = sk.accept()
dic_size = conn.recv(4)# 先接受4字節長度的一個bytes, 代表字典的大小
dic_size = struct.unpack('i',dic_size)[0]#  將這個特殊的bytes轉變成原數字
dic_str = conn.recv(dic_size).decode('utf-8')# 根據字典大小去獲取字典,以免和底下獲取文件內容發生粘包
dic = json.loads(dic_str)# 反序列化   得到字典 opt  filename   filesize
if dic['opt'] == 'upload':
    '''接收文件'''
    filename = '1'+dic['filename']# 將文件名字修改,防止重名
    with open(filename,'wb') as f:
        while dic['filesize']:
            content = conn.recv(1024)
            f.write(content)
            dic['filesize'] -= len(content)
elif dic['opt'] == 'download':
    '''給客戶端傳輸文件'''
conn.close()
sk.close()
文件上傳 服務端

 

import socket
import os
import json

import struct

sk = socket.socket()
sk.connect(('127.0.0.1',9090))
l = ['upload','download']
for i,v in enumerate(l):
    print(i+1,v)
dic = {'opt':None,'filename':None,'filesize':None}
while 1:
    opt = input("請輸入功能選項>>>")# 客戶要執行的操作選項
    if opt == '1':
        '''upload'''
        file_dir = input('請輸入文件路徑>>>')# 'E:/sylar/python_workspace/day34/作業/時間同步機制_client.py'
        file_name = os.path.basename(file_dir)# 獲取文件名字
        file_size = os.path.getsize(file_dir)# 獲取文件大小
        dic['opt'] = l[int(opt)-1]
        dic['filename'] = file_name
        dic['filesize'] = file_size
        dic_str = json.dumps(dic)# 將字典序列化成一個字符串形式的字典
        dic_size = len(dic_str)# 獲取字典的大小
        ds = struct.pack('i',dic_size)# 把一個小於21.3E的一個數字轉變成一個4字節長度的bytes
        sk.send(ds + dic_str.encode('utf-8'))# 發送給服務器
        with open(file_dir,'rb') as f:
            while file_size:
                content = f.read(1024)# 文件內容
                sk.send(content)
                file_size -= len(content)
    elif opt == '2':
        '''download'''
        pass
    else:
        print('有誤')

sk.close()
文件上傳 客戶端

 

 

收發概括

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM