libvncserver 라이브러리를 사용하여 VNC 서버를 빠르게 구축

VNC란?

VNC(Virtual Network Computing)는 원격 프레임 버퍼(RFB)를 사용하는 화면 공유 및 원격 운영 소프트웨어입니다. VNC 서버는 RFP 프로토콜을 통해 데스크톱 비디오 프레임을 인터넷으로 내보낼 수 있으며 인터넷으로 내보낸 비디오 프레임은 VNC 클라이언트로 액세스할 수 있습니다. VNC 서버가 웹 소켓을 활성화하면 VNC 뷰어 외에도 브라우저에서 VNC 뷰어(예: noVNC)를 통해 연결하고 액세스할 수 있습니다.

VNC는 일반적으로 C/S 아키텍처를 따릅니다. 따라서 다음과 같이 요약할 수 있습니다.

  • VNC 서버, VNC 클라이언트에 의해 수동적으로 제어되는 제어되는 기계의 화면 공유
  • VNC Viewer라고도 불리는 VNC Client는 서버가 제어하는 ​​화면을 보면서 원격으로 서버를 운영할 수 있습니다.
  • 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. 리모콘의 게시판은 유니코드 텍스트 복사 및 붙여넣기를 지원하지 않으며 라틴-1 문자 집합 이외의 문자 집합 코드를 전송할 수 없습니다.
5. 한 가지 주의할 점은 오픈 소스 VNC 프레임워크인 libvncserver에는 여전히 많은 버그가 있다는 것입니다.

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

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 서버를 구축할 수 있습니다. 더 심층적인 최적화를 원하면 TigerVNC, UltraVNC, TightVNC, RealVNC와 같은 다른 VNC 오픈 소스 프로젝트를 참조할 수 있습니다.

추천

출처blog.csdn.net/yang1fei2/article/details/132371918