項目旨在讓大家理解遠控軟件的原理,通過遠控桌面可以實現遠程控制我們的電腦,更好更方便的管理電腦。文末將給出初始版的完整代碼,需要使用到的其他工具也會有所說明。最終實現的效果就是只要用戶點擊了客戶端的程序運行,我們就可以在服務端對其進行控制。效果如下:左邊是客服端程序運行了,然后我們就可以在左邊的另一台電腦上打開服務端程序進行控制,可以看到左邊的屏幕圖像也已經顯示在了右邊的電腦上。完整代碼見文末!
01
遠控流程
1.1 環境要求
本次環境使用的是python3.6.5+windows平台
主要用的庫有:圖像處理庫opencv,包括用來目標檢測和圖像處理等操作。
Socket用來遠程傳輸數據達到遠程控制的效果;
Threading模塊用來創建多線程管理;
Numpy模塊用來輔助opencv對圖像進行一些像素值操作;
PIL模塊用來獲取屏幕圖像數據;
pynput.mouse用來控制鼠標點擊事件。達到遠程控制鼠標的作用。
1.2 客戶端講解
客戶端在這里指的是被控制的電腦,就是我們需要受到控制的電腦。
(1)首先是導入相關模塊:
-
1#客戶端代碼
-
2import socket
-
3import threading
-
4import cv2
-
5import numpy as np
-
6from PIL import ImageGrab
-
7from pynput.mouse import Button,Controller
(2)接着創建一個鼠標控制器和用來接收服務端數據的函數。因為需要一直都接收數據,故需要嵌入循環。在這里客戶端還需要接收數據的原因是,用來接收服務端傳來的鼠標控制信息,要不然怎么實現鼠標控制桌面的效果呢。
-
1#接受服務器返回的數據的函數
-
2m = Controller()
-
3def recvlink(client):
-
4 while True:
-
5 msg=client.recv(1024)
-
6 msg=msg.decode('utf-8')
-
7 print(msg)
-
8 key = msg.split(",")
-
9 xp = int(key[0])
-
10 yp = int(key[1])
-
11 m.position = ((xp,yp))
-
12 m.click(Button.left,1)
-
(3)創建ipv4的socket對象,使用TCP協議(SOCK_STREAM)。然后設置服務端IP地址,以及端口。這里用來向服務端傳輸數據,即傳輸桌面圖像數據。注釋代碼如下:
-
1#創建ipv4的socket對象,使用TCP協議(SOCK_STREAM)
-
2client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
-
3#設置服務器ip地址,注意應該是服務器的公網ip
-
4host='服務器的公網ip'
-
5#設置要發送到的服務器端口,需要在雲服務器管理界面打開對應端口的防火牆
-
6port=設置的端口
-
7#建立TCP協議連接,這時候服務器就會監聽到到連接請求,並開始等待接受client發送的數據
-
8client.connect((host,port))
-
9#建立連接后,服務器端會返回連接成功消息
-
10start_msg=client.recv(1024)
-
11print(start_msg.decode('utf-8'))
-
12#開啟一個線程用來接受服務器發來的消息
-
13t=threading.Thread(target=recvlink,args=(client,))
-
14t.start()
-
15p = ImageGrab.grab()#獲得當前屏幕
-
16quality = 25 # 圖像的質量
-
17encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
-
18while True:
-
19 im = ImageGrab.grab()
-
20 imm=cv2.cvtColor(np.array(im), cv2.COLOR_RGB2BGR)#轉為opencv的BGR格式
-
21 imm = cv2.resize(imm, (1535, 863))
-
22 img_encode = cv2.imencode(".jpg", imm, encode_param)[1]
-
23 data_encode = np.array(img_encode)
-
24 str_encode = data_encode.tostring()
-
25 #print(len(str_encode))
-
26 #輸入要發送的信息
-
27 sendmsg="kehu"
-
28 #向服務器發送消息
-
29 client.send(str_encode)
-
30 if sendmsg=='quit':
-
31 break
-
32#結束時關閉客戶端
-
33client.close()
-
1.3 服務端講解
服務端指的是用來控制遠程電腦的那一端,為了方便使用,我們直接在服務器上使用即可。
(1)導入使用到的模塊:
-
1#服務器端
-
2import socket
-
3import threading
-
4import numpy as np
-
5import cv2
-
6import os
-
(2)創建鼠標點擊事件函數,用來獲取鼠標點擊的位置坐標:
-
1print("等待連接---")
-
2def mouse_click(event, x, y, flags, para):
-
3 if event == cv2.EVENT_LBUTTONDOWN: # 左邊鼠標點擊
-
4 f=open("1.txt","w")
-
5 f.write(str(x)+","+str(y))
-
6 f.close()
-
(3)創建服務器端接收數據函數,用來實時接收傳輸過來的圖像數據並顯示:
-
1def recv_msg(clientsocket):
-
2 while True:
-
3 # 接受客戶端消息,設置一次最多接受10240字節的數據
-
4 recv_msg = clientsocket.recv(102400)
-
5 # 把接收到的東西解碼
-
6 msg = np.fromstring(recv_msg, np.uint8)
-
7 img_decode = cv2.imdecode(msg, cv2.IMREAD_COLOR)
-
8 try:
-
9 s=img_decode.shape
-
10 img_decode=img_decode
-
11 temp=img_decode
-
12 except:
-
13 img_decode=temp
-
14 pass
-
15 cv2.imshow('SERVER', img_decode)
-
16 cv2.setMouseCallback("SERVER", mouse_click)
-
17 try:
-
18 f=open("1.txt")
-
19 txt=f.read()
-
20 f.close()
-
21 reply=txt
-
22 print(reply)
-
23 clientsocket.send(reply.encode('utf-8'))
-
24 os.remove("1.txt")
-
25 except:
-
26 pass
-
27 if cv2.waitKey(1) & 0xFF == ord('q'):
-
28 break
-
(4)主函數,用來建立連接和數據接收等功能。
-
1def main():
-
2 socket_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
-
3 host='服務器的本地ip'
-
4 #設置被監聽的端口號,小於1024的端口號不能使用
-
5 port=設置的端口
-
6 socket_server.bind((host,port))
-
7 #設置最大監聽數,也就是最多可以同時響應幾個客戶端請求,一般配合多線程使用
-
8 socket_server.listen(5)
-
9 #等待客戶端連接,一旦有了連接就立刻向下執行,否則等待
-
10 #accept()函數會返回一個元組,第一個元素是客戶端socket對象,第二個元素是客戶端地址(ip地址+端口號)
-
11 clientsocket,addr=socket_server.accept()
-
12 # 有了客戶端連接后之后才能執行以下代碼,我們先向客戶端發送連接成功消息
-
13 clientsocket.send('連接成功'.encode('utf-8'))
-
14 # 和客戶端一樣開啟一個線程接受客戶端的信息
-
15 t=threading.Thread(target=recv_msg,args=(clientsocket,))
-
16 t.start()
-
02
遠程控制GUI窗口
遠控桌面GUI主要是為了美觀而用,需要大家根據遠程代碼進行集合修改。當然單獨使用上述代碼已經可以實現功能了,只是不夠美觀。由於考慮到此處代碼量較大,且不是重點,故粗略講解
(1)導入相關庫:
-
1from PyQt5.QtWidgets import *
-
2from PyQt5.QtCore import *
-
3from PyQt5.QtGui import QPalette, QBrush, QPixmap
-
4import os
-
5import socket
-
6import threading
-
7import cv2
-
8import numpy as np
-
9from PIL import ImageGrab
-
10from pynput.mouse import Button,Controller
-
11import time
-
(2)建立鼠標控制函數和點擊函數
-
1m = Controller()
-
2def mouse_click(event, x, y, flags, para):
-
3 if event == cv2.EVENT_LBUTTONDOWN: # 左邊鼠標點擊
-
4 print( x, y)
-
5 m.position = (x, y)
-
6 time.sleep(0.1)
-
7 m.click(Button.left, 1)
-
(3)GUI界面初始化,由於我們需要把實時的視頻顯示在窗口上,故也需要使用到opencv。
-
1def __init__(self, parent=None):
-
2 super(Ui_MainWindow, self).__init__(parent)
-
3 # self.face_recong = face.Recognition()
-
4 self.timer_camera = QtCore.QTimer()
-
5 self.cap = cv2.VideoCapture()
-
6 self.CAM_NUM = 0
-
7 self.set_ui()
-
8 self.slot_init()
-
9 self.__flag_work = 0
-
10 self.x = 0
-
11 self.count = 0
-
(4)設置窗口大小和控件位置等信息。創建布局和設置名稱
-
1def set_ui(self):
-
2 self.__layout_main = QtWidgets.QHBoxLayout()
-
3 self.__layout_fun_button = QtWidgets.QVBoxLayout()
-
4 self.__layout_data_show = QtWidgets.QVBoxLayout()
-
5 self.button_open_camera = QtWidgets.QPushButton(u'遠程桌面')
-
6 self.button_close = QtWidgets.QPushButton(u'退出')
-
7 # Button 的顏色修改
-
8 button_color = [self.button_open_camera, self.button_close]
-
9 for i in range(2):
-
10 button_color[i].setStyleSheet("QPushButton{color:black}"
-
11 "QPushButton:hover{color:red}"
-
12 "QPushButton{ outline: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; list-style: none; overflow-wrap: break-word; height: 22px;">13 "QPushButton{border:2px}"
-
14 "QPushButton{border-radius:10px}"
-
15 "QPushButton{padding:2px 4px}")
-
16 self.button_open_camera.setMinimumHeight(50)
-
17 self.button_close.setMinimumHeight(50)
-
18 # move()方法移動窗口在屏幕上的位置到x = 300,y = 300坐標。
-
19 self.move(500, 500)
-
20 # 信息顯示
-
21 self.label_show_camera = QtWidgets.QLabel()
-
22 self.label_move = QtWidgets.QLabel()
-
23 self.label_move.setFixedSize(100, 100)
-
24 self.label_show_camera.setFixedSize(1530,863)
-
25 self.label_show_camera.setAutoFillBackground(False)
-
26 self.__layout_fun_button.addWidget(self.button_open_camera)
-
27 self.__layout_fun_button.addWidget(self.button_close)
-
28 self.__layout_fun_button.addWidget(self.label_move)
-
29 self.__layout_main.addLayout(self.__layout_fun_button)
-
30 self.__layout_main.addWidget(self.label_show_camera)
-
31 self.setLayout(self.__layout_main)
-
32 self.label_move.raise_()
-
33 self.setWindowTitle(u'遠控桌面GUI')
-
34 '''
-
35 # 設置背景圖片
-
36 palette1 = QPalette()
-
37 palette1.setBrush(self.backgroundRole(), QBrush(QPixmap('background.jpg')))
-
38 self.setPalette(palette1)
-
39 '''
-
(5)獲取鼠標點擊時的坐標:
-
1def mousePressEvent(self,event):
-
2 if event.buttons() & QtCore.Qt.LeftButton:
-
3 x = event.x()-120
-
4 y = event.y()-10
-
5 text = "x: {0},y: {1}".format(x,y)
-
6 if x>=0 and y>=0:
-
7 m.position = (x, y)
-
8 time.sleep(0.1)
-
9 m.click(Button.left, 1)
-
10 print(text)
-
(6)按鈕綁定所設置的函數:
-
1def slot_init(self):
-
2 self.button_open_camera.clicked.connect(self.button_open_camera_click)
-
3 self.timer_camera.timeout.connect(self.show_camera)
-
4 self.button_close.clicked.connect(self.close)
-
(7)顯示桌面功能函數,並設置點擊時修改名稱,可以隨時關閉桌面
-
1def button_open_camera_click(self):
-
2 if self.timer_camera.isActive() == False:
-
3 self.timer_camera.start(30)
-
4 self.button_open_camera.setText(u'關閉')
-
5 else:
-
6 self.timer_camera.stop()
-
7 self.cap.release()
-
8 self.label_show_camera.clear()
-
9 self.button_open_camera.setText(u'遠程桌面')
-
(8)顯示桌面函數和退出程序函數
-
1def show_camera(self):
-
2 im = ImageGrab.grab()
-
3 imm = cv2.cvtColor(np.array(im), cv2.COLOR_RGB2BGR) # 轉為opencv的BGR格式
-
4 #imm = cv2.resize(imm, (1535, 863))
-
5 self.image = imm
-
6 # face = self.face_detect.align(self.image)
-
7 # if face:
-
8 # pass
-
9 show =cv2.resize(self.image, (1536,863))
-
10 show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
-
11 print(show.shape[1], show.shape[0])
-
12 # show.shape[1] = 640, show.shape[0] = 480
-
13 showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
-
14 self.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage))
-
15 #cv2.setMouseCallback(showImage, mouse_click)
-
16 # self.x += 1
-
17 # self.label_move.move(self.x,100)
-
18 # if self.x ==320:
-
19 # self.label_show_camera.raise_()
-
20def closeEvent(self, event):
-
21 ok = QtWidgets.QPushButton()
-
22 cacel = QtWidgets.QPushButton()
-
23 msg = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning, u"關閉", u"是否關閉!")
-
24 msg.addButton(ok, QtWidgets.QMessageBox.ActionRole)
-
25 msg.addButton(cacel, QtWidgets.QMessageBox.RejectRole)
-
26 ok.setText(u'確定')
-
27 cacel.setText(u'取消')
-
28 # msg.setDetailedText('sdfsdff')
-
29 if msg.exec_() == QtWidgets.QMessageBox.RejectRole:
-
30 event.ignore()
-
31 else:
-
32 # self.socket_client.send_command(self.socket_client.current_user_command)
-
33 if self.cap.isOpened():
-
34 self.cap.release()
-
35 if self.timer_camera.isActive():
-
36 self.timer_camera.stop()
-
37 event.accept()
-