web端实现远程桌面控制

阐述

应着标题所说,web端实现远程桌面控制,方案有好几种,达到的效果就是类似于向日葵一样可以远程桌面,但是操作方可以不用安装客户端,只需要一个web浏览器即可实现,桌面端需写一个程序用来socket连接和执行Windows指令。

实现方案

使用webSocket实现web端和桌面端的实时TCP通讯和连接,连接后桌面端获取自己的桌面流以照片流的形式截图发送blob格式给web端,web端再接收后将此格式解析再赋值在img标签上,不停的接收覆盖,类似于快照的形式来呈现画面,再通过api获取web端的鼠标事件和键盘事件通过webSocket发送给客户端让他执行Windows事件,以此来达到远程控制桌面控制效果。

Demo实现

因为为了方便同事观看,所以得起一个框架服务,我习惯用vue3了,但大部分都是js代码,可参考改写。

html只需一行搞定。

 
<div>
   <img ref="imageRef" class="remote" src="" alt="">
 </div>

接下来就是socket连接,用socket库和直接new webSocket都可以连接,我习惯用库了,因为多一个失败了可以自动连接的功能,少写一点代码🤣,库也是轻量级才几k大小。顺便把socket心跳也加上,这个和对端协商好就行了,一般都是ping/pong加个type值,发送时记得处理一下使用json格式发送,如果连接后60秒后没有互相发送消息客户端就会认为你是失联断开连接了,所以他就会强行踢掉你连接状态,所以心跳机制还是必不可少的。

 
import ReconnectingWebSocket from 'reconnecting-websocket'
const remoteControl = '192.168.1.175'
const scoketURL = `ws://${remoteControl}:10086/Echo`
const imageRef = ref()

onMounted(() => {
  createdWebsocket()
})

const createdWebsocket = () => {
  socket = new ReconnectingWebSocket(scoketURL)
  socket.onopen = function () {
    console.log('连接已建立')
    resetHeart()
  }
  socket.onmessage = function (event) {
    // console.log(event.data)
    const objectDta = JSON.parse(event.data)
    console.log(objectDta)
    if (objectDta.type === 100) {
      resetHeart()
    }
  }
  socket.onclose = function () {
    console.log('断开连接')
  }
}

let heartTime = null // 心跳定时器实例
let socketHeart = 0  // 心跳次数
let HeartTimeOut = 20000 // 心跳超时时间
let socketError = 0 // 错误次数
// socket 重置心跳
const resetHeart = () => {
  socketHeart = 0
  socketError = 0
  clearInterval(heartTime)
  sendSocketHeart()
}
const sendSocketHeart = () => {
  heartTime = setInterval(() => {
    if (socketHeart <= 3) {
      console.log('心跳发送:', socketHeart)
      socket.send(
        JSON.stringify({
          type: 100,
          key: 'ping'
        })
      )
      socketHeart = socketHeart + 1
    } else {
      reconnect()
    }
  }, HeartTimeOut)
}
// socket重连
const reconnect = () => {
  socket.close()
  if (socketError <= 3) {
    clearInterval(heartTime)
    socketError = socketError + 1
    console.log('socket重连', socketError)
  } else {
    console.log('重试次数已用完的逻辑', socketError)
    clearInterval(heartTime)
  }
}

成功稳定连接后那么恭喜你完成第一步了,接下来就是获取对端发来的照片流了,使用socket.onmessageapi用来接收对端消息,需要转一下json,因为发送的数据照片流很快,控制台直接刷屏了,所以简单处理一下。收到照片流把blob格式处理一下再使用window.URL.createObjectURL(blob)赋值给img即可。

 
socket.onmessage = function (event) {
    // console.log(event.data)
    if (event.data instanceof Blob) { // 处理桌面流
      const data = event.data
      const blob = new Blob([data], { type: "image/jpg" })
      imageRef.value.src = window.URL.createObjectURL(blob)
    } else {
      const objectDta = JSON.parse(event.data)
      console.log(objectDta)
      if (objectDta.type === 100) {
        resetHeart()
      }
    }
  }

此时页面可以呈现画面了,并且是可以看得到对面操作的,但让人挠头的是,分辨率和尺寸不对,有上下和左右的滚动条显示,并不是百分百的,解决这个问题倒是不难,但如需考虑获取自身的鼠标坐标发送给对端,这个坐标必须准确无误,简单来说就是分辨率自适应,因为web端使用的电脑屏幕大小是不一样的,切桌面端发送给你的桌面流比如是全屏分辨率的,以此得做适配,这个放后面解决,先来处理鼠标和键盘事件,纪录下来并发送对应的事件给桌面端。记得去除浏览器的拖动和鼠标右键事件,以免效果紊乱。

 
const watchControl = () => { // 监听事件
  window.ondragstart = function (e) { // 移除拖动事件
    e.preventDefault()
  }
  window.ondragend = function (e) { // 移除拖动事件
    e.preventDefault()
  }
  window.onkeydown = function (e) { // 键盘按下
    console.log('键盘按下', e)
    socket.send(JSON.stringify({ type: 0, key: e.keyCode }))
  }
  window.onkeyup = function (e) { // 键盘抬起
    console.log('键盘抬起', e)
    socket.send(JSON.stringify({ type: 1, key: e.keyCode }))
  }
  window.onmousedown = function (e) { // 鼠标单击按下
    console.log('单击按下', e)
    console.log(newPageX, 'newPageX')
    console.log(newPageY, 'newPageY')
    socket.send(JSON.stringify({ type: 5, x: pageX, y: pageY }))
  }
  window.onmouseup = function (e) { // 鼠标单击抬起
    console.log('单击抬起', e)
    socket.send(JSON.stringify({ type: 6, x: pageX, y: pageY }))
  }
  window.oncontextmenu = function (e) { // 鼠标右击
    console.log('右击', e)
    e.preventDefault()
    socket.send(JSON.stringify({ type: 4, x: pageX, y: pageY }))
  }
  window.ondblclick = function (e) { // 鼠标双击
    console.log('双击', e)
  }
  window.onmousewheel = function (e) { // 鼠标滚动
    console.log('滚动', e)
    const moving = e.deltaY / e.wheelDeltaY
    socket.send(JSON.stringify({ type: 7, x: e.x, y: e.y, deltaY: e.deltaY, deltaFactor: moving }))
  }
  window.onmousemove = function (e) { // 鼠标移动
    if (!timer) {
      timer = setTimeout(function () {
        console.log("鼠标移动:X轴位置" + e.pageX + ";Y轴位置:" + e.pageY)
        socket.send(JSON.stringify({ type: 2, x: pageX, y: pageY }))
        timer = null
      }, 60)
    }
  }
}

现在就可以实现远程控制了,发送的事件类型根据桌面端服务需要什么参数协商好就成,接下来就是处理分辨率适配问题了,解决办法大致就是赋值img图片后拿到他的参数分辨率,然后获取自身浏览器的宽高,除以他的分辨率再乘上自身获取的鼠标坐标就OK了,获取img图片事件需要延迟一下,因为是后面socket连接后才赋值的图片,否则宽高就一直是0,加在watchControl事件里面,发送时坐标也要重新计算。

 
const watchControl = () => {
    console.dir(imageRef.value)
    imgWidth.value = imageRef.value.naturalWidth === 0 ? 1920 : imageRef.value.naturalWidth// 图片宽度
    imgHeight.value = imageRef.value.naturalHeight === 0 ? 1080 : imageRef.value.naturalHeight // 图片高度
    clientHeight = document.body.offsetHeight
    
    ......
    
    window.onmousedown = function (e) { // 鼠标单击按下
    console.log('单击按下', e)
    const newPageX = parseInt(e.pageX * (imgWidth.value / clientWidth)) // 计算分辨率
    const newPageY = parseInt(e.pageY * (imgHeight.value / clientHeight))
    console.log(newPageX, 'newPageX')
    console.log(newPageY, 'newPageY')
    socket.send(JSON.stringify({ type: 5, x: newPageX, y: newPageY }))
  }
}

现在就几乎大功告成了,坐标稳定发送,获取的也是正确计算出来的,下面再做一些socket加密优化,还有事件优化,集成到项目里面离开时还是要清除所有事件和socket连接,直接上完整全部代码。

 
<template>
  <div>
    <img ref="imageRef" class="remote" src="" alt="" />
  </div>
</template>

<script setup>
import ReconnectingWebSocket from 'reconnecting-websocket'
import { Base64 } from 'js-base64'

onMounted(() => {
  createdWebsocket()
})

const route = useRoute()
let socket = null
const secretKey = 'keyXXXXXXX'
const remoteControl = '192.168.1.xxx'
const scoketURL = `ws://${remoteControl}:10086/Echo?key=${Base64.encode(secretKey)}`
const imageRef = ref()
let timer = null
const clientWidth = document.documentElement.offsetWidth
let clientHeight = null
const widthCss = (window.innerWidth) + 'px'
const heightCss = (window.innerHeight) + 'px'
const imgWidth = ref() // 图片宽度
const imgHeight = ref() // 图片高度

const createdWebsocket = () => {
  socket = new ReconnectingWebSocket(scoketURL)
  socket.onopen = function () {
    console.log('连接已建立')
    resetHeart()
    setTimeout(() => {
      watchControl()
    }, 500)
  }
  socket.onmessage = function (event) {
    if (event.data instanceof Blob) { // 处理桌面流
      const data = event.data
      const blob = new Blob([data], { type: 'image/jpg' })
      imageRef.value.src = window.URL.createObjectURL(blob)
    } else {
      const objectDta = JSON.parse(event.data)
      console.log(objectDta)
      if (objectDta.type === 100) {
        resetHeart()
      }
    }
  }
  socket.onclose = function () {
    console.log('断开连接')
  }
}

const handleMousemove = (e) => { // 鼠标移动
  if (!timer) {
    timer = setTimeout(function () {
      const newPageX = parseInt(e.pageX * (imgWidth.value / clientWidth)) // 计算分辨率
      const newPageY = parseInt(e.pageY * (imgHeight.value / clientHeight))
      // console.log(newPageX, 'newPageX')
      // console.log(newPageY, 'newPageY')
      // console.log('鼠标移动:X轴位置' + e.pageX + ';Y轴位置:' + e.pageY)
      socket.send(JSON.stringify({ type: 2, x: newPageX, y: newPageY }))
      timer = null
    }, 60)
  }
}
const handleKeydown = (e) => { // 键盘按下
  console.log('键盘按下', e)
  socket.send(JSON.stringify({ type: 0, key: e.keyCode }))
}
const handleMousedown = (e) => { // 鼠标单击按下
  console.log('单击按下', e)
  const newPageX = parseInt(e.pageX * (imgWidth.value / clientWidth)) // 计算分辨率
  const newPageY = parseInt(e.pageY * (imgHeight.value / clientHeight))
  console.log(newPageX, 'newPageX')
  console.log(newPageY, 'newPageY')
  socket.send(JSON.stringify({ type: 5, x: newPageX, y: newPageY }))
}
const handleKeyup = (e) => { // 键盘抬起
  console.log('键盘抬起', e)
  socket.send(JSON.stringify({ type: 1, key: e.keyCode }))
}

const handleMouseup = (e) => { // 鼠标单击抬起
  console.log('单击抬起', e)
  const newPageX = parseInt(e.pageX * (imgWidth.value / clientWidth)) // 计算分辨率
  const newPageY = parseInt(e.pageY * (imgHeight.value / clientHeight))
  console.log(newPageX, 'newPageX')
  console.log(newPageY, 'newPageY')
  socket.send(JSON.stringify({ type: 6, x: newPageX, y: newPageY }))
}

const handleContextmenu = (e) => { // 鼠标右击
  console.log('右击', e)
  e.preventDefault()
  const newPageX = parseInt(e.pageX * (imgWidth.value / clientWidth)) // 计算分辨率
  const newPageY = parseInt(e.pageY * (imgHeight.value / clientHeight))
  console.log(newPageX, 'newPageX')
  console.log(newPageY, 'newPageY')
  socket.send(JSON.stringify({ type: 4, x: newPageX, y: newPageY }))
}

const handleDblclick = (e) => { // 鼠标双击
  console.log('双击', e)
}

const handleMousewheel = (e) => { // 鼠标滚动
  console.log('滚动', e)
  const moving = e.deltaY / e.wheelDeltaY
  socket.send(JSON.stringify({ type: 7, x: e.x, y: e.y, deltaY: e.deltaY, deltaFactor: moving }))
}

const watchControl = () => { // 监听事件
  console.dir(imageRef.value)
  imgWidth.value = imageRef.value.naturalWidth === 0 ? 1920 : imageRef.value.naturalWidth// 图片宽度
  imgHeight.value = imageRef.value.naturalHeight === 0 ? 1080 : imageRef.value.naturalHeight // 图片高度
  clientHeight = document.body.offsetHeight

  window.ondragstart = function (e) { // 移除拖动事件
    e.preventDefault()
  }
  window.ondragend = function (e) { // 移除拖动事件
    e.preventDefault()
  }
  window.addEventListener('mousemove', handleMousemove)
  window.addEventListener('keydown', handleKeydown)
  window.addEventListener('mousedown', handleMousedown)
  window.addEventListener('keyup', handleKeyup)
  window.addEventListener('mouseup', handleMouseup)
  window.addEventListener('contextmenu', handleContextmenu)
  window.addEventListener('dblclick', handleDblclick)
  window.addEventListener('mousewheel', handleMousewheel)
}

let heartTime = null // 心跳定时器实例
let socketHeart = 0 // 心跳次数
const HeartTimeOut = 20000 // 心跳超时时间
let socketError = 0 // 错误次数
// socket 重置心跳
const resetHeart = () => {
  socketHeart = 0
  socketError = 0
  clearInterval(heartTime)
  sendSocketHeart()
}
const sendSocketHeart = () => {
  heartTime = setInterval(() => {
    if (socketHeart <= 3) {
      console.log('心跳发送:', socketHeart)
      socket.send(
        JSON.stringify({
          type: 100,
          key: 'ping'
        })
      )
      socketHeart = socketHeart + 1
    } else {
      reconnect()
    }
  }, HeartTimeOut)
}
// socket重连
const reconnect = () => {
  socket.close()
  if (socketError <= 3) {
    clearInterval(heartTime)
    socketError = socketError + 1
    console.log('socket重连', socketError)
  } else {
    console.log('重试次数已用完的逻辑', socketError)
    clearInterval(heartTime)
  }
}

onBeforeUnmount(() => {
  socket.close()
  console.log('组件销毁')
  window.removeEventListener('mousemove', handleMousemove)
  window.removeEventListener('keydown', handleKeydown)
  window.removeEventListener('mousedown', handleMousedown)
  window.removeEventListener('keyup', handleKeyup)
  window.removeEventListener('mouseup', handleMouseup)
  window.removeEventListener('contextmenu', handleContextmenu)
  window.removeEventListener('dblclick', handleDblclick)
  window.removeEventListener('mousewheel', handleMousewheel)
})
</script>

<style scoped>
.remote {
  width: v-bind(widthCss);
  height: v-bind(heightCss);
}
</style>

现在就算是彻底大功告成了,加密密钥或者方式还是和对端协商,流畅度和清晰度也不错的,简单办公还是没问题的,和不开会员的向日葵效果差不多,后面的优化方式大致围绕着图片压缩来做应该能达到更加流畅的效果,如果项目是https的话socket服务也要升级成wss协议,大致就这样,若有不正确的地方还请指正一番😁😁。

现在就算是彻底大功告成了,加密密钥或者方式还是和对端协商,流畅度和清晰度也不错的,简单办公还是没问题的,和不开会员的向日葵效果差不多,后面的优化方式大致围绕着图片压缩来做应该能达到更加流畅的效果,如果项目是https的话socket服务也要升级成wss协议,大致就这样,若有不正确的地方还请指正一番😁😁。

git地址:

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现 C# Web 实现远程电脑桌面实时预览,需要通过以下步骤: 1. 使用 C# 编写一个应用程序,可以截取远程电脑的桌面并将其转换为图像数据。 2. 创建一个 Web API,将该图像数据作为响应返回给前端。 3. 在前端使用 JavaScript 定时请求该 Web API,从而实现实时预览远程电脑的桌面。 以下是大致实现步骤: 1. 使用 C# 的 Remote Desktop Services API 或类似的库来远程连接到另一个计算机,并截取其桌面图像数据: ```csharp var serverName = "remote-pc-name"; var userName = "remote-user-name"; var password = "password"; var screenShot = RdpHelper.CaptureScreen(serverName, userName, password); ``` 其中,`RdpHelper.CaptureScreen` 方法是自定义的方法,该方法使用 Remote Desktop Services API 连接到远程计算机并截取屏幕截图。 2. 将图像数据转换为 Base64 编码的字符串,作为 Web API 的响应: ```csharp MemoryStream ms = new MemoryStream(); screenShot.Save(ms, ImageFormat.Jpeg); byte[] imageBytes = ms.ToArray(); string base64String = Convert.ToBase64String(imageBytes); return base64String; ``` 3. 在前端使用 JavaScript 定时请求该 Web API 并更新图像: ```javascript setInterval(function() { $.ajax({ url: 'http://localhost:5000/api/screenshot', method: 'GET', success: function(response) { var img = new Image(); img.src = 'data:image/jpeg;base64,' + response; $('#screenshot').attr('src', img.src); } }); }, 1000); ``` 这样,每秒钟就会从服务器获取一次远程电脑的桌面截图,并在网页上实时显示出来。需要注意的是,远程连接需要一定的权限和安全措施,具体实现需要谨慎考虑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值