使用libvncserver库快速搭建VNC服务端

文章目录

    • VNC是什么
    • libvncserver的优点和缺点
    • 构建libvncserver
    • 使用libvncserver搭建VNCServer
    • X11模拟鼠标键盘操作
    • libvncserver中处理鼠标键盘消息

VNC是什么

VNC(Virtual Network Computing)是一种使用远程帧缓冲协议(RFB)的屏幕分享及远程操作软件。VNC的服务端可以通过RFP协议将桌面视频帧导出到互联网上, 导出到互联网上的视频帧可以用VNC Client进行访问。如果VNC Server开启了websockets, 除了VNC Viewer外 我们还可以通过浏览器内的VNC查看器(如noVNC)来进行连接访问。

VNC总体遵循C/S架构。所以可以归结以下几点:

  • VNC Server,共享被控机器的屏幕,以被动的方式受VNC Client控制
  • VNC Client, 有时候也称为VNC Viewer,可以观看服务端控制的屏幕, 远程操作服务端
  • VNC协议,准确来说是RFB协议,没有采用该协议的远程桌面就不叫VNC,该协议的目的非常简单,就是把一帧帧地把像素矩阵(坐标系统是以左上角为原点的二维x,y坐标系)和事件消息(鼠标消息,键盘消息)从服务端传送到客户端。

libvncserver是一个开源的VNC服务器端库,可以集成到C/C++应用程序中,实现允许远程用户通过VNC客户端来显示和控制本地计算机的功能。libvncserver提供了一些功能强大的API,可以方便地与其他应用程序集成。它支持多个操作系统平台,包括Linux、Windows、Mac OS X和FreeBSD等,提供了多种VNC协议的实现。 使用libvncserver可以让开发人员方便地创建自己的VNC服务器,可用于远程桌面、远程支持、监控等多种应用场景。同时,libvncserver还提供了一些高级功能,例如SFTP和SSH隧道,支持SSL加密 安全性较高。

libvncserver的优点和缺点

libvncserver是一个功能强大的VNC服务器端库,具有如下优点:
1.开源免费:libvncserver使用GPL许可证开源,可以免费使用,并可对其进行修改和分发。
2.可移植性强:libvncserver支持多个操作系统平台,包括Linux、Windows、Mac OS X和FreeBSD等,具有良好的可移植性。
3.灵活性好:libvncserver提供了一些功能强大的API,可以方便地与其他应用程序集成,同时也支持多种VNC协议的实现。
4.高级功能完善:libvncserver提供了一些高级功能,比如SFTP和SSH隧道、SSL加密等,可以提高VNC的安全性。

但是,libvncserver也存在以下一些缺点:
1.使用相对复杂:libvncserver是一个可编程的库,使用相对比较复杂,需要开发者具有相关的编程经验和技能。
2.社区相对小:相比其他类似的VNC服务器端库,libvncserver的社区规模相对较小,对于新手来说可能学习起来较为困难。
3.不适合大规模部署:由于libvncserver是一个VNC服务器端库,不能直接用于大规模的VNC部署,而需要结合其他工具或应用程序来实现。
4.远程控制的粘贴板不支持复制粘贴Unicode文本,不能传送任何除Latin-1 character set以外的字符集编码。
5.需要注意的一点就是libvncserver这个开源VNC框架,还有很多BUG.

构建libvncserver

项目地址:
https://github.com/LibVNC/libvncserver

# 下载代码并构建
git clone  https://github.com/LibVNC/libvncserver  
cd libvncserver
mkdir build
cd build
cmake ..
cmake --build .

构建好的libvncserver参见后续的CSDN下载资源

使用libvncserver搭建VNCServer

//引用libvncserver  用于启用服务端
#include <rfb/rfb.h>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>//引用X11库用于抓取桌面,模拟鼠标键盘操作
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>using namespace std;
static rfbCursorPtr myCursor;//用于绘制鼠标焦点
static const char* cur="                   "" x                 "" xx                "" xxx               "" xxxx              "" xxxxx             "" xxxxxx            "" xxxxxxx           "" xxxxxxxx          "" xxxxxxxxx         "" xxxxxxxxxx        "" xxxxx             "" xx xxx            "" x  xxx            ""     xxx           ""     xxx           ""      xxx          ""      xxx          ""                   ";static const char* mask="xx                 ""xxx                ""xxxx               ""xxxxx              ""xxxxxx             ""xxxxxxx            ""xxxxxxxx           ""xxxxxxxxx          ""xxxxxxxxxx         ""xxxxxxxxxxx        ""xxxxxxxxxxxx       ""xxxxxxxxxx         ""xxxxxxxx           ""xxxxxxxx           ""xx  xxxxx          ""    xxxxx          ""     xxxxx         ""     xxxxx         ""      xxx          ";//转换像素数据格式 要不显示颜色异常
//XImage像素顺序RGBA --> 转frameBuffer像素顺序BGRA
void copyImage(const XImage* image, char* buffer, int width, int height, int stride) 
{if((image == NULL) || (buffer == NULL)){return;}char* src = (char*) image->data;for(int index=0; index< width*height; ++index){char single_pixels[4];memcpy(single_pixels,src+index*4, 4);single_pixels[2]= (src+index*4)[0];single_pixels[0]= (src+index*4)[2];memcpy(buffer + index * 4, single_pixels, 4); }	
}//X11 默认抓取的桌面内容不带鼠标的光标, 需要单独绘制鼠标光标
void paint_mouse_pointer(XImage *image, Display* display, int x_off, int y_off, unsigned int width, unsigned int height)
{Display *dpy = display;XFixesCursorImage *xcim;int x, y;int line, column;int to_line, to_column;int pixstride = image->bits_per_pixel >> 3;uint8_t *pix = (uint8_t*)image->data;/* Code doesn't currently support 16-bit or PAL8 */if (image->bits_per_pixel != 24 && image->bits_per_pixel != 32)return;xcim = XFixesGetCursorImage(dpy);x = xcim->x - xcim->xhot;y = xcim->y - xcim->yhot;to_line = min((y + xcim->height), (int)(height + y_off));to_column = min((x + xcim->width), (int)(width + x_off));for (line = max(y, y_off); line < to_line; line++) {for (column = max(x, x_off); column < to_column; column++) {int  xcim_addr = (line - y) * xcim->width + column - x;int image_addr = ((line - y_off) * width + column - x_off) * pixstride;int r = (uint8_t)(xcim->pixels[xcim_addr] >>  0);int g = (uint8_t)(xcim->pixels[xcim_addr] >>  8);int b = (uint8_t)(xcim->pixels[xcim_addr] >> 16);int a = (uint8_t)(xcim->pixels[xcim_addr] >> 24);if (a == 255) {pix[image_addr+0] = r;pix[image_addr+1] = g;pix[image_addr+2] = b;} else if (a) {/* pixel values from XFixesGetCursorImage come premultiplied by alpha */pix[image_addr+0] = r + (pix[image_addr+0]*(255-a) + 255/2) / 255;pix[image_addr+1] = g + (pix[image_addr+1]*(255-a) + 255/2) / 255;pix[image_addr+2] = b + (pix[image_addr+2]*(255-a) + 255/2) / 255;}}}XFree(xcim);xcim = NULL;
}//生成带鼠标光标的桌面截图
XImage* generateDesktopImageWithCursor(Display* display, Window root, int x, int y, unsigned int width, unsigned int height) 
{XImage* image = XGetImage(display, root, x, y, width, height, AllPlanes, ZPixmap);paint_mouse_pointer(image,display,x,y,width,height);return image;
}int main(int argc, char** argv) 
{//开启桌面连接Display* disp = XOpenDisplay(NULL);if (!disp) {printf("open x11 display error\n");exit(1);}//获取桌面窗口Window root = DefaultRootWindow(disp);XWindowAttributes attrs;XGetWindowAttributes(disp, root, &attrs);//分配每一帧的内存空间char* buffer = (char*) malloc(attrs.width * attrs.height * 4); // RGBA 格式if (!buffer) {printf("malloc buffer error \n");exit(1);}//使用 libvncserver 创建服务器rfbScreenInfoPtr server = rfbGetScreen(&argc, argv, attrs.width, attrs.height, 8, 3, 4);server->desktopName = "share desktop server ";server->frameBuffer = (char*) buffer;server->alwaysShared = true;//绘制客户端移动的光标if(!myCursor) {myCursor = rfbMakeXCursor( 19, 19, (char*) cur, (char*) mask);}server->cursor = myCursor;	//初始化服务端rfbInitServer(server);while (true) {//每100ms刷新一帧画面内容XImage* image = generateDesktopImageWithCursor(disp, root, 0, 0, attrs.width, attrs.height);copyImage(image, buffer, attrs.width, attrs.height, server->paddedWidthInBytes);rfbMarkRectAsModified(server, 0, 0, server->width, server->height);XDestroyImage(image);rfbProcessEvents(server, 100000); }//清理缓存XCloseDisplay(disp);free(buffer);rfbShutdownServer(server, true);return 0;
}

在一台机器上启动上面编写的VNC服务端,然后在另一台机器上使用VNC-client(这里推荐VNC-Viewer)连接对应机器的IP,就可以看到另外一台机器上面的桌面了。 到这里我们就实现了服务端到客户端的单向画面传输了。 下一步我们要接收客户端发送过来的指令,并模拟对应的鼠标键盘操作。

X11模拟鼠标键盘操作

Linux下接收到客户端发送的指令之后,就需要模拟对应的鼠标键盘操作了,下面介绍一下如何使用X11库模拟鼠标键盘操作:

如果系统中没有X11开发库,可以通过下面的指令安装:

sudo apt-get install libx11-dev libxext-dev libxtst-dev libxrender-dev libxmu-dev libxmuu-dev

//引用X11对应的库
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XTest.h> 
#include <unistd.h>         
#include <termios.h>  //获取鼠标的位置
bool GetMousePos(int& x, int& y)
{Display *dpy;Window root;Window ret_root;Window ret_child;int root_x;int root_y;int win_x;int win_y;unsigned int mask;dpy = XOpenDisplay(NULL);root = XDefaultRootWindow(dpy);if(XQueryPointer(dpy, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask)){x = root_x;y = root_y;return true;}return false;
}//设置鼠标的位置
bool SetMousePos(const int& x, const int& y){Display *dpy = XOpenDisplay(0);Window root = XRootWindow(dpy, 0);XWarpPointer(dpy, None, root, 0, 0, 0, 0, x, y);XFlush(dpy); XCloseDisplay(dpy);return true;
}//模拟鼠标左键按下
bool LeftPress(){Display *display = XOpenDisplay(NULL);XTestFakeButtonEvent(display, 1, true, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟鼠标左键抬起
bool LeftRelease(){Display *display = XOpenDisplay(NULL);XTestFakeButtonEvent(display, 1, false, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟鼠标右键按下
bool RightPress(){Display *display = XOpenDisplay(NULL);XTestFakeButtonEvent(display, 3, true, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟鼠标右键抬起
bool RightRelease(){Display *display = XOpenDisplay(NULL);XTestFakeButtonEvent(display, 3, false, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟鼠标中键按下
bool MiddlePress(){Display *display = XOpenDisplay(NULL);XTestFakeButtonEvent(display, 2, true, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟鼠标中键抬起
bool MiddleRelease(){Display *display = XOpenDisplay(NULL);XTestFakeButtonEvent(display, 2, false, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟其它键按下
bool PressKey(int key){Display *display = XOpenDisplay(NULL);XTestFakeKeyEvent(display, XKeysymToKeycode(display, key), true, 0);XFlush(display);XCloseDisplay(display);return true;
}//模拟其它键抬起
bool ReleaseKey(int key){Display *display = XOpenDisplay(NULL);XTestFakeKeyEvent(display, XKeysymToKeycode(display, key), false, 0);XFlush(display);XCloseDisplay(display);return true;
}//获取桌面宽度
bool GetScreenWidth(int& w){Display* d = XOpenDisplay(NULL);Screen* s = DefaultScreenOfDisplay(d);w = s->width;return true;
}//获取桌面高度
bool GetScreenHeight(int& h){Display* d = XOpenDisplay(NULL);Screen* s = DefaultScreenOfDisplay(d);h = s->height;return true;
}//判断是否有组合键按下
bool KeyIsDown(int& key){XkbStateRec r;Display* d = XOpenDisplay(NULL);XkbGetState(d, XkbUseCoreKbd, &r);if((r.mods & 0x01) && key == 16) //Shiftreturn true;if((r.mods & 0x04) && key == 17) //Ctrlreturn true;if((r.mods & 0x08) && key == 18) //Altreturn true;XCloseDisplay(d);return false;
}

libvncserver中处理鼠标键盘消息

定义完成模拟鼠标键盘操作的函数之后,我们就可以在VNCServer中接收鼠标键盘消息然后模拟操作了,对应的处理流程如下所示:


//鼠标消息处理
void mouseevent(int buttonMask, int x, int y, rfbClientPtr cl) 
{	static int oldButtonMask = 0;SetMousePos(x, y);if(buttonMask && !oldButtonMask){if(buttonMask == 1)LeftPress();if(buttonMask == 2)MiddlePress();if(buttonMask == 4)RightPress();if(buttonMask == 8)WheelUp();if(buttonMask == 16)WheelDown();}if(!buttonMask && oldButtonMask){if(oldButtonMask == 1)LeftRelease();if(oldButtonMask == 2)MiddleRelease();if(oldButtonMask == 4)RightRelease();}oldButtonMask = buttonMask;
}//处理按键消息
void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl) 
{if(down){PressKey(key);}else {ReleaseKey(key);}
}int main(int argc, char** argv) 
{//省略重复代码//使用 libvncserver 创建服务器rfbScreenInfoPtr server = rfbGetScreen(&argc, argv, attrs.width, attrs.height, 8, 3, 4);server->desktopName = "share desktop server ";server->frameBuffer = (char*) buffer;server->alwaysShared = true;server->alwaysShared = true;//注册鼠标键盘消息的回调函数server->ptrAddEvent = mouseevent;server->kbdAddEvent = keyevent;//省略重复代码return 0;
}

通过上面的处理我们就可以搭建一个基本的VNC服务端了,该服务端能完成基本的远程控制指令。如果想进一步深度优化,可以参考其它的VNC开源项目比如: TigerVNC、UltraVNC、TightVNC、RealVNC

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/42264.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux开机启动程序添加root权限

Linux添加开机启动程序 Debain、Ubuntu系列Linux开机之后会执行/etc/rc.local文件中的命令&#xff0c;所以&#xff0c;如果是想添加登陆用户所具有权限的操作&#xff0c;可以在文件中exit 0之前添加开机自动执行的脚本命令。或者将执行脚本的权限修改为当前登录用户具有执行…

基于R语言APSIM模型进阶应用与参数优化、批量模拟

随着数字农业和智慧农业的发展&#xff0c;基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物生…

moodle单点登陆

在moodle/login添加sso.php <?phprequire(../config.php); require_once(lib.php);if($_SERVER[REQUEST_METHOD]==GET){$tokenId=$_GET[tokenId]; }else{$tokenId="fail";

C++新经典03--共用体、枚举类型与typedef

共用体 共用体&#xff0c;也叫联合&#xff0c;有时候需要把几种不同类型的变量存放到同一段内存单元&#xff0c;例如&#xff0c;把一个整型变量、一个字符型变量、一个字符数组放在同一个地址开始的内存单元中。这三个变量在内存中占的字节数不同&#xff0c;但它们都从同…

idea 转换为 Maven Project 的方法

选项&#xff1a; Add as Maven Project

通过TightVNC远程访问MacOS

目录 一、下载 TightVNC 下载链接&#xff1a;https://www.tightvnc.com/ 下载后按步骤进行安装&#xff0c;安装完成后安装目录如下&#xff1a; 运行 tvnviewer.exe&#xff0c;输入远程 IP&#xff0c;点击【connect】&#xff1a; 输入密码&#xff0c;点击【OK】后即可远…

Matlab中图例的位置(图例放在图的上方、下方、左方、右方、图外面)等

一、图例默认位置 默认的位置在NorthEast r 10; a 0; b 0; t0:0.1:2.1*pi; xar*cos(t); ybr*sin(t); A1plot(x,y,r,linewidth,4);%圆 hold on axis equal A2plot([0 0],[1 10],b,linewidth,4);%直线 legend([A1,A2],圆形,line)二、通过Location对legend的位置进行改变 变…

企业电子招投标采购系统源码之电子招投标的组成 tbms

​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为…

设计模式-观察者模式(观察者模式的需求衍变过程详解,关于监听的理解)

目录 前言概念你有过这样的问题吗&#xff1f; 详细介绍原理&#xff1a;应用场景&#xff1a; 实现方式&#xff1a;类图代码 问题回答监听&#xff0c;为什么叫监听&#xff0c;具体代码是哪观察者模式的需求衍变过程观察者是为什么是行为型 总结&#xff1a; 前言 在软件设计…

【C++类和对象】类有哪些默认成员函数呢?(下)

文章目录 一、类的6个默认成员函数二、日期类的实现2.1 运算符重载部分2.2 日期之间的运算2.3 整体代码1.Date.h部分2. Date.cpp部分 三. const成员函数四. 取地址及const取地址操作符重载扩展内容 总结 ヾ(๑╹◡╹)&#xff89;" 人总要为过去的懒惰而付出代价ヾ(๑╹◡…

2011年下半年 软件设计师 上午试卷2

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

如何为你的表单添加一个反选功能

原始代码 无标题文档 <form id"form1" name"form1" method"post" action""><table width"50%" border"1" cellspacing"0" cellpadding"0"><tr><td bgcolor"#9999…

数据包如何游走于 Iptables 规则之间?

在前文《Linux路由三大件》中&#xff0c;我们提到了 iptables 可以修改数据包的特征从而影响其路由。这个功能无论是传统场景下的 防火墙&#xff0c;还是云原生场景下的 服务路由&#xff08;k8s service&#xff09;、网络策略(calico network policy) 等都有依赖。 虽然业…

ceph数据分布

ceph的存储是无主结构&#xff0c;数据分布依赖client来计算&#xff0c;有两个条主要路径。 1、数据到PG 2、PG 到OSD 有两个假设&#xff1a; 第一&#xff0c;pg的数量稳定&#xff0c;可以认为保持不变&#xff1b; 第二&#xff0c; OSD的数量可以增减&#xff0c;OSD的…

基于Java的深圳坂田附近闲置物品交易群管理系统

开发技术&#xff1a;java 开发框架&#xff1a;springmvc、spring、mybatis 数据库&#xff1a;mysql 备注&#xff1a;方便大家将手中的二手闲置物品转让给需要的人&#xff0c;例如大家搬家的时候&#xff0c;有不要的&#xff08;冰箱、洗衣机、桌子、椅子&#xff09;等物…

RISC-V 整型通用寄存器介绍

简介 RISC-V64位/32位提供了32个整型通用寄存器&#xff0c;编号是x0~x31&#xff0c;这些整型通用寄存器的宽度与架构位数一致。 浮点数寄存器与整形寄存器一样也提供了32个&#xff1a;f0~f31&#xff0c;位数与架构位数一致。 通用寄存器介绍 零寄存器 x0/zero x0寄存…

力扣:65. 有效数字(Python3)

题目&#xff1a; 有效数字&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 一个 小数 或者 整数&#xff08;可选&#xff09;一个 e 或 E &#xff0c;后面跟着一个 整数 小数&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; &#xff08;…

一、进入sql环境,以及sql的查询、新建、删除、使用

1、进入sql环境 》》》mysql -u root -p 》》》输入密码 2、sql语言的分类 3、注意事项&#xff1a; 4、基础操作&#xff1a; &#xff08;1&#xff09;查询所有数据库&#xff1a; show databases; 运行结果&#xff1a; &#xff08;2&#xff09;创建一个新的数据库&…

CSS练习

CSS练习 工具代码运行结果 工具 HBuilder X 代码 <!DOCTYPE html> <!-- 做一个表格&#xff0c;6行4列实现隔行换色&#xff08;背景色&#xff09;并且第3列文字红色第一个单元格文字大小30px。最后一个单元格文字加粗--> <html><head><meta ch…

初阶C语言-结构体

&#x1f31e; “少年有梦不至于心动&#xff0c;更要付诸行动。” 今天我们一起学习一下结构体的相关内容&#xff01; 结构体 &#x1f388;1.结构体的声明1.1结构的基础知识1.2结构的声明1.3结构成员的类型1.4结构体变量的定义和初始化 &#x1f388;2.结构体成员的访问2.1结…