VNC(Virtual Network Computing)是一种使用远程帧缓冲协议(RFB)的屏幕分享及远程操作软件。VNC的服务端可以通过RFP协议将桌面视频帧导出到互联网上, 导出到互联网上的视频帧可以用VNC Client进行访问。如果VNC Server开启了websockets, 除了VNC Viewer外 我们还可以通过浏览器内的VNC查看器(如noVNC)来进行连接访问。
VNC总体遵循C/S架构。所以可以归结以下几点:
libvncserver是一个开源的VNC服务器端库,可以集成到C/C++应用程序中,实现允许远程用户通过VNC客户端来显示和控制本地计算机的功能。libvncserver提供了一些功能强大的API,可以方便地与其他应用程序集成。它支持多个操作系统平台,包括Linux、Windows、Mac OS X和FreeBSD等,提供了多种VNC协议的实现。 使用libvncserver可以让开发人员方便地创建自己的VNC服务器,可用于远程桌面、远程支持、监控等多种应用场景。同时,libvncserver还提供了一些高级功能,例如SFTP和SSH隧道,支持SSL加密 安全性较高。
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.
项目地址:
https://github.com/LibVNC/libvncserver
# 下载代码并构建
git clone https://github.com/LibVNC/libvncserver
cd libvncserver
mkdir build
cd build
cmake ..
cmake --build .
构建好的libvncserver参见后续的CSDN下载资源
//引用libvncserver 用于启用服务端
#include
#include
#include
#include
//引用X11库用于抓取桌面,模拟鼠标键盘操作
#include
#include
#include
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,就可以看到另外一台机器上面的桌面了。 到这里我们就实现了服务端到客户端的单向画面传输了。 下一步我们要接收客户端发送过来的指令,并模拟对应的鼠标键盘操作。
Linux下接收到客户端发送的指令之后,就需要模拟对应的鼠标键盘操作了,下面介绍一下如何使用X11库模拟鼠标键盘操作:
如果系统中没有X11开发库,可以通过下面的指令安装:
sudo apt-get install libx11-dev libxext-dev libxtst-dev libxrender-dev libxmu-dev libxmuu-dev
//引用X11对应的库
#include
#include
#include
#include
#include
#include
#include
//获取鼠标的位置
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) //Shift
return true;
if((r.mods & 0x04) && key == 17) //Ctrl
return true;
if((r.mods & 0x08) && key == 18) //Alt
return true;
XCloseDisplay(d);
return false;
}
定义完成模拟鼠标键盘操作的函数之后,我们就可以在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