1、项目需求:
要实现一个像ssh远程连接工具一样,在终端输入命令,返回对应的结果。
比如window的dos命令:
dir :查看目录下的文件
ipconfig : 查看网卡信息
tasklist : 查看进程列表
linux的命令:
ls : 查看目录下的文件
ifconfig : 查看网卡信息
ps -aux : 查看进程列表
2、项目分析:
这就是一个典型的c/s模式,在客户端发送一个命令,服务端接收到命令后,执行命令,并获取到执行的结果,再发送给客户端。
那么如何执行命令呢,python中提供了os模块的system可以执行系统命令
import os
res = os.system('dir')
print(res)
运行结果:
os.system()获取的结果只是打印出来了,通过变量去获取打印出来是0,通过这样无法获取。
还有一个比os.system()模块更好的subprocess模块,他更安全,还带有管道。
3、代码实现
服务端代码,server.py
#--coding:utf-8--import socket
import subprocess
'''
socket.AF_INET:表示是基于网络的套接字家族
socket.SOCK_STREAM:表示流式模块,基于tcp协议
'''
#创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('0.0.0.0',8000))
#监听
server.listen()print('staring....')
while True: #连接循环conn , addr = server.accept()print(addr)while True: #通信循环try:#1、接收命名cmd = conn.recv(1024) #1、单位:bytes 2、最大接收1024个bytesif not cmd:break #适用于linux操作系统,如果客户端断开了连接#2、执行命令obj = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)stdout = obj.stdout.read()stderr = obj.stderr.read()#3、把命令结果返回给客户端conn.send(stdout+stderr)except ConnectionResetError: #适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误breakconn.close()
server.close()
客户端代码,client.py
import socketclient = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#发起连接,服务端的ip和端口
client.connect(('127.0.0.1',8000))while True:cmd = input(">>: ").strip() #去掉空格if not cmd:continue #如果发的是空就进入下一次循环client.send(cmd.encode("gbk")) #因为是在windows系统下,所以用gbkdata = client.recv(1024)print(data.decode("gbk"))client.close()
运行结果:
可以看到客户端第一次输入ipconfig命令后返回了结果,但是在第二次输入dir 命令后,首先接收的是第一次没有接收完的网卡信息,这是因为客户端接收的数据存放在管道中,一次最大只能接收1024个字节,超出的部分依然保留在管道里面,下次再接收数据的时候,会先把积压的数据给取出来,再取后面的。这种现象就是粘包现象。还有如果发送端多次发送数据量小且间隔时间短也会出现粘包问题。
- TCP (transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一- -成对的socket,因此,发送端为了将多个发往接收端的包,更有
效的发到对方,使用了优化方法(Nagle算法) ,将多次间隔较小且数据量小的数据,合并成一 个大的
数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
2. UDP (user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会
使用块的合并优化算法,,由于UDP支持的是- -对多的模式,所以接收端的skbuff(套接字缓冲区)采用
了链式结构来记录每一个到达的UDP包, 在每个UDP包中就有了消息头(消息来源地址,端口等信
息),这样,对于接收端来说,就容易进行区分处理了。即面向消息的通信是有消息保护边界的。
3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,
udp协议会帮你封装.上消息头,实验略
如何解决粘包问题
目前的问题就是客户端只接收了一次数据,数据量大的时候没有接收完,如果我们知道这个数据的大小就可以通过循环来把所有的数据都读取出来了。
那么服务端在发送命令结果前要先把结果大小先传给客户端,可以在数据包的前面加一个数据报的头,这个头是一个固定大小的字节。这个头可以用一个字典来处理。
服务端改善的代码如下,文件名server.py
#--coding:utf-8--import socket
import subprocess
import struct
import json
import threading#创建套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('0.0.0.0',8000))
#监听
server.listen()print('staring....')
while True: #连接循环conn , addr = server.accept()print(addr)while True: #通信循环try:#1、接收命名cmd = conn.recv(2048) #1、单位:bytes 2、最大接收1024个bytesif not cmd:break #适用于linux操作系统,如果客户端断开了连接#2、执行命令obj = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)stdout = obj.stdout.read()stderr = obj.stderr.read()#3、把命令结果返回给客户端#第一步制作固定长度的包头header_dic = {'filename':'a.txt','md5':'********','total_size': len(stdout)+len(stderr)}#将字典转化成字符串header_json = json.dumps(header_dic)#在将字符串转换为bytesheader_bytes = header_json.encode("gbk")#第二步,先发送包头的长度conn.send(struct.pack('i',len(header_bytes)))#第三步: 发送报头conn.send(header_bytes)#第四步:再发送真实的数据conn.send(stdout)conn.send(stderr)except ConnectionResetError: #适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误breakconn.close()
server.close()
完善后的客户端代码,文件名client.py
import socket
import struct
import jsonclient = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#发起连接,服务端的ip和端口
client.connect(('127.0.0.1',8000))while True:cmd = input(">>: ").strip() #去掉空格if not cmd:continue #如果发的是空就进入下一次循环#1、发送命令client.send(cmd.encode("gbk"))#2、拿命令的结果并打印#第一步:接收报头的长度obj = client.recv(4)header_size = struct.unpack('i',obj)[0]#第二步:再收报头header_bytes = client.recv(header_size)#第三步:从包头中解析出真实数据的描述信息header_json = header_bytes.decode("gbk")header_dic = json.loads(header_json)#一次不能把内容接收完,那就循环接收,只要知道这个内容的总共大小total_size = header_dic['total_size']#接收真实的数据recv_size = 0 #接收的数据大小recv_data = b'' #接收的数据while recv_size < total_size:data = client.recv(1024)recv_data = recv_data + datarecv_size = recv_size + len(data)print(recv_data.decode("gbk"))client.close()
运行结果:
现在就可以一次显示完整了