Use a biblioteca libvncserver para construir rapidamente um servidor VNC

O que é VNC

VNC (Virtual Network Computing) é um software de compartilhamento de tela e operação remota usando Remote Frame Buffer (RFB). O servidor VNC pode exportar quadros de vídeo de desktop para a Internet por meio do protocolo RFP, e os quadros de vídeo exportados para a Internet podem ser acessados ​​com o VNC Client. Se o servidor VNC habilitar websockets, além do VNC Viewer, também podemos conectar e acessar através do visualizador VNC (como noVNC) no navegador.

O VNC geralmente segue a arquitetura C/S. Portanto, pode ser resumido nos seguintes pontos:

  • VNC Server, compartilhe a tela da máquina controlada, controlada passivamente pelo VNC Client
  • O VNC Client, às vezes chamado de VNC Viewer, pode observar a tela controlada pelo servidor e operar remotamente o servidor
  • O protocolo VNC, para ser mais preciso, é o protocolo RFB. Uma área de trabalho remota que não adota esse protocolo não é chamada de VNC. A finalidade desse protocolo é muito simples. sistema de coordenadas x, y) e mensagens de eventos (mensagens do mouse, teclado mensagens) são transmitidas do servidor para o cliente.

libvncserver é uma biblioteca de servidor VNC de código aberto que pode ser integrada a aplicativos C/C++ para permitir que usuários remotos exibam e controlem o computador local por meio do cliente VNC. O libvncserver fornece algumas APIs poderosas que podem ser facilmente integradas a outros aplicativos. Ele suporta várias plataformas de sistema operacional, incluindo Linux, Windows, Mac OS X e FreeBSD, etc., e fornece uma variedade de implementações de protocolo VNC. O uso do libvncserver permite que os desenvolvedores criem facilmente seu próprio servidor VNC, que pode ser usado em vários cenários de aplicativos, como área de trabalho remota, suporte remoto e monitoramento. Ao mesmo tempo, o libvncserver também fornece algumas funções avançadas, como túneis SFTP e SSH, e oferece suporte à criptografia SSL com alta segurança.

Vantagens e desvantagens do libvncserver

libvncserver é uma poderosa biblioteca VNC do lado do servidor, que tem as seguintes vantagens:
1. Código aberto e gratuito: libvncserver usa a licença GPL para código aberto, que pode ser usado gratuitamente e pode ser modificado e distribuído.
2. Forte portabilidade: libvncserver suporta várias plataformas de sistema operacional, incluindo Linux, Windows, Mac OS X e FreeBSD, etc., e tem boa portabilidade.
3. Boa flexibilidade: libvncserver fornece algumas APIs poderosas, que podem ser facilmente integradas a outros aplicativos e também suporta a implementação de vários protocolos VNC.
4. Funções avançadas completas: libvncserver fornece algumas funções avançadas, como túneis SFTP e SSH, criptografia SSL, etc., que podem melhorar a segurança do VNC.

No entanto, libvncserver também tem as seguintes desvantagens:
1. Relativamente complicado de usar: libvncserver é uma biblioteca programável, que é relativamente complicada de usar e requer que os desenvolvedores tenham experiência e habilidades relevantes em programação.
2. A comunidade é relativamente pequena: em comparação com outras bibliotecas semelhantes do lado do servidor VNC, a comunidade de libvncserver é relativamente pequena, o que pode ser difícil para iniciantes aprenderem.
3. Não é adequado para implantação em larga escala: como libvncserver é uma biblioteca VNC do lado do servidor, ela não pode ser usada diretamente para implantação VNC em larga escala, mas precisa ser implementada em combinação com outras ferramentas ou aplicativos.
4. A área de transferência do controle remoto não suporta copiar e colar texto Unicode e não pode transmitir nenhum código de conjunto de caracteres diferente do conjunto de caracteres Latin-1.
5. Uma coisa a observar é que libvncserver, um framework VNC de código aberto, ainda tem muitos bugs.

Compilar libvncserver

Endereço do projeto:
https://github.com/LibVNC/libvncserver

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

Para o libvncserver construído, consulte os recursos de download CSDN subsequentes

Use libvncserver para construir o 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 o servidor VNC escrito acima em uma máquina e, em seguida, use o cliente VNC (o VNC-Viewer é recomendado aqui) para conectar-se ao IP da máquina correspondente em outra máquina e você poderá ver a área de trabalho na outra máquina. Até agora, realizamos a transmissão de tela unidirecional do servidor para o cliente. Na próxima etapa, precisamos receber as instruções enviadas pelo cliente e simular as operações de mouse e teclado correspondentes.

X11 simula operações de mouse e teclado

Após receber as instruções enviadas pelo cliente no Linux, é necessário simular as operações de mouse e teclado correspondentes. A seguir é descrito como utilizar a biblioteca X11 para simular as operações de mouse e teclado:

Se não houver biblioteca de desenvolvimento X11 no sistema, você pode instalá-la através do seguinte 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;
}

Lidar com mensagens de mouse e teclado no libvncserver

Após definir as funções para simular operações de mouse e teclado, podemos receber mensagens de mouse e teclado no VNCServer e então simular operações. O fluxo de processamento correspondente é o seguinte:


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

Através do processamento acima, podemos construir um servidor VNC básico, que pode completar comandos básicos de controle remoto. Se você deseja uma otimização mais aprofundada, pode consultar outros projetos de código aberto VNC, como: TigerVNC, UltraVNC, TightVNC, RealVNC

Acho que você gosta

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