socket tcp 粘包解決
何為粘包:
先看代碼
session=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
在定義socket對象的時候 有兩個參數 一個是 socket地址家族,另一個是處理類型socket.SOCK_STREAM,註意是 ‘stream’:流
那既然是流處理類型,理解上就是 水流式 處理數據。 這個時候數據是沒有邊界(也就是沒有從頭開始,到哪裏)的概念就像下圖
現在執行命令很正常:
執行了一個cat /etc/passwd ,
也能顯示,但是後面發生了什麽鬼
在主機上執行命令: ,可以看到 ,遠程執行cat 的時候只是 拿到了rtkit ,而rtkit後面的 數據沒有cat 到,這就是粘包
服務端在接收到 cat /etc/passwd 的時候,然後將字節轉換成命令, 並讀取結果 將結果send回客戶端 ,而客戶端這時也是recv 1024 ,所以如果數據過多,客戶端這裏從自己的bruff cache 中讀取到的1-1024 字節不夠收,就造成了數據不對應 粘包的現象。
解決方法: 服務器在recv 字節處理後的stdout.read() 和stderr.read() 的結果都要加一個報頭
報頭: 有固定的長度。
並且還有對數據信息的描述
需要一個新模塊 import struct ,
struct 使用: server 端
import struct
cmd=conn.recv(1024) res=subprocess.Popen(cmd.decode(‘utf-8‘),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) out_res=res.stdout.read() err_res=res.stderr.read() data_size=len(out_res)+len(err_res) #獲取執行結果的長度 #發送報頭 conn.send(struct.pack(‘i‘,data_size)) # struct.pack 的 i 是表示 4 個字節 4*8=32位bytes的存儲 ,這 i 裏面就已經包括了整個數據的長度,這樣客戶端在recv 結果的時候也知道要收多長的字節 #發送數據部分 conn.send(out_res) 再發送 stdout.read() conn.send(err_res) 再發送stderr.read()
完整代碼:
#!/usr/bin/env python import socket,subprocess,struct talk=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=(‘192.168.100.149‘,9000) talk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) talk.bind(ip_port) talk.listen(2) while True: conn,addr=talk.accept() print(‘=============host============‘,addr) while True: try: data=conn.recv(1024) if not data:break print(data) res=subprocess.Popen(data,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE) cmd_out=res.stdout.read() cmd_err=res.stderr.read() info_size=len(cmd_out)+len(cmd_err) conn.send(struct.pack(‘i‘,info_size)) conn.send(cmd_out) conn.send(cmd_err) except Exception: break conn.close() talk.close()
struct 使用:client 端
分析:server端已經做好了報頭,那 client 在接收的時候也應該先接收報頭長度,再接收報頭,,,,,,,,,,這樣就知道數據的長度,再recv 自己 的 buffer cachhe 數據。
import socket,struct talk=socket.socket(socket.AF_INET,socket.SOCK_STREAM) talk.connect((‘192.168.100.149‘,9000)) while True: msg=input(‘>>>>>‘).strip() if not msg:continue talk.send(bytes(msg,encoding=‘utf-8‘)) msg_head=talk.recv(4) head_unpack=struct.unpack(‘i‘,msg_head)[0] print(msg_head) recv_size=0 recv_data=b‘‘ while recv_size<head_unpack: data=talk.recv(1024) recv_size+=len(data) recv_data+=data print(recv_data.decode(‘utf-8‘)) socket.close()
執行效果:
現在已經解決粘包的問題,但是 報頭 不僅僅描述文件的長度(大小), 還應該包含一些其它的信息如 文件大小 文件名:
那現在就可以用到字典的格式存儲這些值了。
server 端代碼
#!/usr/bin/env python #!-*- coding:utf-8 -*- import socket,json,subprocess,struct 需要用到 json 序列化,因為字典在傳的時候只能是字節 形式 session=socket.socket(socket.AF_INET,socket.SOCK_STREAM) session.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 設置地址重用 ip_port=(‘192.168.100.149‘,9000) session.bind(ip_port) session.listen(4) while True: conn,addr=session.accept() #通訊無限循環 while True: try: client_cmd=conn.recv() res=subprocess.Popen(client_cmd.encode(‘utf-8‘),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) cmd_result=res.stdout.read() cmd_err=res.stderr.read() cmd_result_size=len(cmd_result)+len(cmd_err) #所有執行結果的 長度 head_dic={‘data_size‘:cmd_result_size} #將所有執行結果的長度,放在 dict 裏面 head_json=json.dumps(head_dic) #將字典做成 json 序列化 head_bytes=head_json.encode(‘utf-8‘) #再轉成utf-8的字節形式 head_len=len(head_bytes) #再獲取 報頭 字典 的長度 conn.send(struct.pack(‘i‘,head_len)) #發送字典 報頭的長度 conn.send(head_bytes) # 再發送所有數據的長度 conn.send(cmd_result) # 發送執行結果 的數據 conn.send(cmd_err) #發送執行結果的數據 except Exception: #捕捉任何異常,終止本次 break conn.close() #所有程序走完才close 這一個客戶端 的連接 session.close() #關閉整個socket
client端代碼
#!/usr/bin/env python #!-*- coding:utf-8 -*- import socket,json,subprocess,struct session=socket.socket(socket.AF_INET,socket.SOCK_STREAM) conn_ip_port=(‘192.168.100.149‘,9000) session.connect(conn_ip_port) #對應服務端的session.accept() while True: cmd=input(‘>>>>: ‘).strip() if not cmd:continue session.send(bytes(cmd,encoding=‘utf-8‘)) #對應服務端conn.recv(1024) head_struct=session.recv(4) #對應服務端conn.send(struct.pack(‘i‘,head_len)) print(‘四個字節報頭‘,head_struct) head_recv_data_len=struct.unpack(‘i‘,head_struct)[0] #對應服務端conn.send(struct.pack(‘i‘,head_len)) head_recv=session.recv(head_recv_data_len) #對應服務端 conn.send(head_bytes) head_json=head_recv.decode(‘utf-8‘) #對應服務端 head_bytes=head_json.encode(‘utf-8‘) head_dic=json.loads(head_json) #對應服務端 head_json=json.dumps(head_dic) print(head_dic) data_size=head_dic[‘data_size‘] recv_size=0 recv_data=b‘‘ while recv_size<data_size: data=session.recv(1024) #對應服務端 conn.send(head_bytes) conn.send(out_res)conn.send(err_res) recv_size+=len(data) recv_data+=data print(recv_data.decode(‘utf-8‘)) session.close()
FTP 上傳下載文件功能: …………………………………………………………………………..
socket tcp 粘包解決