ソケットネットワークプログラミング(9)の章VIII:sshのシミュレーションプログラムの最終バージョン(CS標準アーキテクチャテンプレート)

  • 私たちは、基本的な機能を実現するが、いくつか問題がある前に、
    • 報告書は、ファイルの記述も、追加の情報が含まれている必要があり、事前にデータの長さが含まれています
    • 任意のモードのための構造体モジュールパック、ヘッダの長さ制限がある(Lモードが唯一8バイト)

このバージョンは、非常に標準テンプレートソケットCSサービスは、真剣にする必要があります。

server.py

import socket
import subprocess
import struct  # 用来打包,解包固定长度的包
import json
import locale  # 取得服务器端的os默认decode编码,用于解码subprocess加密的数据

ssh_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建套接字对象,参数:网络通信,TCP协议
ssh_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 端口重复使用
ssh_server.bind(('127.0.0.1', 8080))  # 绑定服务端IP和端口
ssh_server.listen(3)  # 设置最大可挂起客户端连接数

# 取得服务器端的os默认decode编码
os_locale = locale.getdefaultlocale()
os_encode = os_locale[1]
print('OS默认文档编辑编码', os_encode)  # 打印服务端默认编码

while True:
    conn, addr = ssh_server.accept()  # 等待连接,连接成功时实例化conn对象用来接下来的通信
    while True:
        try:  # 异常处理:防止客户端强制断开连接,导致windows系统报错
            cmd = conn.recv(1024)  # 接收客户端数据(修改:这里最好不要直接decode,因为如果未空值的话会报错)
            if not cmd: break  # 异常处理:防止客户端强制断开连接,导致linux系统死循环
            print("excute cmd:", cmd)
            
            # 用subprocess模块来执行系统命令(使用这个模块时因为可以返回结果)
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,  # 修改:decode在这里做
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()  # 读取执行结果
            stderr = obj.stderr.read()  # 读取error信息

            # 打印结果 ※用os默认的编码解码
            # print(stdout.decode(os_encode))

            # 第一步:生成数据的报头信息数据(非固定长度)
            # 我们可以用python的数据类型来保存报头内容,推荐用字典,因为有映射关系
            header_dic = {
                'filename': 'a.txt',
                'md5': 'xxxx',
                'total_size': len(stdout) + len(stderr),
                'os_encode': os_encode
            }
            # 因为字典不能直接网络传送,所以我们需要想方法
            header_json = json.dumps(header_dic)  # 这里我们把字典dump成json形式的str类型
            header_bytes = header_json.encode('utf-8')  # encode成bytes类型,这样我们就有了可以发送的header了
            # 但是我们不能这么发送,因为会和数据粘包。我们只学过struct模块来发报头。
            print('报头长度', len(header_bytes))  # 因为没用struct,所以我们的报头并不是固定长度
            print('数据长度', header_dic['total_size'])

            # 这里我们可以用struct把报头的长度先发过去,这样接下来我们只要接收报头的数据就可以了,而且报头的数据一般不会很大
            # 第二部:发送报头长度
            conn.send(struct.pack('i', len(header_bytes)))  # 这种情况下'i'模式也一般够用,固定长度4bytes

            # 第三部:再发报头
            conn.send(header_bytes)

            # 第四部:最后发真实数据
            # 把命令结果返回给客户端
            # conn.send(stdout+stderr)  # 这里之前用+连接一起发送,但其实粘包也可以一起发送
            conn.send(stdout)  # 连续发送可以粘包
            conn.send(stderr)  # 与上一行粘包成一个数据包了
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

ssh_server.close()

client.py

import socket
import struct
import json

ssh_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建套接字对象,参数:网络通信,TCP协议

ssh_client.connect(('127.0.0.1', 8080))  # 连接服务器IP和端口

while True:
    # 发命令
    cmd = input(">>>>:").strip()
    if not cmd:continue  # 异常处理:防止输入为空导致服务器无响应而永久挂起
    ssh_client.send(cmd.encode('utf-8'))  # 发送信息

    # 第一步:先收报头长度
    obj = ssh_client.recv(4)  # 接收服务器发来的,由struct打包的4bytes包
    header_size = struct.unpack('i', obj)[0]  # 解包,获得报头长度

    # 第二部:收报头
    header_bytes = ssh_client.recv(header_size)  # 根据报头长度收报头
    header_json = header_bytes.decode('utf-8')  # 服务端用utf-8 encode的json,所以这里用utf-8 decode
    header_dic = json.loads(header_json)  # 反序列json,获得字典

    total_size = header_dic['total_size']
    server_os_encode = header_dic['os_encode']
    print(header_dic)  # 打印下报头的字典

    # 第三部:接受真实收据
    recv_size = 0
    recv_data = b''
    while recv_size < total_size:  # 循环直到收完指定长度的包
        res = ssh_client.recv(1024)
        recv_data += res
        recv_size += len(res)

    print(recv_data.decode(server_os_encode))  # 这里我们使用报头里的服务器encode来decode

ssh_client.close()

おすすめ

転載: www.cnblogs.com/py-xiaoqiang/p/11299003.html