在web端使用html5访问远程桌面
前几天看见一篇文章很受启发,通过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>