该篇用socket来模拟终端命令的实现。通过客户端来发送指令,服务器接收到指令后,执行指令,将执行后的结果发送给客户端,客户端接收到指令之后,打印在屏幕上。
1、客户端
- 生成socket连接对象
- 建立连接
- 输入要发送的"指令"信息
- 判断发送内容是否为空。是,重新输入;否,发送信息到服务器
- 接收服务器发回来的信息,打印
由于客户端一次性接收信息的大小有限,所以如果发回来的信息过大,客户端只能够接收一部分的信息,剩下的信息会暂存在服务器的缓存区里,得等到下一次交互的时候才会将剩余的部分发过来,这就导致了下一次交互的指令收到的却是上一次指令的结果。
所以按照上面的流程难以实现,所以我们现在修改下上面的流程:
- 生成socket连接对象
- 建立连接
- 输入要发送的"指令"
- 判断输入内容是否为空。是,重新输入;否,发送信息到服务器
- 接收服务器发过来的信息,该信息的内容是服务器将要发送的返回结果的大小
- 设置循环直到接收的数据大小>=服务器告诉我们的大小,结束接收
- 打印
下面是客户端具体的实现代码:
# Author: Mr.Xue
# 2019.10.29
# socket_ssh_client.py
import socket
client = socket.socket() # 声明socket类型,同时生成socket连接对象
client.connect(('localhost', 6961)) # 开始连接,ip地址为本地,端口号为6961
while True:
msg = input(">>").strip() # 输入要发送的信息
#print(len(msg))
if len(msg) == 0: continue # 判断输入的信息是否为空,如果空,重新输入
client.send(msg.encode("utf-8")) # 发送信息到服务器
cmd_res_size = client.recv(1024) # 接收服务器将发送过来的文件的大小
print("命令结果的大小:",cmd_res_size.decode())
client.send("准备好接收了".encode("utf-8"))
receive_size = 0 # 记录接收数据的大小
receive_data = b'' # 记录接收数据的变量
while receive_size < int(cmd_res_size.decode()): #判断接收到的大小和服务器计算出来的大小进行比较
data = client.recv(1024) # 接收服务器发过来的信息
receive_size += len(data)
receive_data += data
else:
print("cmd_res receive done...", receive_size)
print(receive_data.decode()) # 打印服务器发来的信息
client.close() # 最后,关闭客户端
2、服务器
服务器的实现就比较简单了,唯一的难点就是怎么执行客户端发来的"指令"信息生成结果,实现的流程应如下所示:
- 生成socket连接对象
- 绑定要监听端口
- 监听
- 等待客户端的连接
- 接收客户端信息
- 处理接收到的信息
- 将处理后的结果发回给客户端
服务器具体实现代码如下:
# Author: Mr.Xue
# 2019.10.29
#socket_ssh_server.py
import socket, os
server = socket.socket() # 声明socket类型,同时生成socket连接对象
server.bind(('localhost', 6961)) # 绑定要监听端口
server.listen(3) # 监听
print("我要等电话了")
while True:
print("wait1...")
conn, addr = server.accept() # 等待电话打进来
# conn就是客户端连过来而在服务器端为其生成的一个连接实例
print(conn, addr)
print("电话来了")
while True:
print("wait2...")
data = conn.recv(1024) # 等待数据过来并接收
if not data: # 如果没有数据,说明有一个客户端离线
print("lost a link...")
break
print("执行指令", data)
cmd_res = os.popen(data.decode()).read() # 接收字符串,执行结果也是字符串
print("before send...", len(cmd_res))
if len(cmd_res) == 0:
cmd_res = "cmd_res has no output..."
conn.send(str(len(cmd_res.encode())).encode("utf-8"))
con = conn.recv(1024) # 防止粘包
print("zhun bei hao fa song")
conn.send(cmd_res.encode("utf-8"))
print("send done")
server.close() # 关闭服务器
服务器端的实现代码中用os模块的popen()函数执行指令;服务器端还预防了粘包的发生。
3、测试效果
先启动服务器
xue@xue-MacBookAir:~/python_learn$ python3 socket_ssh_server.py
我要等电话了
wait1...
服务器启动之后,启动客户端
client
xue@xue-MacBookAir:~/python_learn$ python3 socket_ssh_client.py
>>
server
xue@xue-MacBookAir:~/python_learn$ python3 socket_ssh_server.py
我要等电话了
wait1...
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6961), raddr=('127.0.0.1', 48694)> ('127.0.0.1', 48694)
电话来了
wait2...
之后客户端发送指令,服务器返回结果
client
>>ifconfig
8
命令结果的大小: 1001
cmd_res receive done... 1001
lo Link encap:本地环回
inet 地址:127.0.0.1 掩码:255.0.0.0
inet6 地址: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 跃点数:1
接收数据包:22767 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:22767 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1000
接收字节:3111413 (3.1 MB) 发送字节:3111413 (3.1 MB)
wlp3s0 Link encap:以太网 硬件地址 c8:69:cd:b6:29:1e
inet 地址:192.168.0.118 广播:192.168.0.255 掩码:255.255.255.0
inet6 地址: fe80::30d3:2cb7:d774:13bb/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1
接收数据包:1533623 错误:0 丢弃:0 过载:0 帧数:678870
发送数据包:1107983 错误:15981 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1000
接收字节:1597160255 (1.5 GB) 发送字节:246097859 (246.0 MB)
中断:18
>>
server
xue@xue-MacBookAir:~/python_learn$ python3 socket_ssh_server.py
我要等电话了
wait1...
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6961), raddr=('127.0.0.1', 48694)> ('127.0.0.1', 48694)
电话来了
wait2...
执行指令 b'ls'
before send... 1052
zhun bei hao fa song
send done
wait2...
执行指令 b'ifconfig'
before send... 767
zhun bei hao fa song
send done
wait2...
发送一条不存在的指令看看
client
>> nonono
6
命令结果的大小: 24
cmd_res receive done... 24
cmd_res has no output...
>>
server
xue@xue-MacBookAir:~/python_learn$ python3 socket_ssh_server.py
我要等电话了
wait1...
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6961), raddr=('127.0.0.1', 48694)> ('127.0.0.1', 48694)
电话来了
wait2...
执行指令 b'ls'
before send... 1052
zhun bei hao fa song
send done
wait2...
执行指令 b'ifconfig'
before send... 767
zhun bei hao fa song
send done
wait2...
执行指令 b'nonono'
/bin/sh: 1: nonono: not found
before send... 0
zhun bei hao fa song
send done
wait2...