一、subprocess.Popen()
subprocess模塊定義了一個類: Popen
類原型:
class subprocess.Popen( args,
bufsize = 0,
executable = None,
stdin = None,
stdout = None,
stderr = None,
preexec_fn = None,
close_fds = False,
shell = False,
cwd = None,
env = None,
universal_newlines = False,
startupinfo = None,
creationflags = 0)
我們只需要關注其中幾個參數:
args:
args參數。可以是一個字符串,可以是一個包含程序參數的列表。要執行的程序一般就是這個列表的第一項,或者是字符串本身。
shell=True:
在Linux下,當shell=True時,如果arg是個字符串,就使用shell來解釋執行這個字符串。如果args是個列表,則第一項被視為命令,其余的都視為是給shell本身的參數。也就是說,等效於:
subprocess.Popen(['/bin/sh', '-c', args[0], args[1], ...])
stdin stdout和stderr:
stdin stdout和stderr,分別表示子程序的標准輸入、標准輸出和標准錯誤。可選的值有PIPE或者一個有效的文件描述符(其實是個正整數)或者一個文件對象,還有None。如果是PIPE,則表示需要創建一個新的管道,如果是None,不會做任何重定向工作,子進程的文件描述符會繼承父進程的。另外,stderr的值還可以是STDOUT,表示子進程的標准錯誤也輸出到標准輸出。
二、粘包現象
所謂粘包問題主要還是因為接收方不知道消息之間的界限,還有系統緩存區的問題,時間差的原因,不知道一次性提取多少字節的數據所造成的。
須知:只有TCP有粘包現象,UDP永遠不會粘包
粘包不一定會發生,如果發生了:1.可能是在客戶端已經粘了;2.客戶端沒有粘,可能是在服務端粘了
緩沖區的作用:存儲少量數據
如果你的網絡出現短暫的異常或者波動,接收數據就會出現短暫的中斷,影響你的下載或者上傳的效率。但是,緩
沖區解決了上傳下載的傳輸效率的問題,帶來了黏包問題。
收發的本質:不一定是一收一發
三、為什么出現粘包?
1,接收方沒有及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據,產生粘包)recv會產生黏包(如果recv接受的數據量(1024)小於發送的數據量,第一次只能接收規定的數據量1024,第二次接收剩余的數據量)
2,發送端需要等緩沖區滿才發送出去,造成粘包(發送數據時間間隔很短,數據也很小,會合到一起,產生粘包)send 也可能發生粘包現象。(連續send少量的數據發到輸出緩沖區,由於緩沖區的機制,也可能在緩沖區中不斷積壓,多次寫入的數據被一次性發送到網絡)
出現粘包現象的代碼實例
server. py
import socket
import subprocess
# 建立
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定
phone.bind(('127.0.0.1', 8081))
# 監聽
phone.listen(5)
# 通信循環
while True:
# 接收客戶端連接請求
conn, client_addr = phone.accept()
while True:
# 接收客戶端數據/命令
cmd = conn.recv(1024)
if not cmd:
break
# 創建管道
obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 向客戶端發送數據
conn.send(stdout)
conn.send(stderr)
# 結束連接
conn.close()
# 關閉套接字
phone.close()
client. py
import socket
# 建立
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 連接
phone.connect(('127.0.0.1', 8081))
while True:
cmd = input('>>> ').strip()
if not cmd:
continue
if cmd == 'quit':
break
# 給服務端發送數據/命令
phone.send(cmd.encode('utf-8'))
# 接收服務端數據/命令
data = phone.recv(1024)
print(data.decode('utf-8'))
# 關閉套接字
phone.close()
粘包現象運行結果



可以觀察到執行兩次ls命令后,服務端返回的仍然是ifconfig命令的結果,最后一次ls命令的末尾才出現ls命令返回的部分結果
四、解決粘包問題的代碼實例
server. py
import socket
import subprocess
import json
import struct
# 建立
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定
phone.bind(('127.0.0.1', 8081))
# 監聽
phone.listen(5)
# 通信循環
while True:
# 接收客戶端連接請求
conn, client_addr = phone.accept()
while True:
# 接收客戶端數據/命令
cmd = conn.recv(1024)
if not cmd:
continue
# 創建數據流管道
obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 向客戶端發送數據
# 解決粘包問題
# 1.制作固定長度的報頭
header_dic = {
'filename': 'a.txt',
'total_size': len(stdout)+len(stderr)
}
# 序列化報頭
header_json = json.dumps(header_dic) # 序列化為byte字節流類型
header_bytes = header_json.encode('utf-8') # 編碼為utf-8(Mac系統)
# 2.先發送報頭的長度
# 2.1 將byte類型的長度打包成4位int
conn.send(struct.pack('i', len(header_bytes)))
# 2.2 再發報頭
conn.send(header_bytes)
# 2.3 再發真實數據
conn.send(stdout)
conn.send(stderr)
# 結束連接
conn.close()
# 關閉套接字
phone.close()
client. py
import socket
import struct
import json
# 建立
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 連接
phone.connect(('127.0.0.1', 8081))
while True:
cmd = input('>>> ').strip()
if not cmd:
continue
if cmd == 'quit':
break
# 給服務端發送命令
phone.send(cmd.encode('utf-8'))
# 接收服務端數據
# 1.先收報頭長度
obj = phone.recv(4)
header_size = struct.unpack('i', obj)[0]
# 2.收報頭
header_bytes = phone.recv(header_size)
# 3.從報頭中解析出數據的真實信息(報頭字典)
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['total_size']
# 4.接受真實數據
recv_size = 0
recv_data = b''
while recv_size < total_size:
res = phone.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode('utf-8'))
# 關閉套接字
phone.close()