套接字:
就是將傳輸層以下的協議封裝成子接口
對于應用程序來說只需調用套接字的接口,寫出的程序自然是遵循tcp或udp協議的
實現第一個功能個:
實現:通過客戶端向服務端發送命令,調取windows下面的cmd窗口,將服務端執行命令的結構,返回并顯示在
客戶端窗口上。
subprocess:
1.可以將執行結果返回
2.返回值是bytes類型
(基于這兩點,可以應用在server端,將服務端的返回直接以bytes的格式直接send給客戶端,
實現在客戶端的顯示)
問題1:粘包問題
粘包問題:實際是由TCP協議在傳輸數據時的2大特性來的
TCP協議又叫流式協議,在數據傳輸上,只要客戶端發送相應的數據請求,
服務端會將數據像流水一樣不斷的send給客戶端
基于這個特點,就會存在一個問題,當客戶端給服務端發送一條命令,服務端成功接收并將命令的
結果返回到客戶端的時候,由于客戶端recv()的數量限制,可以一次不能完全取出,
這個時候就會存在,下次輸入命令客戶端首先拿到的返回值就是上次殘留的沒有收完的數據
基于粘包問題的解決思路就是:
發數據之前先把報頭發給對方,讓對方先知道要收的報頭的長度,后面再傳數據文件
自定義報頭:
為甚么要自定義報頭:
因為struck path(‘i’,3443242)
1.’i‘:類型不同,后面數字的長度大小也不同,大小是有限的(當超出范圍時會報錯)
2.因為報頭里面含有的內容可能不僅僅只有total_siz還有filename、hash等等,知識單純的把total_size
當做報頭傳入不合理,所以我們要自定義報頭
Server端配置
from socket import *
importsocket,subprocess,struct,json
server=socket.socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)whileTrue:
conn,client=server.accept()print(client)whileTrue:try:
cmd= conn.recv(1024)if len(cmd) == 0: breakobj=subprocess.Popen(
cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out=obj.stdout.read()
err=obj.stderr.read()#制作報頭
header_dic={'filename':'a.txt','total_size':len(out)+len(err),'hash':'abc32i5o24'}#對包頭進行序列化
header_json=json.dumps(header_dic) #字符串格式
header_bytes=header_json.encode('utf-8')#1.先發型報頭的長度 len(header_bytes) struck為固定的4個字節
conn.send(struct.pack('i',len(header_bytes)))#2.發送報頭
conn.send(header_bytes)#3.發送真是數據
conn.send(out)
conn.send(err)exceptConnectionResetError:breakconn.close()
server.close()
Client端配置
from socket import *
importsocket,struct,json
client=socket.socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))whileTrue:
cmd=input('輸入你要操作的命令:')
client.send(cmd.encode('utf-8'))if len(cmd) == 0:continue
#1.先收報頭的四個字節,首先拿到報頭傳來的長度-》bytes
header=client.recv(4) #i類型足夠了 header為bytes類型
header_size=struct.unpack('i',header)[0] #拿到元祖形式,取第一個就是整個報頭的長度 print(header_size) ?#為報頭的長度值
#2.再收報頭(對應服務端的conn.send(header_bytes))
header_bytes=client.recv(header_size) #根據報頭的固定長度去收接收
#3.解析包頭(就是將header_bytes文件先解碼成json格式)
header_str=header_bytes.decode('utf-8')
header_dic=json.loads(header_str)print(header_dic)
total_size=header_dic['total_size']print(total_size)
recv_size=0 #定義一個初始的接收變量為0,只是個計數變量,為了統計與總的total_size的len大小
res=b''
while recv_size
recv_data=client.recv(1024) #每次傳過來的recv_data是bytes類型
res+=recv_data
recv_size+=len(recv_data) #循環增加每次接收值的長度#cmd=client.recv(1024)
print(res.decode('gbk'))
client.close()
粘包問題的最終解決方案,分析:
服務端:
目的為了自定義報頭(報頭中不僅包含長度,可能還有文件名等信息)
subprocess
...1.制作報頭(字典形式)
header_dic={'filename':'a.txt','total_size':len(out)+len(err),'hash':'abc32i5o24'}2.通過json將報頭序列化再encode為bytes類型
header_json=json.dumps(header_dic) #字符串類型
header_bytes=header_json.encode('utf-8') #bytes類型
3.發送報頭的長度,struck目的是固定封裝好的報頭為4個字節長度 =====對應客戶端剛開始 header=client.recv(4)
struck:1.將數字轉為bytes類型,保證發送過去的是bytes類型 2.固定4個字節
第一次發:conn.send(struck.pack('i',len(header_bytes)))
先傳給客戶端固定了收的時候報頭的長度,不至于報頭和其他內容粘在一起4.發送報頭 =======對應客戶端接收報頭 header_bytes=client.recv(header_size)
第二次發:conn.send(header_bytes)5.發送真實的數據
conn.send(out)
conn.send(err)
客戶端:(bytes--int)1.通過服務端返回的字節,拿到報頭的的長度
header=client.recv(4) #header是4個bytes字節
header_size=struck.unpack('i',header)[0] #字節頭-拿到int大小
2.再收報頭
header_bytes=client.recv(header_size) #bytes類型
3.解析報頭(1.先把內存中存放的bytes類型,用decode('utf-8')解碼為字符串)
header_json=header_bytes.decode('utf-8')
header_dic=json.loads(header_json) #json反序列化出字典格式 (對應server第2步)
print(header_dic)4.拿到字典中的報頭大小
total_size=header_dic['total_size']print(total_size)
struck功能輔助理解:所以客戶端剛開始接收4 大小是足夠把報頭接收完的。
#struck工鞥理解:importstruct#將整型轉成bytes
res=struct.pack('i',1232435436)print(res,len(res)) #res:為bytes類型,還是固定長度4(一般情況已經可以包含很多)
#將bytes轉成整型
aa=struct.unpack('i',res)print(aa,aa[0],type(aa)) #
print(len(res)) #4 一般情況多數bytes 4個長度足夠了
"""結果
b'\xecxuI' 4
(1232435436,) 1232435436
4"""