立即學習:https://edu.csdn.net/course/play/24458/296243?utm_source=blogtoedu
粘包現象的解決:簡單版
?
1.思路:
????? 在服務器端計算出執行命令后結果的字節長度,然后再將字節數長度send即通知給客戶端,客戶端根據這個字節數的長度一次性即可將相應的命令執行結果給接收,進而解決了粘包問題。
?
2.知識點:
?
1)互聯網協議:報頭+數據
?
2)報頭是固定長度字節的,一般是4字節數,包含了一段數據的相關信息,如數據的字節總數以及相關描述等;
?
3)struct模塊,是python內置模塊,用于報頭的相關函數,如res = struct.pack('i',信息)是用于定制固定長度的函數,得到的是一個對象,而struct.unpack('i',res)則是解析報頭的函數,得到的是一個元組,第一個元素為字節數長度
?
3.關鍵代碼
'''
服務端
'''......#1接收客戶端發送過來的命令cmd = conn.recv(1024)#2處理命令,執行命令并且獲得命令得到的結果obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,#將正確運行命令得到的結果傳給管道stdout中stderr=subprocess.PIPE)#將沒有正確運行命令得到的返回信息存放在stderr管道中stdout = obj.stdout.read()stderr = obj.stderr.read()total_size = len(stderr + stdout)#1)定制固定長度的報頭,報頭包含命令執行結果的字節數長度header = struct.pack('i',total_size)#2)將報頭發送給客戶端conn.send(header)#3)將真實的命令執行結果信息發送給客戶端data = stdout + stderrconn.send(data)......'''
客戶端
'''.......#4、接收服務器返回來的數據recv()#1)先接收由服務器返回來的報頭,報頭是固定長度的,因此取前面4字節的數據即為報頭header = phone.recv(4)#返回的是一個對象#2)解析返回的報頭,獲得字節數總長信息obj_truple = struct.unpack('i',header)#返回的是一個元組total_size = obj_truple[0]#取元組第一個元素即為總字節數#3)接收真實的命令執行結果信息recv_size = 0data = b''while recv_size < total_size:recv_data = phone.recv(1024)#接收小于1024bytes的數據recv_size += len(recv_data)data += recv_dataprint('服務器返回來的數據:',data.decode('gbk')).......
結果:由結果可以得到,輸入相應的命令可以得到正確的命令執行結果
#第一個命令
請輸入:dir
服務器返回來的數據:? 驅動器 C 中的卷是 本地磁盤
?卷的序列號是 B476-3C7C
?C:\Users\jinlin\Desktop\python_further_study\socket編程\粘包現象解決(簡單版) 的目錄
2020/03/09? 14:46??? <DIR>????????? .
2020/03/09? 14:46??? <DIR>????????? ..
2020/03/09? 14:46???????????? 1,503 客戶端(粘包).py
2020/03/09? 14:45???????????? 1,434 服務器端(粘包).py
?????????????? 2 個文件????????? 2,937 字節
?????????????? 2 個目錄 122,025,189,376 可用字節
?
#第二個命令
請輸入:tasklist
服務器返回來的數據:
映像名稱?????????????????????? PID 會話名????????????? 會話#?????? 內存使用
========================= ======== ================ =========== ============
System Idle Process????????????? 0 Services?????????????????? 0????????? 4 K
System?????????????????????????? 4 Services?????????????????? 0??????? 568 K
smss.exe?????????????????????? 324 Services?????????????????? 0??????? 784 K
csrss.exe????????????????????? 524 Services?????????????????? 0????? 8,760 K
csrss.exe????????????????????? 620 Console??????????????????? 1???? 37,232 K
wininit.exe??????????????????? 628 Services?????????????????? 0????? 3,940 K
winlogon.exe?????????????????? 656 Console??????????????????? 1????? 6,564 K
..............
cmd.exe????????????????????? 11188 Console??????????????????? 1????? 2,304 K
tasklist.exe????????????????? 9056 Console??????????????????? 1????? 6,176 K
?
#第三個命令
請輸入:dir
服務器返回來的數據:? 驅動器 C 中的卷是 本地磁盤
?卷的序列號是 B476-3C7C
?C:\Users\jinlin\Desktop\python_further_study\socket編程\粘包現象解決(簡單版) 的目錄
2020/03/09? 14:46??? <DIR>????????? .
2020/03/09? 14:46??? <DIR>????????? ..
2020/03/09? 14:46???????????? 1,503 客戶端(粘包).py
2020/03/09? 14:45???????????? 1,434 服務器端(粘包).py
?????????????? 2 個文件????????? 2,937 字節
?????????????? 2 個目錄 122,024,615,936 可用字節
?
4.完整代碼
'''
服務端
'''
import socket
import subprocess
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',8080))
phone.listen(5)
while True:#接收客戶端發送過來連接服務器請求res = phone.accept()conn,client_addr = reswhile True:try:#1接收客戶端發送過來的命令cmd = conn.recv(1024)#2處理命令,執行命令并且獲得命令得到的結果obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,#將正確運行命令得到的結果傳給管道stdout中stderr=subprocess.PIPE)#將沒有正確運行命令得到的返回信息存放在stderr管道中stdout = obj.stdout.read()stderr = obj.stderr.read()total_size = len(stderr + stdout)#1)定制固定長度的報頭,報頭包含命令執行結果的字節數長度header = struct.pack('i',total_size)#2)將報頭發送給客戶端conn.send(header)#3)將真實的命令執行結果信息發送給客戶端data = stdout + stderrconn.send(data)except ConnectionResetError:breakconn.close()
phone.close()
phone.close()
'''
客戶端
'''
#導入模塊
import socket
import struct#1、設置phone套接字
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#2、連接服務器(打電話),本地地址:127.0.0.1
phone.connect(('127.0.0.1',8080))#3、向服務器發送請求send(),發送的數據不能直接發送字符串,因為要傳送到物理層底層,因此需要轉換成二進制的bytes類型進行發送,只需:發送的數據.encode('utf-8')即可
while True:cmd = input("請輸入:")#修復客戶端發送空字符串而服務器卡在接收信息處的bug,continue表示跳出本次循環,重新開始下一次的循環if not cmd:continuephone.send(cmd.encode('utf-8'))#4、接收服務器返回來的數據recv()#1)先接收由服務器返回來的報頭,報頭是固定長度的,因此取前面4字節的數據即為報頭header = phone.recv(4)#返回的是一個對象#2)解析返回的報頭,獲得字節數總長信息obj_truple = struct.unpack('i',header)#返回的是一個元組total_size = obj_truple[0]#取元組第一個元素即為總字節數#3)接收真實的命令執行結果信息recv_size = 0data = b''while recv_size < total_size:recv_data = phone.recv(1024)#接收小于1024bytes的數據recv_size += len(recv_data)data += recv_dataprint('服務器返回來的數據:',data.decode('gbk'))#5、關閉套接字phone
phone.close()
?
?