前几天看见一篇文章很受启发,通过html5实现远程桌面共享,但是文中给的两段代码实在没有多大用途,
于是这几天正好西安封城,就研究了一下简单实现了,不过还不太稳定,先发布出来。
1、原理简介
服务器端就是被控制的电脑,开启websocket端口监听,可以用封装好的fleck包。
接收到客户端信息解析键盘和鼠标事件,然后在本机上发送这些键盘和鼠标按键(用现成的组件KeyMouseHook)。
加个定时器每隔500毫秒执行截屏,并给所有客户端发送。
2、服务器端代码
using Fleck;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Loamen.KeyMouseHook;
using Loamen.KeyMouseHook.Native;
namespace 服务器
{
public partial class Form1 : Form
{
List<IWebSocketConnection> allSockets;
WebSocketServer server;
InputSimulator sim = new InputSimulator();
[DllImport("user32.dll")]
public static extern bool PrintWindow(
IntPtr hwnd, // Window to copy,Handle to the window that will be copied.
IntPtr hdcBlt, // HDC to print into,Handle to the device context.
UInt32 nFlags // Optional flags,Specifies the drawing options. It can be one of the following values.
);
public Form1()
{
InitializeComponent();
}
private void fsAddConsole(string message)
{
if (richTextBox1.Text.Length > 50000)
{
richTextBox1.Clear();
}
richTextBox1.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"));
richTextBox1.AppendText(" " + message);
richTextBox1.AppendText("\r\n");
richTextBox1.ScrollToCaret();
//写日志
if (checkBox3.Checked)
{
string path = ConfigurationManager.AppSettings["check_log_path"].ToString() + "/" + DateTime.Now.ToString("yyyy-MM-dd");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
File.AppendAllText(path + "/wsserver" + "_巡检日志.log", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff") + " " + message + "\r\n", Encoding.UTF8);
}
else {
}
}
private void button1_Click(object sender, EventArgs e)
{
//管理Socket
allSockets = new List<IWebSocketConnection>();
//配置地址
server = new WebSocketServer("ws://" + textBox1.Text + ":" + textBox2.Text);
//出错后进行重启
server.RestartAfterListenError = true;
//开始监听
server.Start(socket =>
{
//关联连接建立事件
socket.OnOpen = () =>
{
Invoke((new Action(() =>
{
fsAddConsole("Open!");
})));
allSockets.Add(socket);
//allSockets.ToList().ForEach(
// s => s.Send (ImageToBytes(pictureBox1.Image))
// );
};
//关联连接关闭事件
socket.OnClose = () =>
{
Invoke((new Action(() =>
{
fsAddConsole("Close!");
})));
allSockets.Remove(socket);
};
//接受客户端消息事件
socket.OnMessage = message =>
{
Invoke((new Action(() =>
{
fsAddConsole(message);
if (checkBox2.Checked)
{
var cmd = JsonConvert.DeserializeObject<ClientCmd>(message);
if (cmd.action == "mousemove")
{
var point = new Point(cmd.x, cmd.y).ToAbsolutePoint();
sim.Mouse.MoveMouseTo(point.X, point.Y);
}
else if (cmd.action == "mousedown")
{
sim.Mouse.LeftButtonDown();
}
else if (cmd.action == "mouseup")
{
sim.Mouse.LeftButtonUp();
}
else if (cmd.action == "click")
{
sim.Mouse.LeftButtonClick();
}
else if (cmd.action == "dbclick")
{
sim.Mouse.LeftButtonDoubleClick();
}
else if (cmd.action == "rightclick")
{
sim.Mouse.RightButtonClick();
}
else if (cmd.action == "keypress")
{
//sim.Keyboard.KeyPress();
}
else if (cmd.action == "keyup")
{
List<VirtualKeyCode> modifiedkey = new List<VirtualKeyCode>();
if (cmd.shift)
{
modifiedkey.Add(VirtualKeyCode.SHIFT);
}
if (cmd.ctrl)
{
modifiedkey.Add(VirtualKeyCode.CONTROL);
}
if (cmd.alt)
{
modifiedkey.Add(VirtualKeyCode.MENU);
}
sim.Keyboard.ModifiedKeyStroke(modifiedkey, (VirtualKeyCode)cmd.key);
}
else
{
//其他指令
}
}
else {
//查看模式,不响应远程指令
}
})));
//allSockets.ToList().ForEach(
// s => s.Send("bili: " + message));
};
//socket.OnPing
});
fsAddConsole("开始监听");
}
private void Form1_Shown(object sender, EventArgs e)
{
textBox1.Text = ConfigurationManager.AppSettings["ip"].ToString();
textBox2.Text = ConfigurationManager.AppSettings["port"].ToString();
}
Image imgTemp;
private Image GetWindowImage(IntPtr windownHandle)
{
Control control = Control.FromHandle(windownHandle);
Bitmap image = new Bitmap(control.Width, control.Height);
Graphics gp = Graphics.FromImage(image);
IntPtr dc = gp.GetHdc();
PrintWindow(windownHandle, dc, 0);
gp.ReleaseHdc();
gp.Dispose();
return image;
}
private void button2_Click(object sender, EventArgs e)
{
try
{
// imgTemp = GetWindowImage(Handle);
Bitmap bt = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Graphics g = Graphics.FromImage(bt);
g.CopyFromScreen(new Point(0, 0), new Point(0, 0), Screen.PrimaryScreen.Bounds.Size);//获取屏幕截图
imgTemp = SaveJpg(bt, 10);//设置图片清晰度
// //mm = GetWebImage(mm, 360, 240);//改变截屏图片大小
// //mm.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
// // pictureBox1.Image = tmgTemp;
// PrintScreen ps = new PrintScreen();
//imgTemp = ps.CaptureScreen();
//ps.CaptureScreenToFile(di + $"\\screenShoot{Guid.NewGuid()}.png", ImageFormat.Png);
fsAddConsole("获取屏幕成功");
}
catch (Exception ex)
{
fsAddConsole("获取屏幕出错"+ex.Message);
// throw;
}
}
public Image SaveJpg(Image image, long value)//设置图像质量1—100
{
ImageCodecInfo icInfo = null;
ImageCodecInfo[] infos = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo info in infos)
{
if (info.MimeType == "image/jpeg")
{
icInfo = info;
break;
}
}
EncoderParameters ep = new EncoderParameters(2);
ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, value);//质量,定义图片的清晰度
ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionLZW);//压缩,似乎无效果
return image;
}
/// <summary>
/// Convert Image to Byte[]
/// </summary>
/// <param name="image"></param>
/// <returns></returns>
public byte[] ImageToBytes(Image image)
{
MemoryStream ms = new MemoryStream();
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.ToArray();
}
private void button3_Click(object sender, EventArgs e)
{
allSockets.ToList().ForEach(
s => s.Send(ImageToBytes(imgTemp))
);
}
private void button4_Click(object sender, EventArgs e)
{
byte[] xx = ImageToBytes(imgTemp);
MessageBox.Show(xx.Length.ToString()
);
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
timer1.Enabled = checkBox1.Checked;
}
private void timer1_Tick(object sender, EventArgs e)
{
button2_Click(sender, e);
button3_Click(sender, e);
}
}
}
3、服务端界面截图

不做过多解释,上面的控件名称都是默认的,容易找到。
4、服务器端其他文件
还有一个指令类,没啥内容就截图看看

程序配置文件

5、客户端是jquery写的
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script src="jquery-1.11.2.min.js"></script>
<style>
body {
padding: 0px;
margin: 0px;
}
</style>
</head>
<body onselectstart="return false" width="100%" height="100%">
<!-- <img id="desktop" width="100%" height="100%" /> -->
<canvas id="desktop" width="1200px" height="800px"></canvas>
</body>
<script>
var wsImpl = window.WebSocket || window.MozWebSocket;
var ws = new wsImpl("ws://127.0.0.1:10086/");
var _canvas = document.getElementById("desktop");
var _ctx = _canvas.getContext("2d");
_canvas.width = window.innerWidth;
_canvas.height = window.innerHeight;
var image = new Image();
ws.onmessage = function (evt) {
//第一种方法:直接设置图片路径
// var image = document.getElementById("desktop");
// image.src = URL.createObjectURL(evt.data) ;
//第二种方法:设置canvas 通过二进制图片方式
image.src = URL.createObjectURL(evt.data);
image.onload = function () {
_ctx.drawImage(image, 0, 0);
var imagedata = _ctx.getImageData(0, 0, 1200, 1200);
_ctx.createImageData(imagedata);
};
//第三种方法:
// var imageData = _ctx.createImageData(1200, 800);
// imageData.data.set(evt.data);
// _ctx.putImageData(imageData, 0, 0);
};
// $(window).keypress(function (event) {
// //键按下的时候
// });
var shift_press = "false";
var ctrl_press = "false";
var alt_press = "false";
$(window).keydown(function (event) {
if (event.ctrlKey) {
ctrl_press = "true";
} else {
ctrl_press = "false";
}
if (event.shiftKey) {
shift_press = "true";
} else {
shift_press = "false";
}
if (event.altKey) {
alt_press = "true";
} else {
alt_press = "false";
}
if (event.key != "Shift" && event.key != "Ctrl" && event.key != "Alt") {
ws.send(
"{'action':'keydown','key':" +
event.keyCode +
",'shift':" +
shift_press +
",'ctrl':" +
ctrl_press +
",'alt':" +
alt_press +
"}"
);
}
});
$(window).keyup(function (event) {
if (event.ctrlKey) {
ctrl_press = "true";
} else {
ctrl_press = "false";
}
if (event.shiftKey) {
shift_press = "true";
} else {
shift_press = "false";
}
if (event.altKey) {
alt_press = "true";
} else {
alt_press = "false";
}
if (event.key != "Shift" && event.key != "Ctrl" && event.key != "Alt") {
ws.send(
"{'action':'keyup','key':" +
event.keyCode +
",'shift':" +
shift_press +
",'ctrl':" +
ctrl_press +
",'alt':" +
alt_press +
"}"
);
}
});
$(desktop).mousemove(function (e) {
var _x = e.offsetX;
var _y = e.offsetY;
ws.send("{'action':'mousemove','x':'" + _x + "','y':'" + _y + "'}");
});
$(desktop).contextmenu(function (e) {
e.returnValue = false;
var _x = e.offsetX;
var _y = e.offsetY;
ws.send("{'action':'rightclick','x':'" + _x + "','y':'" + _y + "'}");
});
$(desktop).mousedown(function (e) {
ws.send(
"{'action':'mousedown','x':'" + e.offsetX + "','y':'" + e.offsetY + "'}"
);
});
$(desktop).mouseup(function (e) {
ws.send(
"{'action':'mouseup','x':'" + e.offsetX + "','y':'" + e.offsetY + "'}"
);
});
$(desktop).click(function (e) {
ws.send(
"{'action':'click','x':'" + e.offsetX + "','y':'" + e.offsetY + "'}"
);
});
$(desktop).dblclick(function (e) {
ws.send(
"{'action':'dbclick','x':'" + e.offsetX + "','y':'" + e.offsetY + "'}"
);
});
document.oncontextmenu = function () {
event.returnValue = false;
};
</script>
</html>
