Use la biblioteca libvncserver para construir rápidamente un servidor VNC

¿Qué es VNC?

VNC (Virtual Network Computing) es un software de operación remota y uso compartido de pantalla que utiliza Remote Frame Buffer (RFB). El servidor VNC puede exportar cuadros de video de escritorio a Internet a través del protocolo RFP, y se puede acceder a los cuadros de video exportados a Internet con el cliente VNC. Si el servidor VNC habilita websockets, además del visor VNC, también podemos conectarnos y acceder a través del visor VNC (como noVNC) en el navegador.

VNC generalmente sigue la arquitectura C/S. Por lo que se puede resumir en los siguientes puntos:

  • Servidor VNC, comparta la pantalla de la máquina controlada, controlada pasivamente por VNC Client
  • VNC Client, a veces llamado VNC Viewer, puede ver la pantalla controlada por el servidor y operar el servidor de forma remota
  • El protocolo VNC, para ser precisos, es el protocolo RFB. Un escritorio remoto que no adopta este protocolo no se llama VNC. El propósito de este protocolo es muy simple. sistema de coordenadas x, y) y mensajes de eventos (mensajes de mouse, teclado mensajes) se transmiten desde el servidor al cliente.

libvncserver es una biblioteca de servidor VNC de código abierto que se puede integrar en aplicaciones C/C++ para permitir que los usuarios remotos muestren y controlen la computadora local a través del cliente VNC. libvncserver proporciona algunas API potentes que se pueden integrar fácilmente con otras aplicaciones. Admite múltiples plataformas de sistemas operativos, incluidos Linux, Windows, Mac OS X y FreeBSD, etc., y proporciona una variedad de implementaciones de protocolos VNC. El uso de libvncserver permite a los desarrolladores crear fácilmente su propio servidor VNC, que se puede usar en varios escenarios de aplicaciones, como escritorio remoto, soporte remoto y monitoreo. Al mismo tiempo, libvncserver también proporciona algunas funciones avanzadas, como túneles SFTP y SSH, y admite cifrado SSL con alta seguridad.

Ventajas y desventajas de libvncserver

libvncserver es una poderosa biblioteca del lado del servidor VNC, que tiene las siguientes ventajas:
1. Código abierto y gratuito: libvncserver usa la licencia GPL para código abierto, que se puede usar de forma gratuita y se puede modificar y distribuir.
2. Gran portabilidad: libvncserver admite múltiples plataformas de sistemas operativos, incluidos Linux, Windows, Mac OS X y FreeBSD, etc., y tiene una buena portabilidad.
3. Buena flexibilidad: libvncserver proporciona algunas API potentes, que se pueden integrar fácilmente con otras aplicaciones y también admite la implementación de múltiples protocolos VNC.
4. Funciones avanzadas completas: libvncserver proporciona algunas funciones avanzadas, como túneles SFTP y SSH, cifrado SSL, etc., que pueden mejorar la seguridad de VNC.

Sin embargo, libvncserver también tiene las siguientes desventajas:
1. Relativamente complicado de usar: libvncserver es una biblioteca programable, que es relativamente complicada de usar y requiere que los desarrolladores tengan experiencia y habilidades de programación relevantes.
2. La comunidad es relativamente pequeña: en comparación con otras bibliotecas del lado del servidor VNC similares, la comunidad de libvncserver es relativamente pequeña, lo que puede ser difícil de aprender para los principiantes.
3. No apto para implementación a gran escala: dado que libvncserver es una biblioteca del lado del servidor de VNC, no se puede usar directamente para la implementación de VNC a gran escala, sino que debe implementarse en combinación con otras herramientas o aplicaciones.
4. La mesa de trabajo del control remoto no admite copiar y pegar texto Unicode, y no puede transmitir ningún código de conjunto de caracteres que no sea el conjunto de caracteres Latin-1.
5. Una cosa a tener en cuenta es que libvncserver, un marco VNC de código abierto, todavía tiene muchos errores.

compilar servidor libvnc

Dirección del proyecto:
https://github.com/LibVNC/libvncserver

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

Para el servidor libvnc integrado, consulte los recursos de descarga de CSDN subsiguientes

Use libvncserver para compilar 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;
}

Inicie el servidor VNC escrito anteriormente en una máquina y luego use el cliente VNC (aquí se recomienda VNC-Viewer) para conectarse a la IP de la máquina correspondiente en otra máquina, y podrá ver el escritorio en la otra máquina. Hasta ahora hemos realizado la transmisión de pantalla unidireccional desde el servidor al cliente. En el siguiente paso, necesitamos recibir las instrucciones enviadas por el cliente y simular las operaciones correspondientes con el mouse y el teclado.

X11 simula las operaciones del mouse y el teclado

Después de recibir las instrucciones enviadas por el cliente bajo Linux, es necesario simular las operaciones correspondientes con el mouse y el teclado. A continuación se describe cómo usar la biblioteca X11 para simular las operaciones con el mouse y el teclado:

Si no hay una biblioteca de desarrollo X11 en el sistema, puede instalarla mediante el siguiente comando:

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) //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;
}

Manejar mensajes de mouse y teclado en libvncserver

Después de definir las funciones para simular las operaciones del mouse y el teclado, podemos recibir mensajes del mouse y del teclado en VNCServer y luego simular las operaciones. El flujo de procesamiento correspondiente es el siguiente:


//鼠标消息处理
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;
}

A través del procesamiento anterior, podemos construir un servidor VNC básico, que puede completar comandos básicos de control remoto. Si desea optimizar aún más, puede consultar otros proyectos de código abierto de VNC como: TigerVNC, UltraVNC, TightVNC, RealVNC

Supongo que te gusta

Origin blog.csdn.net/yang1fei2/article/details/132371918
Recomendado
Clasificación