socket网络编程:最终版本的ssh模拟程序(标准cs构架模板)
Posted py-xiaoqiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket网络编程:最终版本的ssh模拟程序(标准cs构架模板)相关的知识,希望对你有一定的参考价值。
- 之前我们实现了基本功能,但是有一些问题
- 报头里只包含了数据长度,对文件的描述应该还包含其他信息
- struct模块pack的任何模式,报头长度是有极限的(l模式也就只有8bytes)
这个版本是一个很标准的套接字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()
以上是关于socket网络编程:最终版本的ssh模拟程序(标准cs构架模板)的主要内容,如果未能解决你的问题,请参考以下文章
Python--网络编程-----socket编程示例--模拟ssh远程执行命令
第六章 - 网络编程 - 1.简单的套接字通信/2.加上通信循环/3.bug修复/4.加上链接循环/5.模拟ssh远程执行命令