当前位置:   article > 正文

websocket实现远程控制桌面_远程桌面websocket

远程桌面websocket

WebSocket 简介

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它允许在客户端和服务器之间建立持久性的连接。相比传统的 HTTP 请求-响应模型,WebSocket 提供了更低的延迟和更高的效率

WebSocket API

是一组用于在 Web 浏览器和服务器之间进行实时全双工通信标准化接口。该 API 允许开发者创建 WebSocket 连接,以便在客户端和服务器之间进行双向数据传输。

WebSocket API 的关键部分包括:

(1)WebSocket 对象: 在 JavaScript 中,使用 WebSocket 构造函数可以创建 WebSocket 对象。这个对象代表了客户端和服务器之间的连接。通过 WebSocket 对象,开发者可以发送和接收消息,以及处理连接的状态变化。

// 创建 WebSocket 连接
const socket = new WebSocket('ws://example.com/socket');
  • 1
  • 2

(2)事件处理: WebSocket API 提供了一系列的事件,用于处理连接的不同阶段和接收消息。常见的事件包括 onopen(连接打开时触发)、onmessage(接收到消息时触发)、onclose(连接关闭时触发)、onerror(发生错误时触发)等。

socket.onopen = function(event) {
  console.log('WebSocket 连接已打开');
};

socket.onmessage = function(event) {
  console.log('收到消息:', event.data);
};

socket.onclose = function(event) {
  console.log('WebSocket 连接已关闭');
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(3)发送和接收消息: 使用 send 方法可以向服务器发送消息,而收到的消息将通过 onmessage 事件处理函数处理。

// 发送消息
socket.send('Hello, Server!');
  • 1
  • 2

(4)关闭连接: 使用 close 方法可以关闭 WebSocket 连接。服务器和客户端也可以相互发送关闭帧来关闭连接。

// 关闭连接
socket.close();
  • 1
  • 2

具体实例(实现远程桌面控制)前端HTML

1、创建 WebSocket 连接

const websocket = new WebSocket("ws://169.254.139.219:8000/ws/");
  • 1

2、连接打开时触发该事件

websocket.onopen = () => {              //连接打开时触发
	console.log("WebSocket connected!");
	// 连接成功后请求屏幕截图
	websocket.send("screenshot");
        };
  • 1
  • 2
  • 3
  • 4
  • 5

WebSocket.onopen用于指定连接成功后的回调函数。

websocket.send("screenshot");使用 WebSocket API 发送一个消息,其中消息内容是 “screenshot”。客户端请求服务器截取屏幕截图的动作。
这段代码通常用于客户端向服务器发送指定的消息。这个消息可以是任何你想要传递给服务器的信息。
.send() 是用于向服务端发送消息的方法

3、接收到消息时触发该事件(处理从后端接收到的数据,并在前端展示成图片)

websocket.onmessage = event => {
	// 处理从后端接收到的数据
	const data = event.data;
	const blob = new Blob([data], {type: "image/jpeg"});
	const screenshotImage = document.getElementById("screenshot");
	screenshotImage.onerror = function() {
		console.log('图片加载失败,刷新页面...');
        location.reload(); // 刷新页面
        };
    screenshotImage.src = URL.createObjectURL(blob);
    websocket.send("screenshot");
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

事件处理函数定义:websocket.onmessage事件处理函数,这个函数在 WebSocket 接收到消息时被调用。

数据处理: const data = event.data; 从事件对象中获取接收到的数据。这可能是二进制数据或文本数据,这取决于后端发送消息的方式
event 事件对象

创建 Blob 对象:const blob = new Blob([data], { type: "image/jpeg" }); 将接收到的数据转换为 Blob 对象。在这里,它被指定为图像类型 “image/jpeg”。
(1)Blob(Binary Large Object)对象是 JavaScript 中用来表示二进制数据的一种数据类型。它可以包含任意类型的数据,包括图像、音频、视频、文本等。Blob 对象通常用于处理和操作二进制数据,比如在前端处理文件上传、图像处理、音视频播放等场景。

获取图片元素: const screenshotImage = document.getElementById("screenshot"); 获取在 HTML 中定义的图片元素,其 id 为 “screenshot”。

在整个 HTML 文档中找到特定 ID 的元素

document.getElementById("screenshot")目的是在整个 HTML 文档中找到具有 ID 为 “screenshot” 的元素,并将该元素的引用存储在变量 screenshotImage 中。
(1)document: 是 JavaScript 中的一个全局对象,代表整个 HTML 文档。
(2)getElementById: 是 document 对象的一个方法,用于通过元素的 ID 获取对应的 HTML 元素。
(3)“screenshot”: 是作为参数传递给 getElementById 方法的元素 ID。

错误处理: screenshotImage.onerror 设置了图片加载失败时的错误处理函数。如果图片加载失败,会在控制台输出消息,并通过 location.reload() 刷新整个页面。
(1)console.log("haha"): 是 JavaScript 中用于输出信息到控制台的方法。它是一个用于调试和记录信息的常见手段。

设置图片元素的 src 属性: screenshotImage.src = URL.createObjectURL(blob); 使用 URL.createObjectURL 将 Blob 对象转换为图片的 URL,并将其赋值给图片元素的 src 属性,从而显示在页面上。
(1)src 属性是 HTML 元素中的一个属性,用于指定资源的来源。对于 元素而言,src 属性指定要显示的图像的来源,即图像的 URL 地址。

发送消息到后端: websocket.send("screenshot"); 在处理完数据并更新页面后,向后端发送 “screenshot” 消息,可能是为了请求下一次截图或执行其他操作。
发送的消息是一个简单的字符串 “screenshot”,可能表示客户端请求服务器发送一个屏幕截图。

??疑问:
1、=()=>、event=>、function()
2、screenshotImage.onerror = function(){ }
!!解答:()和function()意思相同,都是匿名函数(可以没有名字)。
event等同于(event),作为参数出现。
大概是这样。
查一下:
没有具体名称的函数常被称为匿名函数,因为它没有在定义时被命名。
匿名函数通常用于简单的、一次性的操作,比如在事件处理中、回调函数中,或者作为某些方法的参数。
使用匿名函数的好处之一是可以将其直接定义和传递,而不必显式地在代码中定义一个命名函数。这对于简短的、仅在一个地方使用的函数来说是一种方便的写法。

4、WebSocket 连接的关闭事件

websocket.onclose = () => {
	console.log("WebSocket disconnected!");
	alert("连接断开,将刷新重试!")
	location.reload()
};
  • 1
  • 2
  • 3
  • 4
  • 5

() => { ... }:这是一个箭头函数(arrow function),用于定义匿名的事件处理函数。在这里,箭头函数接收的参数为空(没有显式声明参数),因为 onclose` 事件通常不需要额外的参数。

websocket.onclose: 这一行代码给 WebSocket 对象设置了一个 onclose 事件处理函数,表示当 WebSocket 连接关闭时将执行这个函数。

alert("连接断开,将刷新重试!");: 弹出一个警告框,向用户提示 WebSocket 连接已经断开,建议刷新页面以尝试重新连接。

location.reload();: 调用 location.reload() 方法,刷新当前页面。这样做的目的是尝试重新建立 WebSocket 连接,以便恢复与服务器的通信。

5、鼠标移动事件

let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
document.addEventListener("mousemove", event => {
	// 计算鼠标位置信息相对于屏幕截图的坐标
	const screenshotImage = document.getElementById("screenshot");
	const rect = screenshotImage.getBoundingClientRect();
	const mouseX = event.clientX - rect.left;
	const mouseY = event.clientY - rect.top;
	lastMouseX = event.clientX - rect.left;
	lastMouseY = event.clientY - rect.top;
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
添加事件监听器

addEventListener 是 JavaScript 中用于为特定事件注册监听器(或回调函数)的方法。通过 addEventListener,你可以指定在特定事件发生时执行的操作。

target.addEventListener(type, listener [, options]);
  • 1

target: 你要添加事件监听器的目标对象(通常是 DOM 元素)。
type: 事件的类型,比如 “click”、“mouseover”、“keydown” 等。
listener: 事件发生时要调用的函数,也称为事件处理函数或回调函数。
options(可选): 是一个配置对象,可以包含 capture、once、passive 等选项。

document.addEventListener("mousemove", event => { ... });: 通过 addEventListener 方法向文档添加一个 mousemove 事件监听器。当用户在文档中移动鼠标时,触发这个事件。

const screenshotImage = document.getElementById("screenshot");: 获取文档中具有 id 为 “screenshot” 的元素,通常这是一个用于显示屏幕截图的图像元素。

const rect = screenshotImage.getBoundingClientRect();: 使用 getBoundingClientRect() 方法获取屏幕截图元素的位置信息(相对于视口的位置、大小等)。
getBoundingClientRect() 是一个 DOM 元素的方法,用于返回一个包含元素的位置、大小等信息的 DOMRect 对象。这个对象包含了一组属性,可以用于确定元素在视口中的位置和大小。
对于 screenshotImage.getBoundingClientRect(),它是在获取 screenshotImage 这个图像元素的位置信息。以下是这个方法返回的 DOMRect 对象的一些属性:

getBoundingClientRect()的一些属性

top: 元素上边缘距离视口顶部的距离。
right: 元素右边缘距离视口左边的距离。
bottom: 元素下边缘距离视口顶部的距离。
left: 元素左边缘距离视口左边的距离。
width: 元素的宽度。
height: 元素的高度。
这些值都是相对于视口(即浏览器窗口)的坐标。
(视口(Viewport)是指浏览器窗口中用于显示网页内容的区域。浏览器窗口的视口是用户实际可见的部分,而网页内容可能会比视口大。在移动设备上,视口通常是屏幕的大小。)

计算鼠标在屏幕截图上的相对坐标:
const mouseX = event.clientX - rect.left;: 计算鼠标相对于屏幕截图左侧边界的水平位置。
const mouseY = event.clientY - rect.top;: 计算鼠标相对于屏幕截图顶部边界的垂直位置。
(1)event.clientX,是写的html中的鼠标的横坐标
是一个鼠标事件对象属性,表示鼠标事件发生时鼠标指针相对于浏览器窗口客户区域(viewport)的水平坐标。

lastMouseX 和 lastMouseY: 这两个变量用于存储鼠标在屏幕截图上的相对坐标。在这段代码中,它们可能是全局变量,因为它们被声明在事件监听器函数的外部。这样的设计可能是为了在其他地方使用这些坐标信息。

??疑问:
1、mousemove 是一个鼠标事件类型,这个是已知有的吗,还是自己定义的?
2、target 一般是 DOM 元素,document是整个HTML的意思吗?
!!解答一下:
1、yes
2、不清楚,DOM元素再说吧
查一下:

鼠标事件合集

鼠标事件是在用户与页面上的元素进行交互时触发的事件。以下是一些常见的鼠标事件:
click: 当用户单击鼠标左键时触发。如果元素被点击,就会触发该事件。
dblclick: 当用户双击鼠标左键时触发。一般而言,会在两次点击之间的短时间内触发。
mousedown: 当用户按下鼠标按钮时触发。此事件会在 click 之前触发。
mouseup: 当用户释放鼠标按钮时触发。此事件会在 click 之前触发。
mousemove: 当用户移动鼠标时触发。该事件会在鼠标指针经过元素时连续触发。
contextmenu: 当用户右键单击时触发。通常会触发浏览器的上下文菜单。
wheel: 当鼠标滚轮滚动时触发。可以用来实现滚动效果或缩放操作。
dragstart: 当用户开始拖动可拖动元素时触发。
drag: 当用户拖动元素时触发。
dragend: 当用户完成拖动操作时触发。
mouseover: 当鼠标指针移入元素时触发。如果鼠标移动到元素内部,也会触发。
mouseout: 当鼠标指针离开元素时触发。如果鼠标从元素内部移动到外部,也会触发。
mouseenter: 当鼠标指针进入元素时触发。与 mouseover 不同,mouseenter 不会冒泡。
mouseleave: 当鼠标指针离开元素时触发。与 mouseout 不同,mouseleave 不会冒泡。

这只是一小部分鼠标事件的例子,还有其他一些事件,具体取决于开发者的需求。在处理鼠标事件时,可以使用 addEventListener 方法来为元素添加相应的事件监听器。

6、定时检测鼠标位置是否发生变化

setInterval(()=>{
	if (lastMouseX>0 && lastMouseY>0){
		if(isDragging){
			websocket.send(`mouseMove:${lastMouseX},${lastMouseY}`);
		}
	}
},150);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

setInterval 函数: JavaScript 中的定时器函数,用于按照指定的时间间隔重复执行指定的代码块。
(1)

setInterval(callback, delay);
  • 1

callback: 要周期性调用的函数或要执行的代码块。
delay: 以毫秒为单位的时间间隔,表示执行 callback 的频率。
如果你想要停止重复执行,可以使用 clearInterval 函数

() => { ... }: 这是箭头函数语法,表示匿名的函数体。在这里,箭头函数没有显式声明参数。
150: setInterval 的第二个参数,表示定时器的时间间隔,这里是 150 毫秒,即每隔 150 毫秒执行一次。

lastMouseX > 0 && lastMouseY > 0: 条件判断,确保 lastMouseX 和 lastMouseY 大于 0,即确保鼠标位置有效。

isDragging: 另一个条件,确保 isDragging 变量的值为真,表示正在拖拽操作。

websocket.send(mouseMove:${lastMouseX},${lastMouseY});: 如果满足上述条件,就通过 WebSocket 发送鼠标位置信息。这里使用了模板字符串,将 lastMouseX 和 lastMouseY 的值嵌入字符串中。

7、鼠标左键按下事件处理

        document.addEventListener("dragstart", event => {
            event.preventDefault(); // 阻止默认的左键点击
        });
  • 1
  • 2
  • 3

document.addEventListener("dragstart", event => { ... });: 通过 addEventListener 方法向文档添加一个 dragstart 事件监听器。dragstart 事件在拖拽操作开始时触发。

event.preventDefault();: preventDefault() 方法是用来阻止事件的默认行为。在这里,它被用于阻止拖拽操作中的默认行为。在拖拽操作中,浏览器通常会显示一个半透明的拖拽图标,但这个代码阻止了默认的左键点击拖拽行为,可能是为了自定义拖拽效果或禁止拖拽操作
例如,当用户点击一个链接时,浏览器会尝试导航到链接指定的 URL。如果在点击链接时调用了 preventDefault(),则不会执行该默认的导航行为。

8、鼠标左键按下和抬起

        // 鼠标左键按下事件处理
        document.getElementById("screenshot").addEventListener("mousedown", event => {
            isDragging = true;
            // event.preventDefault(); // 阻止默认的左键点击
            // 计算鼠标位置信息相对于屏幕截图的坐标
            const screenshotImage = document.getElementById("screenshot");
            const rect = screenshotImage.getBoundingClientRect();
            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;
            // 将鼠标点击信息发送给服务端(使用映射后的坐标)
            websocket.send(`mouseDown:${mouseX},${mouseY}`);
        });
        // 鼠标左键抬起事件处理
        document.getElementById("screenshot").addEventListener("mouseup", event => {
            isDragging = false;
            // 计算鼠标位置信息相对于屏幕截图的坐标
            const screenshotImage = document.getElementById("screenshot");
            const rect = screenshotImage.getBoundingClientRect();
            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;
            // 将鼠标点击信息发送给服务端(使用映射后的坐标)
            websocket.send(`mouseUp:${mouseX},${mouseY}`);
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

document.getElementById("screenshot"): 获取具有 ID 为 “screenshot” 的元素。
.addEventListener("mousedown", event => { ... });: 为该元素添加一个鼠标按下事件监听器。

9、鼠标右键点击

阻止默认的右键菜单弹出,获取鼠标点击位置的坐标信息,并通过 WebSocket 将这些信息发送给服务端。

        // 鼠标右键点击事件处理ok
        document.addEventListener("contextmenu", event => {
            event.preventDefault(); // 阻止默认的右键菜单弹出
            // 计算鼠标位置信息相对于屏幕截图的坐标
            const screenshotImage = document.getElementById("screenshot");
            const rect = screenshotImage.getBoundingClientRect();
            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;
            // 将鼠标右键点击信息发送给服务端(使用映射后的坐标)
            websocket.send(`mouseRightClick:${mouseX},${mouseY}`)
        });

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

??疑问:为什么左键需要单独设置阻止事件的默认行为,而右键不需要?
为什么左键的阻止事件的默认行为会影响到键盘呀?

10、鼠标滚轮事件

        // 鼠标滚轮事件处理
        document.addEventListener("wheel", event => {
            // 计算鼠标位置信息相对于屏幕截图的坐标
            const screenshotImage = document.getElementById("screenshot");
            const rect = screenshotImage.getBoundingClientRect();
            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;

            // 获取滚轮滚动量
            const scrollAmount = -event.deltaY;
            //console.log("hello")
            // 将鼠标滚轮信息发送给服务端(使用映射后的坐标和滚动量)
            console.log(`${mouseX},${mouseY}:${scrollAmount}`)
            websocket.send(`mouseScroll:${mouseX},${mouseY}:${scrollAmount}`);
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

const scrollAmount = -event.deltaY;: event.deltaY 表示垂直方向上的滚动量。通过取负值,可以根据滚轮滚动的方向获得正向或负向的滚动量。
(1)event.deltaY:是鼠标滚轮事件(wheel 事件)中的一个属性,表示用户滚动滚轮时的滚动量。它返回一个数字,表示垂直方向上的滚动量。
常规规定:
当用户向上滚动鼠标滚轮时,event.deltaY 返回负值。
当用户向下滚动鼠标滚轮时,event.deltaY 返回正值。
一些wheel事件的其他属性:

document.addEventListener('wheel', event => {
  console.log('DeltaX:', event.deltaX);
  console.log('DeltaY:', event.deltaY);
  console.log('DeltaMode:', event.deltaMode);
  console.log('DeltaZ:', event.deltaZ);
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

11、键盘按下事件

        // 键盘按键按下事件处理
        document.addEventListener("keydown", event => {
            const key = event.key;
            // 添加对组合键 Ctrl+C 和 Ctrl+V 的支持
            if (event.ctrlKey && key === 'c') {
                websocket.send(`keyDown:ctrl_c`);
            } else if (event.ctrlKey && key === 'v') {
                websocket.send(`keyDown:ctrl_v`);
            }else if (event.ctrlKey && key === 'x') {
                websocket.send(`keyDown:ctrl_x`);
            } else {
                websocket.send(`keyDown:${key}`);
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

document.addEventListener("keydown", event => { ... });: 添加一个键盘按键事件监听器。keydown 事件在用户按下键盘上的任意键时触发。

const key = event.key;: 获取被按下的键的标识。是一个键盘事件对象属性,表示被按下的键的标识。它返回一个字符串,表示按下的键。该字符串通常是按键上的字符。例如,如果用户按下字母 “A” 键,key 就会是字符串 “A”。

KeyboardEvent 对象的常用属性

event.key: 表示按下的键的标识。返回一个字符串,表示按下的键。这个属性是相对于键盘布局的,可能与实际字符不一致,特别是对于控制键。
event.code: 表示按下的键的物理位置,不受键盘布局的影响。返回一个字符串,通常与键盘上的标识相匹配。例如,不考虑 Shift 键,event.code 对于字母 “A” 将始终是 “KeyA”。
event.ctrlKey、event.shiftKey、event.altKey、event.metaKey: 表示是否同时按下了 Ctrl、Shift、Alt 或 Meta(Windows 键或 Command 键)键。这些属性返回布尔值。
event.keyCode 和 event.which: 这两个属性已被废弃,不推荐使用。过去用于获取按下键的字符编码,现在推荐使用 event.key。
event.repeat: 表示按键是否处于重复状态。返回一个布尔值,为 true 表示按键重复,为 false 表示按键首次按下。

if (event.ctrlKey && key === 'c') { ... } else if (event.ctrlKey && key === 'v') { ... } else if (event.ctrlKey && key === 'x') { ... } else { ... }: 这是一系列的条件语句,用于检测是否按下了特定的组合键,如 Ctrl+C、Ctrl+V、Ctrl+X 等。如果是,则通过 WebSocket 发送相应的消息。

websocket.send(keyDown:ctrl_c);: 使用 WebSocket 发送消息。这里的消息格式可能是类似于 keyDown:ctrl_c,其中 keyDown 是消息类型,ctrl_c 表示按下了 Ctrl+C 组合键。

具体实例(实现远程桌面控制)后端Python

1、导入模块

import asyncio
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse

from PIL import ImageGrab
import io
import pyautogui
from pyautogui import hotkey
from jinja2 import Environment, FileSystemLoader
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

asyncio(用于异步操作)
FastAPI(用于构建 Web 应用程序)
Pillow(用于图像处理,即 ImageGrab),用于截取屏幕图像
io(用于处理 I/O 操作)
pyautogui(用于进行用户界面自动化操作,例如模拟鼠标和键盘输入)
jinja2(用于模板引擎)

FastAPI

FastAPI 是一个用于构建 Web API 的现代、快速、基于 Python 的框架。 FastAPI 的主要目的是帮助开发者快速构建高性能的 Web API。你可以使用 FastAPI 来创建 RESTful API,处理 HTTP 请求和响应,并提供数据服务。 FastAPI 提供原生支持 WebSocket,使得实时通信和事件推送变得更容易。

app = FastAPI():创建了一个 FastAPI 应用程序实例,将其赋值给变量 app。该实例将用于配置和运行应用程序。可以在 app 实例上添加路由定义API操作以及配置应用程序的各种设置

定义 WebSocket 路由:

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    # WebSocket 连接的处理逻辑
    pass
  • 1
  • 2
  • 3
  • 4

使用 @app.websocket 装饰器创建了一个 WebSocket 路由,该路由位于 “/ws/{client_id}”,其中 {client_id} 是路径参数。WebSocket 连接的处理逻辑将在 websocket_endpoint 函数中实现。

WebSocket 连接处理逻辑:

async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        # 处理从客户端接收到的数据
        pass
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这个简化的例子中,WebSocket 连接被接受,然后在一个无限循环中接收从客户端发送的文本数据。实际应用中,你需要根据具体需求来处理接收到的数据。

路由

在 Web 开发中,路由(Route)是指将请求(Request)映射到相应的处理程序(Handler)或控制器(Controller)的机制。简而言之,路由定义了请求的路径(URL)与处理请求的代码之间的关系。
路由在 Web 开发中起到了非常重要的作用,它定义了应用程序如何响应不同的 URL 请求。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

@app.get("/") 装饰器表示对根路径的 HTTP GET 请求的处理函数。当用户访问根路径时,read_root 函数将返回一个包含 {“Hello”: “World”} 的 JSON 响应。
@app.get("/") 其中的/,表示路径的意思
127.0.0.1 在浏览器里访问表示本机??

2、创建一个Web API,并使用PyAutoGUI函数获取屏幕的宽度和高度

app = FastAPI()
pyautogui.PAUSE = 0

#获取屏幕分辨率
screen_width, screen_height = pyautogui.size()
  • 1
  • 2
  • 3
  • 4
  • 5

pyautogui.PAUSE = 0: 这一行设置了 PyAutoGUI 的 PAUSE 属性为 0,意味着 PyAutoGUI 将不会在执行操作时暂停,而是立即执行。默认情况下,PyAutoGUI 在执行每个动作之后会暂停一小段时间,以便用户有时间观察屏幕上的变化。通过将 PAUSE 设置为 0,可以禁用这种暂停。

screen_width, screen_height = pyautogui.size(): 这一行使用 PyAutoGUI 的 size() 函数获取屏幕的宽度和高度,并将它们分别赋给 screen_width 和 screen_height 变量。这可以用于后续的屏幕坐标计算。

3、管理WebSocket连接

# WebSocket连接管理器
class ConnectionManager:
    def __init__(self):
        self.active_connections = set()

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.add(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def send_screenshot(self, screenshot_data: bytes):
        for connection in self.active_connections:
            await connection.send_bytes(screenshot_data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

定义了一个名为 ConnectionManager 的类,用于管理WebSocket连接和处理与鼠标、键盘等交互相关的操作。

(1)初始化类

def __init__(self):
        self.active_connections = set()
  • 1
  • 2
'
运行

在创建类的实例时,为该实例初始化一个空的集合,用于跟踪该实例中的活动连接。这在管理WebSocket连接的类中是很常见的做法,以便在实例中维护连接的状态

★ 类 和 函数


类是一种抽象数据类型,用于封装数据和相关的操作。它是一种面向对象编程的基本概念,允许将数据和功能封装在一起。支持封装,即将对象的内部细节隐藏起来,并通过公共接口进行访问。多态允许同一个方法名在不同的类中表现出不同的行为。

示例:

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f"{self.name} is barking!")

my_dog = Dog("Buddy")
my_dog.bark()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
'
运行

如果你希望在创建类的实例时执行一些初始化工作,就需要定义 init 方法init 方法不是必须的。如果你的类不需要进行初始化操作,你可以不定义这个方法。

函数
函数是一段可重复使用的代码块,用于执行特定的任务。函数接受输入(参数),进行处理,然后返回输出(结果)。
函数可以接受零个或多个输入参数,用于影响函数的行为。函数的输出可以通过 return 语句返回。 函数通过调用来执行,可以在程序的任何地方多次调用,实现代码的复用。

示例:

def greet(name):
    return f"Hello, {name}!"

result = greet("Alice")
print(result)
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

(2)接受连接并将连接对象添加到类实例的 active_connections 集合中

async def connect(self, websocket: WebSocket):
    await websocket.accept()
    self.active_connections.add(websocket)
  • 1
  • 2
  • 3

async def connect(self, websocket: WebSocket):: 定义了一个异步方法 connect,它接受两个参数,self 表示类实例本身,而 websocket 是一个 WebSocket 对象,代表一个客户端与服务端建立的 WebSocket 连接。

await websocket.accept(): 在 WebSocket 连接建立时,通过 await websocket.accept() 来接受连接。这是一个异步操作,它通知客户端连接已成功建立,可以开始进行通信。

self.active_connections.add(websocket): 将成功建立的 WebSocket 连接对象 websocket 添加到 active_connections 这个集合中。这个集合通常用于跟踪当前类实例中所有活动的连接。

异步编程

在 WebSocket 服务器中,异步方法通常用于处理连接的建立、消息的接收和发送等异步操作。这样可以允许服务器同时处理多个连接,而不会因为等待某个连接的数据而阻塞整个程序的执行。

在 Python 中,当你调用一个异步函数或方法时,如果这个函数或方法使用了 async def 声明,并且其中包含了 await 关键字,那么调用它的地方也需要使用 await 关键字来等待它的完成。

相较于多线程编程,异步编程使用单线程,减少了线程切换和线程管理的开销。这使得异步方法在处理大量并发任务时更为高效。
在Python中,异步编程(asynchronous programming)是一种用于处理并发任务和提高程序性能的编程模型。异步方法允许程序在等待I/O操作、网络请求等阻塞操作时,不会阻塞整个程序的执行,而是可以切换到执行其他任务,从而提高程序的吞吐量和响应性。

(3)断开 WebSocket 连接

def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
  • 1
  • 2

active_connections 集合通常用于追踪当前类实例中的活动连接。通过调用 remove 方法,可以将指定的连接对象从集合中删除。

在断开 WebSocket 连接时,从类实例中移除相应的连接对象。通常在WebSocket服务器中,当客户端主动关闭连接或发生错误时,服务器会调用这个方法,以确保连接对象从活动连接集合中正确地被清除,从而保持状态的一致性。

(4)向所有活动的 WebSocket 连接发送屏幕截图的数据

async def send_screenshot(self, screenshot_data: bytes):
        for connection in self.active_connections:
            await connection.send_bytes(screenshot_data)
  • 1
  • 2
  • 3
'
运行

async def send_screenshot(self, screenshot_data: bytes):: 定义了一个名为 send_screenshot 的异步方法,它接受两个参数,self 表示类实例本身,而 screenshot_data 是一个字节串,包含屏幕截图的数据。

for connection in self.active_connections:: 使用 for 循环遍历 self.active_connections 集合中的每一个 WebSocket 连接对象。

await connection.send_bytes(screenshot_data): 对于每个连接对象,使用异步操作 await 调用连接对象的 send_bytes 方法,将屏幕截图的数据发送给客户端。send_bytes 方法通常是 WebSocket 库提供的方法,用于发送二进制数据。

4、处理前端通过 WebSocket 发送的鼠标和键盘事件

这些方法的主要目的是在接收到前端通过 WebSocket 发送的事件时,通过 PyAutoGUI 模拟鼠标和键盘的动作。

async def handle_mouse_move(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseMove:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y
    # 在服务端电脑上模拟鼠标移动
    pyautogui.moveTo(mouse_x, mouse_y, duration=0)

async def handle_mouse_click(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseClick:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y

    # 在服务端电脑上模拟鼠标移动
    pyautogui.click(mouse_x, mouse_y)
    
async def handle_mouse_down(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseDown:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y

    # 在服务端电脑上模拟鼠标点击
    pyautogui.mouseDown(mouse_x, mouse_y)
    
async def handle_mouse_up(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseUp:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y

    # 在服务端电脑上模拟鼠标左键
    pyautogui.mouseUp(mouse_x, mouse_y)

async def handle_mouse_right_click(self, data: str):
    # 解析前端发送的鼠标右键点击信息
    x, y = data.replace("mouseRightClick:", '').split(",")
    x, y = int(float(x)), int(float(y))

    # 在服务端电脑上模拟鼠标右键点击
    pyautogui.rightClick(x, y)

async def handle_mouse_scroll(self, data: str):        # 添加鼠标滚轮部分
    data = data.replace("mouseScroll:", "")
    first = data.split(":")[0]
    second = data.split(":")[1]
    # 解析前端发送的鼠标滚动量信息
    scroll_amount = int(float(second))
    x = int(float(first.split(",")[0]))    #改了一下
    y = int(float(first.split(",")[1]))

    # 在服务端电脑上模拟鼠标滚轮操作
    pyautogui.scroll(scroll_amount)

async def handle_key_down(self, data: str):
    # 解析前端发送的键盘按下信息
    event, key = data.split(":")

    # 在服务端电脑上模拟键盘按下
    pyautogui.keyDown(key)

async def handle_key_up(self, data: str):
    # 解析前端发送的键盘松开信息
    event, key = data.split(":")

    # 在服务端电脑上模拟键盘松开
    pyautogui.keyUp(key)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
'
运行

(1)鼠标移动

async def handle_mouse_move(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseMove:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y
    # 在服务端电脑上模拟鼠标移动
    pyautogui.moveTo(mouse_x, mouse_y, duration=0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
'
运行

async def handle_mouse_move(self, data: str):: 定义了一个异步方法 handle_mouse_move,接受两个参数,self 表示类实例本身,而 data 是一个字符串,包含前端发送的鼠标移动事件信息。

global mouse_x, mouse_y: 使用 global 关键字声明 mouse_x 和 mouse_y 为全局变量。这样做的目的是将鼠标的位置信息在全局范围内共享,以便其他方法或部分可以访问和使用这些信息。
在Python中,global 是一个关键字,用于声明一个变量在当前作用域之外为全局变量。当你在一个函数内部使用 global 关键字声明一个变量时,该变量将被视为全局变量,即使它在函数内部被赋值。

x, y = data.replace("mouseMove:", '').split(","): 解析前端发送的鼠标位置信息。首先通过 replace 方法去掉字符串中的 “mouseMove:” 部分,然后通过 split(“,”) 将字符串分割为两个部分,分别赋值给 x 和 y。

replace

replace 是Python字符串对象的方法之一,用于替换字符串中的特定子字符串。下面是一个简单的例子:

original_string = "Hello, world!"
new_string = original_string.replace("world", "Python")
print(new_string)
  • 1
  • 2
  • 3
'
运行

在这个例子中,replace 方法将原始字符串中的 “world” 替换为 “Python”,并将结果存储在新的字符串中。运行这段代码将输出:

Hello, Python!
  • 1

x, y = int(float(x)), int(float(y)): 将 x 和 y 转换为浮点数,然后再转换为整数。这一步的目的可能是确保鼠标位置的坐标为整数值。

mouse_x, mouse_y = x, y: 更新全局变量 mouse_x 和 mouse_y 的值为解析得到的鼠标位置。

pyautogui.moveTo(mouse_x, mouse_y, duration=0): 使用 PyAutoGUI 库,在服务端电脑上模拟鼠标移动到指定的坐标位置。duration=0 表示鼠标移动的过程没有延迟。

pyautogui

pyautogui 是一个Python库,用于在屏幕上进行鼠标和键盘自动化操作。它允许你通过程序模拟用户的鼠标和键盘输入,执行各种任务,例如自动化测试、屏幕截图、图像识别等。
以下是 pyautogui 常用的功能:

鼠标控制:
pyautogui.moveTo(x, y, duration=seconds): 将鼠标移动到屏幕上的指定位置。
pyautogui.click(x, y, clicks=1, interval=seconds): 模拟鼠标点击操作。
pyautogui.rightClick(x, y): 模拟鼠标右键点击。
pyautogui.doubleClick(x, y): 模拟鼠标双击。
pyautogui.mouseDown(x, y, button=‘left’): 模拟鼠标按下操作。
pyautogui.mouseUp(x, y, button=‘left’): 模拟鼠标松开操作。
pyautogui.scroll(amount): 模拟鼠标滚轮滚动。

键盘控制:
pyautogui.typewrite(‘Hello, World!’): 模拟键盘输入字符串。
pyautogui.press(‘enter’): 模拟按下指定键。
pyautogui.hotkey(‘ctrl’, ‘c’): 模拟按下组合键。

屏幕信息和截图:
pyautogui.size(): 获取屏幕的分辨率。
pyautogui.position(): 获取当前鼠标的位置。
pyautogui.screenshot(): 获取屏幕截图。

图像识别:
pyautogui.locateOnScreen(‘image.png’): 在屏幕上查找指定图像的位置。
pyautogui.click(pyautogui.locateOnScreen(‘button.png’)): 根据图像位置进行鼠标点击。

(2)鼠标左键点击

async def handle_mouse_click(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseClick:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y

    # 在服务端电脑上模拟鼠标移动
    pyautogui.click(mouse_x, mouse_y)
    
async def handle_mouse_down(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseDown:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y

    # 在服务端电脑上模拟鼠标点击
    pyautogui.mouseDown(mouse_x, mouse_y)
    
async def handle_mouse_up(self, data: str):
    global mouse_x, mouse_y
    # 解析前端发送的鼠标位置信息
    x, y = data.replace("mouseUp:", '').split(",")
    x, y = int(float(x)), int(float(y))
    # 更新鼠标位置信息
    mouse_x, mouse_y = x, y

    # 在服务端电脑上模拟鼠标左键
    pyautogui.mouseUp(mouse_x, mouse_y)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
'
运行

(3)鼠标右键点击

async def handle_mouse_right_click(self, data: str):
    # 解析前端发送的鼠标右键点击信息
    x, y = data.replace("mouseRightClick:", '').split(",")
    x, y = int(float(x)), int(float(y))

    # 在服务端电脑上模拟鼠标右键点击
    pyautogui.rightClick(x, y)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
'
运行

(4)鼠标滚轮操作

async def handle_mouse_scroll(self, data: str):        # 添加鼠标滚轮部分
    data = data.replace("mouseScroll:", "")
    first = data.split(":")[0]
    second = data.split(":")[1]
    # 解析前端发送的鼠标滚动量信息
    scroll_amount = int(float(second))
    x = int(float(first.split(",")[0]))    #改了一下
    y = int(float(first.split(",")[1]))

    # 在服务端电脑上模拟鼠标滚轮操作
    pyautogui.scroll(scroll_amount)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
'
运行

(5)键盘操作

async def handle_key_down(self, data: str):
    # 解析前端发送的键盘按下信息
    event, key = data.split(":")

    # 在服务端电脑上模拟键盘按下
    pyautogui.keyDown(key)

async def handle_key_up(self, data: str):
    # 解析前端发送的键盘松开信息
    event, key = data.split(":")

    # 在服务端电脑上模拟键盘松开
    pyautogui.keyUp(key)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
'
运行

5、创建了一个ConnectionManager 类的实例。

manager = ConnectionManager()
  • 1

ConnectionManager 类是一个用于管理 WebSocket 连接的自定义类(前面自定义的)。在这个实例化过程中,你创建了一个可以用于管理 WebSocket 连接的对象。

通常,ConnectionManager 类可能包含了一系列方法,用于处理连接、断开连接、发送消息等操作。通过实例化 manager 对象,你可以使用这些方法来管理 WebSocket 连接。

6、定义 WebSocket 路由 /ws/,使用 FastAPI 框架处理 WebSocket 连接

# WebSocket路由
@app.websocket("/ws/")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)

    try:
        while True:

            data = await websocket.receive_text()
            # print(data)


            if data == "screenshot":
                # print(111)
                # 获取屏幕截图
                screenshot = ImageGrab.grab()
                # 对截图进行压缩
                buffered_screenshot = io.BytesIO()
                screenshot.save(buffered_screenshot, format="JPEG", quality=90)
                screenshot_bytes = buffered_screenshot.getvalue()
                # 发送压缩后的截图数据给前端
                await manager.send_screenshot(screenshot_bytes)
                await asyncio.sleep(0.01)

            elif data.startswith("mouseMove"):
                print(f"鼠标移动事件:{data}")
                # 处理鼠标位置信息
                await manager.handle_mouse_move(data)
                # pass

            elif data.startswith("mouseDown"):
                # 处理鼠标左键按下信息
                await manager.handle_mouse_down(data)
                

            elif data.startswith("mouseUp"):
                # 处理鼠标左键抬起信息
                await manager.handle_mouse_up(data)
                

            elif data.startswith('mouseRightClick'):
                print(f"鼠标右键点击事件:{data}")
                await manager.handle_mouse_right_click(data)

            elif data.startswith('mouseClick'):
                print(f"鼠标左键点击事件:{data}")
                await manager.handle_mouse_click(data)
            
            elif data.startswith('mouseScroll'):
                print(f"鼠标滚轮事件:{data}")
                await manager.handle_mouse_scroll(data)

            elif data.startswith('keyDown'):
                print(f"键盘按下事件:{data}")
                
                if data.startswith('keyDown:ctrl_c'):  
                    pyautogui.hotkey('ctrl', 'c')
                elif data.startswith('keyDown:ctrl_v'):  
                    pyautogui.hotkey('ctrl', 'v')
                elif data.startswith('keyDown:ctrl_x'):  
                    pyautogui.hotkey('ctrl', 'x')
                elif data.startswith('keyDown:ctrl_a'):  
                    pyautogui.hotkey('ctrl', 'a')
                elif data.startswith('keyDown:ctrl_z'):  
                    pyautogui.hotkey('ctrl', 'z')
                elif data.startswith('keyDown:ctrl_s'):  
                    pyautogui.hotkey('ctrl', 's')
                else:
                    await manager.handle_key_down(data)

            elif data.startswith('keyUp'):
                print(f"键盘松开事件:{data}")

                await manager.handle_key_up(data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

(1)路由定义、连接管理器

@app.websocket("/ws/")
async def websocket_endpoint(websocket: WebSocket):
	await manager.connect(websocket)

  • 1
  • 2
  • 3
  • 4

这是一个 FastAPI 路由定义,使用 @app.websocket(“/ws/”) 装饰器表示这是一个 WebSocket 路由,监听路径为 /ws/。websocket_endpoint 是处理 WebSocket 连接的异步函数,其中的 websocket 参数表示与客户端建立的 WebSocket 连接。
websocket_endpoint 函数负责处理 WebSocket 连接的生命周期、接收消息,并根据消息类型执行相应的操作。

(2)消息处理循环

while True:
    data = await websocket.receive_text()
  • 1
  • 2

使用一个无限循环,持续等待从前端接收的文本消息。
websocket.receive_text() 是 FastAPI 中用于异步接收 WebSocket 文本消息的方法。在 WebSocket 连接上调用这个方法,可以等待并异步接收客户端发送的文本消息。

(3)截图消息处理

if data == "screenshot":
    # 获取屏幕截图
    screenshot = ImageGrab.grab()
    # 对截图进行压缩
    buffered_screenshot = io.BytesIO()
    screenshot.save(buffered_screenshot, format="JPEG", quality=90)
    screenshot_bytes = buffered_screenshot.getvalue()
    # 发送压缩后的截图数据给前端
    await manager.send_screenshot(screenshot_bytes)
    await asyncio.sleep(0.01)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

screenshot = ImageGrab.grab()
获取屏幕截图:使用 ImageGrab.grab() 方法获取当前屏幕的截图。这是通过 Python Imaging Library (PIL) 或其 fork(如 Pillow)提供的功能。

buffered_screenshot = io.BytesIO()  //创建为一个 BytesIO 对象,用于在内存中存储二进制数据。
screenshot.save(buffered_screenshot, format="JPEG", quality=90)
  • 1
  • 2

对截图进行压缩,将获取的截图保存到内存缓冲区 buffered_screenshot 中,格式为 JPEG,压缩质量为 90。
io.BytesIO() 是 Python 中 io 模块提供的一个类,用于在内存中创建一个二进制数据缓冲区,它的作用类似于一个文件对象。

screenshot_bytes = buffered_screenshot.getvalue()
await manager.send_screenshot(screenshot_bytes)
  • 1
  • 2

将压缩后的截图数据发送给前端。获取压缩后的截图数据,然后通过连接管理器 manager 的 send_screenshot 方法(自定义)将这些数据发送给前端。buffered_screenshot.getvalue()获取整个缓冲区的二进制数据,这包含了压缩后的屏幕截图。

await asyncio.sleep(0.01)
  • 1

使用 asyncio.sleep(0.01) 等待 0.01 秒。这可能是为了限制截图的频率,以免占用过多系统资源。这样的等待操作有助于控制数据发送的速率,使系统更加平滑运行。
asyncio.sleep(0.01) 是异步事件循环模块 asyncio 中的方法,用于在异步函数中实现暂停或延迟操作。

(4)鼠标、键盘消息处理

elif data.startswith("mouseMove"):
    print(f"鼠标移动事件:{data}")
    # 处理鼠标位置信息
    await manager.handle_mouse_move(data)

elif data.startswith("mouseDown"):
    # 处理鼠标左键按下信息
    await manager.handle_mouse_down(data)
    

elif data.startswith("mouseUp"):
    # 处理鼠标左键抬起信息
    await manager.handle_mouse_up(data)
    

elif data.startswith('mouseRightClick'):
    print(f"鼠标右键点击事件:{data}")
    await manager.handle_mouse_right_click(data)

elif data.startswith('mouseClick'):
    print(f"鼠标左键点击事件:{data}")
    await manager.handle_mouse_click(data)

elif data.startswith('mouseScroll'):
    print(f"鼠标滚轮事件:{data}")
    await manager.handle_mouse_scroll(data)

elif data.startswith('keyDown'):
    print(f"键盘按下事件:{data}")
    
    if data.startswith('keyDown:ctrl_c'):  
        pyautogui.hotkey('ctrl', 'c')
    elif data.startswith('keyDown:ctrl_v'):  
        pyautogui.hotkey('ctrl', 'v')
    elif data.startswith('keyDown:ctrl_x'):  
        pyautogui.hotkey('ctrl', 'x')
    elif data.startswith('keyDown:ctrl_a'):  
        pyautogui.hotkey('ctrl', 'a')
    elif data.startswith('keyDown:ctrl_z'):  
        pyautogui.hotkey('ctrl', 'z')
    elif data.startswith('keyDown:ctrl_s'):  
        pyautogui.hotkey('ctrl', 's')
    else:
        await manager.handle_key_down(data)

elif data.startswith('keyUp'):
    print(f"键盘松开事件:{data}")

    await manager.handle_key_up(data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

data.startswith("mouseMove") 是一个字符串方法,用于检查字符串 data 是否以指定的前缀 “mouseMove” 开头。这个条件判断通常用于在处理文本数据时检查消息或指令的类型。

print(f"鼠标移动事件:{data}"):这行代码使用了 Python 的 f-string(格式化字符串)功能,用于在控制台打印一条带有变量值的调试信息。
f"鼠标移动事件:{data}": 这是一个格式化字符串,其中 {data} 是一个占位符,表示在字符串中插入变量 data 的值。
f 表示这是一个 f-string,允许在字符串中通过 {} 插入变量值

 if data.startswith('keyDown:ctrl_c'):  
        pyautogui.hotkey('ctrl', 'c')
  • 1
  • 2

组合键输入,pyautogui.hotkey() 方法用于模拟按下多个键的组合。在这里,它模拟按下 Ctrl + C 组合键。‘ctrl’ 和 ‘c’ 是参数,表示按下 Ctrl 键和 C 键。

elif data.startswith('keyDown'):
    print(f"键盘按下事件:{data}")
    
    if data.startswith('keyDown:ctrl_c'):  
        pyautogui.hotkey('ctrl', 'c')
    elif data.startswith('keyDown:ctrl_v'):  
        pyautogui.hotkey('ctrl', 'v')
    elif data.startswith('keyDown:ctrl_x'):  
        pyautogui.hotkey('ctrl', 'x')
    elif data.startswith('keyDown:ctrl_a'):  
        pyautogui.hotkey('ctrl', 'a')
    elif data.startswith('keyDown:ctrl_z'):  
        pyautogui.hotkey('ctrl', 'z')
    elif data.startswith('keyDown:ctrl_s'):  
        pyautogui.hotkey('ctrl', 's')
    else:
        await manager.handle_key_down(data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

键盘组合键事件,应该在键盘按下事件里,不是单独在外面的!

7、异常处理块

用于捕获可能发生在 try 代码块中的异常。

except Exception as e:
    print(f"error:{e}")
    manager.disconnect(websocket)
  • 1
  • 2
  • 3

except Exception as e: 是一个 try…except 结构,它捕获可能发生在 try 代码块中的异常。Exception 是所有异常的基类,as e 将捕获到的异常赋值给变量 e。

Exception(捕获异常的基类)

在Python中,Exception 是所有内建异常类的基类。在异常类的继承体系中,Exception 是根类,其他具体的异常类都直接或间接地继承自它。这意味着如果你捕获了 Exception 类型的异常,你将捕获到所有内建异常的实例,因为它们都是 Exception 的子类。

以下是 Exception 中的一些常见异常情况及其中文描述:

StopIteration(停止迭代): 迭代器没有更多的值可供迭代。

StopAsyncIteration(停止异步迭代): 异步迭代器没有更多的值可供迭代。

ArithmeticError(算术错误):

FloatingPointError(浮点数错误):浮点运算中的错误,如除以零。
OverflowError(溢出错误):数值运算结果超出数据类型的表示范围。
ZeroDivisionError(除零错误):试图除以零。
AssertionError(断言错误): assert 语句失败。

AttributeError(属性错误): 尝试访问对象没有的属性。

BufferError(缓冲错误): 与缓冲对象相关的错误。

EOFError(文件末尾错误): 未能从输入流中读取数据,达到文件末尾。

ImportError(导入错误):

ModuleNotFoundError(模块未找到错误):导入不存在的模块。
LookupError(查找错误):

IndexError(索引错误):索引超出序列范围。
KeyError(键错误):字典中不存在的键。
MemoryError(内存错误): 内存耗尽。

NameError(名称错误):

UnboundLocalError(未绑定本地变量错误):在局部作用域引用未绑定的局部变量。
OSError(操作系统错误):
BlockingIOError(阻塞 I/O 错误):非阻塞操作试图在阻塞模式下执行。
ChildProcessError(子进程错误):子进程返回非零退出代码。
ConnectionError(连接错误):与连接相关的错误,如断开连接。
FileExistsError(文件已存在错误):创建已经存在的文件或目录。
FileNotFoundError(文件未找到错误):访问不存在的文件或目录。
InterruptedError(中断错误):系统调用被中断。
IsADirectoryError(是目录错误):试图对目录执行不允许的操作。
NotADirectoryError(不是目录错误):试图对非目录执行目录操作。
PermissionError(权限错误):操作没有足够的权限。
ProcessLookupError(进程查找错误):无法找到指定的进程。
TimeoutError(超时错误):操作超时。
ReferenceError(引用错误): 引用了无效的对象。

RuntimeError(运行时错误):

NotImplementedError(未实现错误):尚未实现的方法。
RecursionError(递归错误):递归层次太深。
SyntaxError(语法错误):
IndentationError(缩进错误):缩进不正确。
TabError(制表符错误):缩进混合使用了制表符和空格。
SystemError(系统错误): 解释器发现内部错误。

TypeError(类型错误): 操作或函数应用于不适当类型的对象。

ValueError(值错误):

UnicodeError(Unicode 错误):与 Unicode 相关的错误。
UnicodeDecodeError(Unicode 解码错误):无法解码 Unicode 数据。
UnicodeEncodeError(Unicode 编码错误):无法编码 Unicode 数据。
UnicodeTranslateError(Unicode 转换错误):无法转换 Unicode 数据。
Warning(警告):
DeprecationWarning(弃用警告):警告使用了被弃用的特性。
PendingDeprecationWarning(即将弃用警告):特性即将被弃用。
RuntimeWarning(运行时警告):运行时可能会发生的问题。
SyntaxWarning(语法警告):语法上可能会导致错误的构造。
UserWarning(用户警告):由用户代码生成的警告。
FutureWarning(未来警告):警告关于即将发生的改变。
ImportWarning(导入警告):与导入相关的警告。
UnicodeWarning(Unicode 警告):与 Unicode 相关的警告。
BytesWarning(字节警告):与字节或字节数组相关的警告。
ResourceWarning(资源警告):与资源使用相关的警告。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

8、使用 Jinja2 模板引擎渲染 HTML 模板

配置 Jinja2 环境,使用文件系统加载器加载位于 “templates” 目录下的 “index.html” 模板文件。加载后的模板对象存储在 template 变量中,可以用于后续的渲染操作。

# 使用jinja2渲染HTML模板
env = Environment(loader=FileSystemLoader("templates"))
template = env.get_template("index.html")
  • 1
  • 2
  • 3

(1)Environment 类: Environment 类是 Jinja2 的核心对象之一,用于存储配置和全局对象。通过创建 Environment 实例,你可以配置模板引擎的各种行为。
(2)FileSystemLoader 类: FileSystemLoader 是 Jinja2 提供的加载器之一,用于从文件系统中加载模板。在这里,通过传递 “templates” 参数,告诉加载器模板文件存放的目录是当前工作目录下的 “templates” 目录。
??为什么还要放在一个文件夹里呀??
(3)env.get_template("index.html"): 通过 get_template 方法获取指定名称的模板文件,这里是 “index.html”。这个方法返回一个 Template 对象,可以用于后续的模板渲染。

Jinja2 ??

在 web 框架(如 Flask)中,Jinja2 被广泛用于将动态数据嵌入到 HTML 页面中,实现动态的用户界面。
Jinja2 模板是一种用于生成动态文本的模板系统,广泛用于 web 开发中的模板引擎。Jinja2 模板允许在模板中插入动态内容,如变量、控制结构(条件语句、循环语句)、过滤器等,从而实现灵活的文本生成和渲染。

9、路由处理

一个 FastAPI 应用中的路由处理函数,用于处理 HTTP GET 请求,并返回一个简单的 HTML 页面作为客户端响应。该页面由 Jinja2 模板渲染生成,并包含屏幕分辨率信息。

# 主页,返回一个简单的HTML页面作为客户端
@app.get("/", response_class=HTMLResponse)
async def read_root():
    return template.render(screen_width=screen_width, screen_height=screen_height)
  • 1
  • 2
  • 3
  • 4

@app.get("/"): 这是 FastAPI 中的装饰器语法,表示下面定义的 read_root 函数处理 HTTP GET 请求,且处理的路径是根路径 /。

response_class=HTMLResponse: 这是 FastAPI 的参数,用于指定响应的类型。在这里,HTMLResponse 表示返回的响应是 HTML 类型。

async def read_root():: 这是定义的异步函数 read_root,用于处理根路径的 GET 请求

return template.render(screen_width=screen_width, screen_height=screen_height): 这里使用了之前创建的 Jinja2 模板 template 进行渲染。通过 template.render 方法将 screen_width 和 screen_height 作为参数传递给模板,生成最终的 HTML 内容。

screen_width 和 screen_height 可能是在应用启动时获取的屏幕分辨率。

10、运行当前 Python 脚本时启动 FastAPI 应用

主要用于在执行当前 Python 脚本时启动 FastAPI 应用,并使用 Uvicorn 作为服务器运行。

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
  • 1
  • 2
  • 3

if __name__ == "__main__":: 这是 Python 中的惯用语法,用于判断当前模块是否被直接执行。当 Python 解释器执行脚本时,name 的值会被设置为 “main”,因此这个条件判断成立时表示脚本正在作为主程序执行。

import uvicorn: 这一行导入了名为 uvicorn 的模块,uvicorn 是一个 ASGI(Asynchronous Server Gateway Interface)服务器,用于运行支持异步框架的应用程序,其中包括 FastAPI。
uvicorn 是一个强大的 ASGI 服务器,用于部署和运行异步 Python 应用程序。

uvicorn.run(app, host="0.0.0.0", port=8000): 这一行使用 uvicorn 模块的 run 函数启动 FastAPI 应用。具体参数解释如下:
app: 这是 FastAPI 应用的实例,表示要运行的应用。
host=“0.0.0.0”: 指定服务器监听的主机地址。在这里,“0.0.0.0” 表示接受来自任何可用网络接口的连接。
port=8000: 指定服务器监听的端口号。在这里,使用的是默认的 HTTP 端口 8000。

具体实例(实现远程桌面控制) 后台运行+开机自启动

双击启动使后台运行:

import subprocess

cmd = "python server.py"

subprocess.Popen(cmd, shell=True, creationflags=subprocess.SW_HIDE)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

将代码类型保存为.pyw

在这里插入图片描述

这段代码的目的是在后台启动一个新的Python进程,运行server.py脚本,并在Windows中隐藏新进程的窗口。这对于以服务形式运行的服务器或其他需要在后台默默执行的任务很有用。
import subprocess: 导入Python标准库中的subprocess模块,该模块用于创建和管理额外的进程。

cmd = "python server.py": 定义一个字符串变量cmd,其内容是要在命令行中执行的命令。在这里,它是运行python server.py,即启动名为server.py的Python脚本。

subprocess.Popen(cmd, shell=True, creationflags=subprocess.SW_HIDE): 使用subprocess.Popen函数创建一个新的进程。该函数接受一些参数,其中:

(1)cmd是要执行的命令,这里是之前定义的字符串"python server.py"。
(2)shell=True表示要通过系统的shell执行命令。
使用 shell=True 时,可以在命令中包含通常由 shell 处理的特殊字符,例如空格、通配符、管道等。这使得命令可以更灵活,类似于在命令提示符或终端中直接输入命令。
在这里,将通过shell来运行命令,因为命令中包含了空格,需要被解释器正确解释。
(3)creationflags=subprocess.SW_HIDE是一个特定于Windows的标志,用于隐藏新启动的进程的窗口。
subprocess.SW_HIDE: 是一个标志位,表示新进程的主窗口应该被隐藏。
这个标志对于在后台运行不需要用户交互的进程很有用。

实现开机自启动一个程序

(1) win+R
(2) shell:startup 进入文件夹(程序->启动文件夹)
(3) 将run.pyw的快捷方式放入其中
即可实现!

修改桌面快捷方式图标

只有快捷方式可以修改,所以需要先创建快捷方式。
然后点开该快捷方式的属性
其中有更改图标选项
在这里插入图片描述

特殊情况

在实践过程中出现了某些电脑可以正常运行,某些代码不能正常运行的情况

Windows有一个名为“用户界面权限隔离”(UIPI)的安全机制,它防止较低权限的进程向较高权限的进程发送消息。由于任务管理器通常以较高权限运行,你的远程桌面应用可能因为UIPI而无法与之交互。

远程连接之后 服务器的系统弹窗和类似任务管理器之类的程序 客户端处理不了
原因:windows有一个安全机制 低级别应用操作不了高级别应用
解决办法:是通过修改注册表把这个安全机制关掉
具体操作:请添加图片描述

整个流程图!!

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/943550
推荐阅读
相关标签
  

闽ICP备14008679号