模拟ssh
需要返回ssh
命令执行的结果,我们用【客户端连接.recv
(字节长度)】来接收服务器传回的命令执行结果,但是这个字节长度我们无从得知,定义的如果太小则结果接收不完全,定义的太大,大的上限无法衡量不说还会浪费资源,这时候我们是否会考虑先把执行结果的长度传回给客户端,再把执行结果传回,这样就能很好的解决这个问题,但是我们要怎么操作呢?
尝试一:服务器发送两次,客户端接收两次
1 2 3 4 5 6
| conn.send(b'执行结果长度') conn.send(b'执行结果')
conn.recv(接收长度) conn.recv(接收长度)
|
从客户端的代码已经可以看出问题了,又引出接收的长度要定义多少了,不光有这个问题执行后我们可以发现,服务端紧挨的两次send发送,将发送结果合成了一个大的发送包,两个发送数据合在一起了,即:产生了黏包。
这时候我们来看下面向流的通信特点
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle
算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
对于空消息:tcp
是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp
是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp
协议会帮你封装上消息头发送过去。
可靠黏包的tcp
协议:tcp
的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack
时才会清除缓冲区内容。数据是可靠的,但是会粘包。
尝试二:通过延迟发送
我们可以引入time包,利用sleep延迟两次发送间隔达到两次发送的效果,这种方法时可行的,但是一定程度上降低了程序的运行效率,比较低效
1 2 3 4 5 6 7
| conn.send(b'执行结果长度') time.sleep(1) conn.send(b'执行结果')
conn.recv(接收长度) conn.recv(接收长度)
|
方法三:使用struct
struct
模块中最主要的三个函数式pack()
、unpack()
pack(fmt, v1)
—— 根据所给的fmt描述的格式将值v1转换为一个字符串。
unpack(fmt, bytes)
—— 根据所给的fmt描述的格式将bytes反向解析出来,返回一个元组。
这里我们主要用到以上两个方法,这两个方法的格式化的模式很多种,我们这里只用一个“i”就够了,这个‘i’模式可以讲一个整数转换为一个长度为4的字节;反解即将一个4位字节转换为一个整数。着这样即使发生了黏包,我们依然可以提取前四位解码,来获得接收的长度,也就达到了我们的目的
1 2 3 4 5 6 7
| data_lens = struct.pack('i', 执行结果长度) conn.send(data_lens) conn.send(b'执行结果')
data_lens = struct.unpack('i',conn.recv(4)) conn.recv(data_lens)
|
最后放一个模拟ssh的实例代码给大家,其中用到了struct
防止黏包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| import socket import subprocess import struct server = socket.socket() ip_port = ('10.10.10.103', 9999) server.bind(ip_port)
server.listen(3) print('服务器开始运行……')
while 1: conn, addr = server.accept() print(addr, '已成功连接----------------------') while 1: try: cmd = conn.recv(1024).decode('utf8') res = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) err = res.stderr.read() out = res.stdout.read() out_lens = len(out) err_lens = len(err) if err: struct_data = struct.pack('i', err_lens) print('服务器错误长度:', err_lens) conn.send(struct_data) conn.send(err) else: struct_data = struct.pack('i', out_lens) print('服务器数据长度:', out_lens) conn.send(struct_data) conn.send(out) except Exception as e: break print(addr, '已退出连接----------------------') conn.close()
server.close()
import socket import struct
ip_port = ('10.10.10.103', 9999) client = socket.socket()
client.connect(ip_port)
while 1: shell_str = input('请输入您要执行的命令行:') if shell_str == 'exit': break client.send(shell_str.encode('utf8')) data_lens = struct.unpack('i', client.recv(4))[0] recive_data = b'' total_data = 0 while total_data < data_lens: item_data = client.recv(1024) recive_data +=item_data total_data += len(item_data)
print('客户端里接收你的长度为:', data_lens, total_data) print(recive_data.decode('utf8')) client.close()
|