软件开发环境
python 3.7.3
pycharm Community 2020
PYQT5
教师端控制界面
- 下拉列表显示全部已经连接的客户端ip地址
- 对选中的设备进行查看设备信息,重启电脑,关闭电脑,锁屏等操作。
- 具备广播功能,可以自定义教师广播的文本内容
- 监控的所有学生端屏幕,可以点击查看大图
- 系统信息显示栏,可以显示教师端的IP地址和端口号;显示学生端设备信息,可以显示对学生端电脑作出的重启、关闭、锁屏等
学生端软件界面
学生端软件包括如下内容:
- 连接教师端,需要输入教师端的网址和端口号
- 操作面板提示做的各项操作内容和老师的广播内容
- 可以向教师端传输文件,传输文件大小不超过1Mb
- 举手签到操作,点击举手后教师端可以即时看到举手信息
软件技术规格:
1、软件包括学生端和教师端,采用局域网通信。
2、教师端能够连接至少2台学生端。
3、学生端能够向教师端发送文件。
4、学生端能够进行签到举手操作。
5、教师端能够向学生端设备广播信息,信息内容可自定义。
6、教师端可以对学生端屏幕进行查看。
7、教师端可以对学生端计算机进行重启操作。
8、教师端可以对学生端计算机进行关闭操作。
9、教师端可以对学生端计算机进行锁屏操作。
10、教师端能够接收学生端上传的文件。 - 状态栏自动接收
11、教师端能够查看学生端计算机的状态如CPU占用。 - 状态栏显示
12、教师端能偶查看学生端的签到举手操作。 -状态栏显示
需求详细分析:
控制学生端,发送信息,发送文件,发送监控视频,都要基于数据传输。因此首先选定教师端和学生端的通信协议为TCP/IP。基于此通信协议后,教师端需要接收:客户端的信息,举手信息,设备状态,文件;需要发送:自定义广播内容,重启、关机、锁屏等命令。相对于教师端的接收和操作,每个学生端就应具备相应的发送和接收功能。
教师端对学生端屏幕监控功能。学生端需要具备静默运行的屏幕截取功能,并发送到服务端。服务端能够接收到全部已经连接的学生端截屏,并显示在屏幕上。
软件架构
应用架构
根据以上的需求分析,软件的架构框图如下图所示。
软件在界面的支撑下,需要实现TCP服务端和监控端的功能。TCP服务器具备相应的接收和发送功能。教师端的监控功能持续接收学生端的屏幕截图。
在软件架构的指导下,我们将软件分为以上多条线实现,包括了Server开启,接收Client的信息,并能够发送信息;接收屏幕截图。所有逻辑运行的线路,用多线程方式运行,逻辑线程同界面线程分离,避免界面假死,提高界面的友好度。
数据传输的原理
TCP传输的原理可以参照网络相关资料。如下是TCP进行传输的三次握手过程说明。
TCP三次握手的过程如下:
客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
屏幕截图的原理
python进行屏幕截图,采用pyautogui对屏幕进行截取,并使用HTTPServer来发送屏幕截图。
pyautogui是对屏幕、鼠标、键盘进行控制的python库,调用screenshot()将返回Image对象,该screenshot()功能大约需要100毫秒。
如果你不需要截取整个屏幕,还有一个可选的region参数。你可以把截取区域的左上角XY坐标值和宽度、高度传入截取。其文档位于https://pyautogui.readthedocs.io/en/latest/。
http server就是web server,或者说网页服务器,网站服务器。常用的web server有iis,apache等。iis是internet information server的简称,是windows上主要的web服务器。
界面开发基础
PYQT5开发的方法。采用QtDesigner来简单设计一个界面,并保存为一个文件名为ui的文件。
如下提供了一个python调用ui文件并显示出界面的方法。
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("ui_source_server/server_panel.ui", self) # 加载面板文件,使用qt designer开发
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.setStyleSheet(qss_style)
window.show()
sys.exit(app.exec_())
如果希望对软件界面进行美化,那么就在main中加入qss文件。qss是类似于CSS的文件,下文将做出解释。
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
with open('style_source_server/app_style.qss', encoding='utf-8') as f:
qss_style = f.read()
window.setStyleSheet(qss_style)
window.show()
sys.exit(app.exec_())
实现路径
1、界面初步实现
2、屏幕录制
采用PyAutoGUI.screenshot()完成屏幕截图。
HTTPServer提供截图页面。
from http.server import BaseHTTPRequestHandler, HTTPServer
import PyAutoGUI
import socket
PORT = 8008
# 获取学生机局域网地址
IP = socket.gethostbyname(socket.gethostname())
#windows
class myHandler(BaseHTTPRequestHandler):
def do_GET(self):
img = PyAutoGUI.screenshot() #屏幕截图
if img:
self.send_response(200) #HTTP 状态码
self.send_header('Content-Type', 'image/png')
self.end_headers()
img.save(self.wfile, 'PNG') # 写入HTTP 响应流文件
def main():
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#判断当前端口是否已经打开
result = sock.connect_ex((IP, PORT))
portopen = result == 0
sock.close()
if not portopen:
#启动web服务器,用自定义的响应处理类
server = HTTPServer((IP, PORT), myHandler)
server.serve_forever() # 服务器持续监听
except:
pass
if __name__ == '__main__':
main()
如果单独运行屏幕截取的程序后,在浏览器中打开localhost:8008
,就能立刻看到截取的屏幕图像了。如果手动刷新页面,截取的屏幕图像也随之刷新。
- 连接上后端的数据
教师的服务端程序可以连接上学生端的屏幕图像,再设置轮询以后就可以显示截屏了。该种方法实现的屏幕监控具有弊端,在总结部分有解释。
eval('self.video_' + str(i + 1)).setPixmap(video)
在pyqt5中使用setPixmap将图片显示到label
中。
4.软件界面和逻辑的分离
python中如果采用PYQT5开发界面,强烈推荐使用多线程来开发界面和逻辑分离的部分。如下中self.check_thread
来开启多线程程序,程序的回调信号为pyqtSignal
。回调信号的参数类型可以是python自带的常规函数类型,如int,string,list等,如果是其他更复杂的类型,可以存放到list类型中进行传递。
多线程的函数返回值触发回调函数,如下self.server_callback
。回调函数的参数value
即回调信号的参数。
self.check_thread = Server([]) # 多线程去获取
self.check_thread.signal.connect(self.server_callback)
self.check_thread.start() # 启动线程
class Server(QThread):
signal = pyqtSignal(list) # 括号里填写信号传递的参数
def __init__(self, args_list):
super().__init__()
self.args_data = args_list
def __del__(self):
self.wait()
def run(self):
while True:
client_socket, client_address = socket_server.accept()
if client_address not in conn_list:
conn_list.append(client_address)
conn_dt[client_address] = client_socket
self.signal.emit([client_socket, client_address]) # 发射信号
def server_callback(self, value):
"""
Sever 回调函数功能
:param value:
:return:
"""
5.子窗口的实现
点击每个学生端的查看按钮,可以对学生端的屏幕进行详细查看。
父窗口连接子窗口。
self.child = Child(students[client_num - 1]) # 创建子窗口实例
self.child.exec()
传入一个参数,参数信息为当前查看的学生信息。
class Child(QDialog):
"""
查看客户端详情页。
"""
def __init__(self, label, parent=None):
super().__init__(parent)
self.student = label
self.initUI()
def initUI(self):
loadUi("ui_source_server/child_panel.ui", self)
self.setWindowTitle("多媒体管理软件 -樱桃智库") # 设置窗口标题
self.pushButton_quit.clicked.connect(self.quit) # 点击ok,隐士存在该方法
self.timer = QTimer(self) # 初始化一个定时器
if self.student:
self.timer.timeout.connect(self.get_client_screen) # 每次计时到时间时发出信号
self.timer.start(600) # 设置计时间隔并启动;单位毫秒
else:
pass
def get_client_screen(self):
self._thread = AutoPollScreen([self.student]) # 多线程去获取
self._thread.signal.connect(self.screen_callback)
self._thread.start() # 启动线程
def screen_callback(self, videos):
video = videos[0].scaled(1200, 800)
self.label.setPixmap(video)
def quit(self): # 点击ok是发送内置信号
self.timer.stop()
self.close()
开发完毕
其实可以更进一步地对界面的美化进行提高,但想想也没必要了!
异常捕获
1、屏幕截图慢。
教师控制端采用轮询的方式连接学生端的http服务器,会导致屏幕监控出现卡顿或者帧数较慢的情况出现。针对此问题的解决办法可以借鉴腾讯会议的屏幕分享功能。
2、学生端关闭程序,教师端会卡死。
这个问题主要是因为TCP的连接造成的,TCP连接成功后应该主动断开连接,否则会抛出异常。但是存在的难点是,如果学生端非正常退出,也没有办法将退出信号提前发送给教师端。
参考资料
项目地址
https://github.com/Jarrettluo/multimedia_management_software