模拟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文件等)

猜你喜欢

转载自blog.csdn.net/u013193903/article/details/80561376