模擬ssh遠程socket編程粘包問題_服務端

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2018/6/2 18:29
# @Author  : chen
# @File    : 服務端.py
import json
import socket
import struct
import subprocess

"""
# 關於struct模塊
res = struct.pack('i', 1230)
print(res, type(res), len(res))
# 輸出結果:b'\xce\x04\x00\x00' <class 'bytes'> 4
# 而1230轉成16進制的結果爲4CE,所以struct接收的結果是反向的
obj = struct.unpack('i', res)
print(obj)
# (1230,)
# unpack後,結果就是正向的,所以不需要在意這些細節,只要關注傳輸的是4字節就可以了
"""
# 服務端需要兩個套接字,一個用來發送,另一個用來接收bind,recv
# 客戶端只有一個套接字,connect

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # setsockopt(level,optname,value)
# socket模塊下的socket類,socket.AF_INET是網絡模式
# socket.SOCK_STREAM是代表流模式,其實就是指的tcp
"""
# level定義了哪個選項將被使用。通常情況下是SOL_SOCKET,意思是正在使用的socket選項。它還可以通過設置一個特殊協議號碼來設置協議選項,
#     然而對於一個給定的操作系統,大多數協議選項都是明確的,所以爲了簡便,它們很少用於爲移動設備設計的應用程序。
# 這裏value設置爲1,表示將SO_REUSEADDR標記爲TRUE,操作系統會在服務器socket被關閉或服務器進程終止後馬上釋放該服務器的端口,
# 否則操作系統會保留幾分鐘該端口。
"""
phone.bind(('127.0.0.1', 9901))  # 0-65535; 0-1024給操作系統使用
phone.listen(5)

print('starting...')
while True:
    conn, client_addr = phone.accept()  # conn是接收的一個對象accept() -> (socket object, address info)
    # 可以將conn理解爲三次握手的導向指標(箭頭)
    print(client_addr)
    while True:
        try:
            # 1.收命令
            cmd = conn.recv(8096)  # 1、單位:bytes 2、8096代表最大接收8096個bytes
            if not cmd: break  # 適用於linux操作系統
            print('客戶端數據', cmd)

            # 2.執行命令,拿到結果
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            # 3.把命令的結果返回給客戶端

            # 第一步:製作固定長度的報頭
            header_dic = {
                'file_name': 'a.txt',
                'md5': 'xxxxxdd',
                'total_size': len(stdout) + len(stderr)
            }
            # 將字典序列化(字典轉成字符格式)
            header_json = json.dumps(header_dic)
            # 將字符格式編碼成二進制
            header_bytes = header_json.encode('utf-8')

            # 第二步:先發送報頭長度; 客戶端接收時一次只接收4字節,這樣就不會出現粘包現象了
            conn.send(struct.pack('i', len(header_bytes)))  # 'i' 表示int 整型數據

            # 第三步:再發送報頭
            conn.send(header_bytes)  # 發送給網卡,從應用程序內存拷貝到系統內存,當數據爲空時,操作系統就不會有任何操作

            # 第四步:發送真實數據
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:  # 適用於windows操作系統
            break

    conn.close()  # 一次會話結束

phone.close()  # 連接斷開

# 粘包的終極解決思路是,自己設定一個協議報頭,規定好長度(使用struct模塊),然後再在報頭中填入字符串長度問題
# 可以避免接收字節長度過長超過int或long型的最大值(比如傳輸超大文件這種),還能增加更多信息(比如校驗md5文件等)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章