一、引言
! @9 W1 B7 b# a 本项目旨在让大家理解远控软件的原理,通过远控桌面可以实现远程控制我们的电脑,更好更方便的管理电脑。文末将给出初始版的完整代码,需要使用到的其他工具也会有所说明。最终实现的效果就是只要用户点击了客户端的程序运行,我们就可以在服务端对其进行控制。效果如下:左边是客服端程序运行了,然后我们就可以在左边的另一台电脑上打开服务端程序进行控制,可以看到左边的屏幕图像也已经显示在了右边的电脑上。完整代码见文末!$ |: Y& S/ E: l' |9 I; u1 `" e
二、远控流程
6 t8 I% h5 m& o' I7 F1.1 环境要求
6 q3 Q3 X" X! h: G6 Y _- ^本次环境使用的是python3.6.5+windows平台/ |( d) w" {8 r* v3 { m- l
主要用的库有:图像处理库opencv,包括用来目标检测和图像处理等操作。
7 l4 k6 i0 i dSocket用来远程传输数据达到远程控制的效果;
0 Q9 ]: N6 t3 j, A2 S/ T+ KThreading模块用来创建多线程管理;
) i; V# M! Y- ^2 q3 w* kNumpy模块用来辅助opencv对图像进行一些像素值操作;
, M3 R4 _2 \9 F8 Y( \PIL模块用来获取屏幕图像数据;
% J5 F3 R* A; j& [pynput.mouse用来控制鼠标点击事件。达到远程控制鼠标的作用。
# I( C# m, h1 [1.2 客户端讲解
$ N6 ?& [6 z+ S: n# }5 [% n客户端在这里指的是被控制的电脑,就是我们需要受到控制的电脑。
4 T n3 e6 c0 v5 R0 B (1)首先是导入相关模块:
9 O v& f( a) M, a
- #客户端代码
- import socket
- import threading
- import cv2
- import numpy as np
- from PIL import ImageGrab
- from pynput.mouse import Button,Controller
(2)接着创建一个鼠标控制器和用来接收服务端数据的函数。因为需要一直都接收数据,故需要嵌入循环。在这里客户端还需要接收数据的原因是,用来接收服务端传来的鼠标控制信息,要不然怎么实现鼠标控制桌面的效果呢。/ C5 X3 P; t1 Y1 U3 O
- #接受服务器返回的数据的函数
- m = Controller()
- def recvlink(client):
- while True:
- msg=client.recv(1024)
- msg=msg.decode('utf-8')
- print(msg)
- key = msg.split(",")
- xp = int(key[0])
- yp = int(key[1])
- m.position = ((xp,yp))
- m.click(Button.left,1)
(3)创建ipv4的socket对象,使用TCP协议(SOCK_STREAM)。然后设置服务端IP地址,以及端口。这里用来向服务端传输数据,即传输桌面图像数据。注释代码如下:. y$ V( N& J g
- #创建ipv4的socket对象,使用TCP协议(SOCK_STREAM)
- client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- #设置服务器ip地址,注意应该是服务器的公网ip
- host='服务器的公网ip'
- #设置要发送到的服务器端口,需要在云服务器管理界面打开对应端口的防火墙
- port=设置的端口
- #建立TCP协议连接,这时候服务器就会监听到到连接请求,并开始等待接受client发送的数据
- client.connect((host,port))
- #建立连接后,服务器端会返回连接成功消息
- start_msg=client.recv(1024)
- print(start_msg.decode('utf-8'))
- #开启一个线程用来接受服务器发来的消息
- t=threading.Thread(target=recvlink,args=(client,))
- t.start()
- p = ImageGrab.grab()#获得当前屏幕
- quality = 25 # 图像的质量
- encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
- while True:
- im = ImageGrab.grab()
- imm=cv2.cvtColor(np.array(im), cv2.COLOR_RGB2BGR)#转为opencv的BGR格式
- imm = cv2.resize(imm, (1535, 863))
- img_encode = cv2.imencode(".jpg", imm, encode_param)[1]
- data_encode = np.array(img_encode)
- str_encode = data_encode.tostring()
- #print(len(str_encode))
- #输入要发送的信息
- sendmsg="kehu"
- #向服务器发送消息
- client.send(str_encode)
- if sendmsg=='quit':
- break
- #结束时关闭客户端
- client.close()
1.3 服务端讲解
5 S& v$ c/ r& u 服务端指的是用来控制远程电脑的那一端,为了方便使用,我们直接在服务器上使用即可。2 c% C8 X" n3 n# a0 E8 ?
(1)导入使用到的模块:
, S/ X8 v" D) d# L! |
8 ]4 T% |) A; [: R, f9 h; ~- #服务器端
- import socket
- import threading
- import numpy as np
- import cv2
- import os
(2)创建鼠标点击事件函数,用来获取鼠标点击的位置坐标:) A% n; `5 f- T
- print("等待连接---")
- def mouse_click(event, x, y, flags, para):
- if event == cv2.EVENT_LBUTTONDOWN: # 左边鼠标点击
- f=open("1.txt","w")
- f.write(str(x)+","+str(y))
- f.close()
(3)创建服务器端接收数据函数,用来实时接收传输过来的图像数据并显示:/ K8 r4 E% {# a0 Z
- def recv_msg(clientsocket):
- while True:
- # 接受客户端消息,设置一次最多接受10240字节的数据
- recv_msg = clientsocket.recv(102400)
- # 把接收到的东西解码
- msg = np.fromstring(recv_msg, np.uint8)
- img_decode = cv2.imdecode(msg, cv2.IMREAD_COLOR)
- try:
- s=img_decode.shape
- img_decode=img_decode
- temp=img_decode
- except:
- img_decode=temp
- pass
- cv2.imshow('SERVER', img_decode)
- cv2.setMouseCallback("SERVER", mouse_click)
- try:
- f=open("1.txt")
- txt=f.read()
- f.close()
- reply=txt
- print(reply)
- clientsocket.send(reply.encode('utf-8'))
- os.remove("1.txt")
- except:
- pass
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
(4)主函数,用来建立连接和数据接收等功能。
2 E2 F" K# I6 p0 I, J- M) l6 L$ R- def main():
- socket_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- host='服务器的本地ip'
- #设置被监听的端口号,小于1024的端口号不能使用
- port=设置的端口
- socket_server.bind((host,port))
- #设置最大监听数,也就是最多可以同时响应几个客户端请求,一般配合多线程使用
- socket_server.listen(5)
- #等待客户端连接,一旦有了连接就立刻向下执行,否则等待
- #accept()函数会返回一个元组,第一个元素是客户端socket对象,第二个元素是客户端地址(ip地址+端口号)
- clientsocket,addr=socket_server.accept()
- # 有了客户端连接后之后才能执行以下代码,我们先向客户端发送连接成功消息
- clientsocket.send('连接成功'.encode('utf-8'))
- # 和客户端一样开启一个线程接受客户端的信息
- t=threading.Thread(target=recv_msg,args=(clientsocket,))
- t.start()
三、远程控制GUI窗口8 z" w* E# Y; z/ \ p! |
3 N& w8 ], U1 p0 R 远控桌面GUI主要是为了美观而用,需要大家根据远程代码进行集合修改。当然单独使用上述代码已经可以实现功能了,只是不够美观。由于考虑到此处代码量较大,且不是重点,故粗略讲解。
2 t2 c0 \/ f \+ x2 g8 d (1)导入相关库:
5 A+ Y1 s( ], l- from PyQt5.QtWidgets import *
- from PyQt5.QtCore import *
- from PyQt5.QtGui import QPalette, QBrush, QPixmap
- import os
- import socket
- import threading
- import cv2
- import numpy as np
- from PIL import ImageGrab
- from pynput.mouse import Button,Controller
- import time
(2)建立鼠标控制函数和点击函数; }# `2 m6 R m8 w) l. B
- m = Controller()
- def mouse_click(event, x, y, flags, para):
- if event == cv2.EVENT_LBUTTONDOWN: # 左边鼠标点击
- print( x, y)
- m.position = (x, y)
- time.sleep(0.1)
- m.click(Button.left, 1)
(3)GUI界面初始化,由于我们需要把实时的视频显示在窗口上,故也需要使用到opencv。! {/ Q6 s9 E# a
- def __init__(self, parent=None):
- super(Ui_MainWindow, self).__init__(parent)
- # self.face_recong = face.Recognition()
- self.timer_camera = QtCore.QTimer()
- self.cap = cv2.VideoCapture()
- self.CAM_NUM = 0
- self.set_ui()
- self.slot_init()
- self.__flag_work = 0
- self.x = 0
- self.count = 0
(4)设置窗口大小和控件位置等信息。创建布局和设置名称
5 K1 E7 H5 R. g. a- def set_ui(self):
- self.__layout_main = QtWidgets.QHBoxLayout()
- self.__layout_fun_button = QtWidgets.QVBoxLayout()
- self.__layout_data_show = QtWidgets.QVBoxLayout()
- self.button_open_camera = QtWidgets.QPushButton(u'远程桌面')
- self.button_close = QtWidgets.QPushButton(u'退出')
- # Button 的颜色修改
- button_color = [self.button_open_camera, self.button_close]
- for i in range(2):
- button_color[i].setStyleSheet("QPushButton{color:black}"
- "QPushButton:hover{color:red}"
- "QPushButton{background-color:rgb(78,255,255)}"
- "QPushButton{border:2px}"
- "QPushButton{border-radius:10px}"
- "QPushButton{padding:2px 4px}")
- self.button_open_camera.setMinimumHeight(50)
- self.button_close.setMinimumHeight(50)
- # move()方法移动窗口在屏幕上的位置到x = 300,y = 300坐标。
- self.move(500, 500)
- # 信息显示
- self.label_show_camera = QtWidgets.QLabel()
- self.label_move = QtWidgets.QLabel()
- self.label_move.setFixedSize(100, 100)
- self.label_show_camera.setFixedSize(1530,863)
- self.label_show_camera.setAutoFillBackground(False)
- self.__layout_fun_button.addWidget(self.button_open_camera)
- self.__layout_fun_button.addWidget(self.button_close)
- self.__layout_fun_button.addWidget(self.label_move)
- self.__layout_main.addLayout(self.__layout_fun_button)
- self.__layout_main.addWidget(self.label_show_camera)
- self.setLayout(self.__layout_main)
- self.label_move.raise_()
- self.setWindowTitle(u'远控桌面GUI')
- '''
- # 设置背景图片
- palette1 = QPalette()
- palette1.setBrush(self.backgroundRole(), QBrush(QPixmap('background.jpg')))
- self.setPalette(palette1)
- '''
(5)获取鼠标点击时的坐标:, {3 }2 i: R7 I3 H! X% _ ?
- def mousePressEvent(self,event):
- if event.buttons() & QtCore.Qt.LeftButton:
- x = event.x()-120
- y = event.y()-10
- text = "x: {0},y: {1}".format(x,y)
- if x>=0 and y>=0:
- m.position = (x, y)
- time.sleep(0.1)
- m.click(Button.left, 1)
- print(text)
(6)按钮绑定所设置的函数:
; ?" Z# Y8 v6 _/ g% s% @- def slot_init(self):
- self.button_open_camera.clicked.connect(self.button_open_camera_click)
- self.timer_camera.timeout.connect(self.show_camera)
- self.button_close.clicked.connect(self.close)
(7)显示桌面功能函数,并设置点击时修改名称,可以随时关闭桌面" ~$ o: b; C' i
- def button_open_camera_click(self):
- if self.timer_camera.isActive() == False:
- self.timer_camera.start(30)
- self.button_open_camera.setText(u'关闭')
- else:
- self.timer_camera.stop()
- self.cap.release()
- self.label_show_camera.clear()
- self.button_open_camera.setText(u'远程桌面')
(8)显示桌面函数和退出程序函数5 p) A8 Y4 w# f2 N; x
- def show_camera(self):
- im = ImageGrab.grab()
- imm = cv2.cvtColor(np.array(im), cv2.COLOR_RGB2BGR) # 转为opencv的BGR格式
- #imm = cv2.resize(imm, (1535, 863))
- self.image = imm
- # face = self.face_detect.align(self.image)
- # if face:
- # pass
- show =cv2.resize(self.image, (1536,863))
- show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
- print(show.shape[1], show.shape[0])
- # show.shape[1] = 640, show.shape[0] = 480
- showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
- self.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage))
- #cv2.setMouseCallback(showImage, mouse_click)
- # self.x += 1
- # self.label_move.move(self.x,100)
- # if self.x ==320:
- # self.label_show_camera.raise_()
- def closeEvent(self, event):
- ok = QtWidgets.QPushButton()
- cacel = QtWidgets.QPushButton()
- msg = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning, u"关闭", u"是否关闭!")
- msg.addButton(ok, QtWidgets.QMessageBox.ActionRole)
- msg.addButton(cacel, QtWidgets.QMessageBox.RejectRole)
- ok.setText(u'确定')
- cacel.setText(u'取消')
- # msg.setDetailedText('sdfsdff')
- if msg.exec_() == QtWidgets.QMessageBox.RejectRole:
- event.ignore()
- else:
- # self.socket_client.send_command(self.socket_client.current_user_command)
- if self.cap.isOpened():
- self.cap.release()
- if self.timer_camera.isActive():
- self.timer_camera.stop()
- event.accept()
5 s; ]# A8 ~0 A& E/ M
2 w3 f6 C) j: j. T& F |