1. 程式人生 > >socket tcp 粘包解決

socket tcp 粘包解決

connect line 應該 字節 unpack otto stdout except soc

何為粘包:

先看代碼

session=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

在定義socket對象的時候 有兩個參數 一個是 socket地址家族,另一個是處理類型socket.SOCK_STREAM,註意是 ‘stream’:流

那既然是流處理類型,理解上就是 水流式 處理數據。 這個時候數據是沒有邊界(也就是沒有從頭開始,到哪裏)的概念就像下圖

現在執行命令很正常:

技術分享

執行了一個cat /etc/passwd ,

技術分享

也能顯示,但是後面發生了什麽鬼

技術分享

在主機上執行命令: ,可以看到 ,遠程執行cat 的時候只是 拿到了rtkit ,而rtkit後面的 數據沒有cat 到,這就是粘包

,數據沒有 頭和尾,導致程序也不知道頭和尾在哪,直到recv 雖然取了1024數據,但是數據只取了一半,還有一半在緩存裏,沒有取出來,導致在執行下一次執行命令的時候又取了1024字節的數據但是數據仍然不止1024字節,又沒有取完,就導致了後面一直亂了。
技術分享

服務端在接收到 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 粘包解決