首页
社区
课程
招聘
远程控制之原理和实战
发表于: 2023-6-12 11:11 24318

远程控制之原理和实战

2023-6-12 11:11
24318

按理来说,本人不该发表此类专业文章,鄙人零星碎片化的开发经历,让本人斗胆向诸位网友,在远控方面做一点演示说明,谈论一点自己的认识。

程序工程代码地址:点击此处下载

程序分为两个部分,控制端和被控端,他们之间通过网络来连接和交互,其工作过程大体如下:

被控端每隔20毫秒截屏,图像经过压缩,通过tcp网络传输给控制端,控制端对接收到的视频帧实时刷新显示;被控端实时接收控制端的对屏幕的操作消息(主要是键盘的按键、鼠标的位置和动作等),并在本端模拟这些键盘鼠标操作。

控制端代码主要在RemoteControlRecver.cpp和RemoteControlListener.cpp中,被控端代码主要在RemoteControlProc.cpp和RemoteControl.cpp中。

程序是自己对远控的一点探索和demo演示,实现过程吻合本文思路,虽然离商用有很远距离,但从实际的使用效果看,已经具备了远控的基础功能和效果。

经典的远控,比如国外的TeamViewer、国内近几年出现的ToDesk,功能强大且精密复杂,但这并不说明它的高不可攀(远控软件,除非算法上的突破,无论理论和工程技术,恐怕都无法为设计开发者赢得博士学位),它的原理无非就是:将被控端的屏幕实时复制到主控端并保持刷新,主控端就像使用本地的屏幕一样,使用菜单、键盘输入、鼠标点击等视觉交互,主控端在该屏幕上的键盘和鼠标操作,通过网络传输给被控端,转化为被控端相应的键盘和鼠标操作

当然这是极为简略的描述,实践中要考虑很多其他因素,比如对网络流量的考虑:如果每一帧画面都是截屏数据,要保证动画的连贯逼真,每秒至少要传输24帧以上的画面,每一帧画面如果采用24位真彩色、屏幕分辨率假定是最常用的1920x1080,此时,未压缩前的大小是6220800字节,压缩后一般最少也有150-200kb左右(压缩率跟画面的像素有关,一般来讲,常见的图像压缩算法,越是像素排列无规则、相对速度运动越快、变化率越大的像素值,压缩比越低),这时每秒的带宽压力要达到80M/b(10MB)以上,有些网络环境下,这是一个恐怖的数字,实际环境可能达不到,因此,如何压缩减少视频传输流量,画面的高清显示、提高反应速度和控制的丝滑程度,是此类软件的核心技术之一。

一般来说,要实现此类软件的敏捷开发,最快捷的方式是使用第三方开发包,比如大名鼎鼎的ffmpeg,此开发包中有多种方案可以实现高效的视频传输,如h264、h265协议接口,此类接口可以将传输数据减少1到2个数量级,实际测试数据流量在每秒几百kb甚至100kb/s以下,已经可以满足实际需要,但是,这样的网络流量或者实际显示画面的综合效果并不够优秀,从测试中发现,微软自带的远程控制软件mstsc.exe,在100kb以内的网速下,画面显示依然清晰、控制依然保持流畅,这就不是第三方开发包可以轻易达到的。另外此类第三方接口中没有键盘鼠标消息的处理,很多定制化需求不能被满足,还需要对开发包进一步定制和开发。

如果对第三方开发包不太满意,那就只有自己动手手撸代码了。

魔鬼隐藏在细节中。从经验上来说,就算很简单的理论描述,工程实践中也会有很多细节需要填坑夯实,理学在前挖坑,工程学在后填坑,这也许就是工程学(比如软件工程)存在的意义吧。

下面就是对思路的细节描述。

主控端的具体代码逻辑如下:
每一个被控客户端的远程连接,控制端需要创建两个线程,一个负责与被控端网络通信,一个负责窗口显示刷新和窗口消息。
网络通信线程有两个执行节点,一个节点是执行recv函数,接收被控端的截屏数据;另一个执行节点是执行send函数,发送显示窗口的键盘鼠标消息。窗口显示和消息处理线程主要是实时刷新和显示被控端的截屏,切入点是依靠窗口的WM_PAINT消息,每当网络通信线程接收到一帧截屏后会调用InvalidateRect(参数是显示窗口的HWND句柄),此时窗口程序会执行刷新过程。此线程另外一个功能是,捕获主控端的在显示窗口中的键盘鼠标消息,并存放在全局变量中,这样网络通信线程就可以读取和发送给被控端,被控端模拟点击和键盘输入,将收到的键盘鼠标消息转换为本地的键盘鼠标操作。

另外需要注意的是,两个线程中,资源申请和释放、连接控制等主要是在显示刷新窗口线程中完成的,在主控和被控之间因各种原因断开连接时,要保证所有的资源都有效释放。

两个线程共用的客户端结构体如下:

被控端程序比较简单,主要是在一个循环中,获取截屏数据,发送给控制端,然后接收控制端的键盘鼠标消息,并将这些键盘鼠标消息转换为本地的键盘鼠标消息。

代码中的api采用了函数指针的调用方式,去掉前面的lp前缀就可以理解了。主要的功能模块如下:

1. 被控端截屏发送给控制端。从网上的资料来看,截屏功能的实现方法如下:

上面有几点需要啰嗦几句:
(1) windows上gdi二维图像api都是用DC句柄来实现的。测试发现,GetDC(0)等同于CreateDC("display",0, 0, 0),也等同于GetDC(GetDesktopWindow()),这几种用法都是用来获取桌面的DC。

(2) CreateCompatibleBitmap函数中的HDC要使用桌面的HDC,而不能是新创建的内存hdcmem,这是一个隐蔽的知识盲点,microsoft的解释如下:
图片描述
大意是,CreateCompatibleBitmap产生的hBitmap位图中的位数和颜色跟使用的hdc参数中的保持一致,而使用CreateCompatibleDC函数创建的HDC默认都是2位的位图。

(3) GetDIBits函数有文档中未指明的知识盲点。比如lpbi参数指向的BITMAPINFO,在8位256色颜色模式下,要给调色板留下空间,调色板一般需要另外的1024字节大小的空间,否则调用此api会发生内存越界异常。另外,此函数如果不知道如何填写BITMAPINFO位图参数,可以在第一次调用时,lpData参数为空,调用后,函数会自动填充BITMAPINFO结构的参数,然后第二次调用此函数,即可得到相应参数的位图数据。

BITMAPINFO结构体定义如下:

该函数的官方文档如下:
图片描述
注意这里的描述,如果lpvBits参数有效,那么前6个参数必须初始化,并且扫描线的数值必须是Dword对齐。前6个参数指的是biSizeImage之前的6个参数,biSizeImage的计算比较复杂,不论位图的颜色深度是多少位,扫描线长度必须要4字节对齐。测试中还发现扫描线的行数并不需要dword对齐。

文档中说,函数调用时hbitmap参数不能被SelectObject选中,测试中发现,hbitmap即使已经被调用了SelectObject函数被选中,调用时也可以成功。

截图支持8位、16位、24位、32位颜色值,测试程序使用的16位色。从视觉效果上,16位色跟24位,观看起来区别很小,特别是24位色(32位色相对于24位色只是增加了alpha值透明度),已经超过人的眼睛对颜色的识别程度的上限,再高的颜色值已经没有意义。由于传输的是像素值,而不是跟jpeg或者其他视频流算法中使用的近似压缩值(或者近似压缩块),所以画面的清晰度是很好的,这也是相比较于ffmpeg等第三方开发包使用h264、h265压缩视频流的优势。

另外,建议查看文档的英文版,中文版好多翻译不准确或者非常不严谨,长期依赖中文翻译,会导致开发水平得不到提高。

2. 数据压缩传输。采用zip压缩,压缩参数设置为最大化压缩,压缩比估值大概是6-20倍。如果是8位的位图帧,分辨率1920x1080,一帧压缩后大约是60-120KB;如果是16位,压缩后大约为100-300KB;32位的话,大约是150-600KB。当然这样的压缩比仍然难以满足实际需求,此文在第三个话题中会详细介绍如何减少视频流量,最终可以将网络流量降低到平均100KB/S。
3. 截屏帧的显示刷新。主要代码如下:

代码中,lpClientBitmap指向接收到的内存中的bmp文件,调用CreateDIBSection函数是为了创建一个类似于此bmp文件格式和参数的hbitmap句柄后,然后将bmp文件的像素值拷贝到句柄指向的像素内存地址中,并使用StretchBlt将像素值显示在当前窗口中的客户区中,因为客户端和服务器的窗口大小可能不一样,所以使用StretchBlt实现缩放而不是BitBlt函数。
同时要注意,CreateDIBSection函数第一个参数为0,0即相当于GetDC(0),代表桌面窗口的HDC,这点在官方文档中并未说明,但是可以直接使用。

4. 键盘鼠标消息。控制消息的收发和视频帧是顺序关系,而不是异步关系,被控端每发送一帧截屏后接收控制端的键盘鼠标消息,并将此消息模拟为本机的键盘鼠标操作;与此同时,控制端每接收一帧截屏后发送键盘鼠标消息。由于网络通信使用阻塞模式,此时一定要保证,被控端的先发送和后接收、控制端先接受再发送,主控和被控任何的执行分支都要分别执行这两对代码节点,否则会造成网络收发的死锁。另外,控制端如果发现,键盘鼠标的位置和动作跟上次的值相同,就会向被控端发送一个REMOTE_DUMMY_PACKET数据包,告诉被控端,控制端没有控制消息给你,你可以适当的增加截屏延时(一次增加10毫秒),以便减少网络消耗。主要的键盘鼠标结构体如下:

该结构由几个全局变量存放:键盘按键值,鼠标左键、中键、右键是否有点击动作,鼠标滚轮的滚动距离,鼠标的坐标位置等。控制端的窗口程序监听鼠标键盘消息,将这些消息填充为上述结构体,并由通信线程发送给被控端。

被控端通过keybd_event和mouse_event将收到的控制信息转换为本机的键盘鼠标消息。例子代码如下:

测试中发现,实际的网速有可能比想象中偏低,比如很多服务器网络带宽只有几百kb/s,上述依靠传输截屏帧的显示方式,按照40ms一帧的延时,8位位图数据帧,经过zip压缩后,网络流量可以平均减少10倍左右也就是大约1~2MB/s左右,依然无法满足实际需求,经过考虑,采用了如下几种改善措施:

此处第一个StretchBlt函数的作用是,将主控端显示窗口大小转化为适合被控端宽度高度的大小,原因是,被控端无法得知主控端显示窗口的大小,被控发送的像素值位置是对本窗口的偏移值,如果两边窗口大小不一致,那么被控端的像素位置值就失去了意义。此时通过StretchBlt函数转换后,就可以将像素值直接写入转换后的hbitmap,并再次调用StretchBlt函数,将客户端的窗口大小调整为主控端显示窗口的大小。此处也用到了SetDIBits和GetDIBits函数,该函数上边已经讲过了,功能比较强大,但是使用起来比较复杂。此处有个内存越界的bug,也就是像素的偏移值会大于整个截屏的像素个数总数,会导致WriteLog那一行的执行,原因应该是,两边的窗口大小不一致,若被控的窗口比较大,而主控端窗口比较小,主控端的缓冲区是按照主控窗口大小分配的,转换坐标后,有可能发生内存溢出。

像素值比对函数如下:

ScreenFrameChecker函数分8位、16位、24位、32位4种颜色深度值,跟上一帧比对截屏像素值,返回发生变化的像素值个数。为了加快速度,也可以按照每种规则,比如每次8个字节比对,或者使用simd指令优化,每次比对16字节。

typedef struct {
    SOCKET                  hSockClient;        //被控客户端socket
    sockaddr_in             stAddrClient;       //被控地址
    HWND                    hwndWindow;         //窗口线程的窗口句柄,据此可以保存和找到该线程,并交互鼠标键盘消息
    char* lpClientBitmap;                       //被控的屏幕像素地址
    char* dibits;                               //被控像素处理内存缓冲
    int                     bufLimit;           //像素地址块分配大小
    int                     lpbmpDataSize;//像素实际大小
    int                     dataType;   //像素块的类型,有两种,一种是截屏,一种是屏幕刷新值
    UNIQUECLIENTSYMBOL      unique;     //被控的信息
    STREMOTECONTROLPARAMS   param;      //被控的屏幕宽度高度位数等显示信息
}REMOTE_CONTROL_PARAM, * LPREMOTE_CONTROL_PARAM;
typedef struct {
    SOCKET                  hSockClient;        //被控客户端socket
    sockaddr_in             stAddrClient;       //被控地址
    HWND                    hwndWindow;         //窗口线程的窗口句柄,据此可以保存和找到该线程,并交互鼠标键盘消息
    char* lpClientBitmap;                       //被控的屏幕像素地址
    char* dibits;                               //被控像素处理内存缓冲
    int                     bufLimit;           //像素地址块分配大小
    int                     lpbmpDataSize;//像素实际大小
    int                     dataType;   //像素块的类型,有两种,一种是截屏,一种是屏幕刷新值
    UNIQUECLIENTSYMBOL      unique;     //被控的信息
    STREMOTECONTROLPARAMS   param;      //被控的屏幕宽度高度位数等显示信息
}REMOTE_CONTROL_PARAM, * LPREMOTE_CONTROL_PARAM;
int GetScreenFrame(int ibits, char* szScreenDCName, int left, int top, int ScrnResolutionX, int ScrnResolutionY, char* lpBuf, char** lppixel, int* pixelsize) {
 
    int iRes = 0;
 
    HWND hwnd = lpGetDesktopWindow();
 
    HDC hdc = lpGetDC(hwnd);
 
    //HDC hdc = lpCreateDCA(szScreenDCName, 0, 0, 0);
 
    //HDC hdc = lpGetDC(0);
    if (hdc == 0)
    {
        writeLog("GetScreenFrame lpCreateDCA error:%d\r\n", GetLastError());
        return FALSE;
    }
 
    HDC hdcmem = lpCreateCompatibleDC(hdc);
 
    HBITMAP hbitmap = lpCreateCompatibleBitmap(hdc, ScrnResolutionX, ScrnResolutionY);
 
    lpSelectObject(hdcmem, hbitmap);
 
    iRes = lpBitBlt(hdcmem, 0, 0, ScrnResolutionX, ScrnResolutionY, hdc, 0, 0, SRCCOPY);
 
    if (hbitmap == 0)
    {
        lpReleaseDC(0, hdc);
        lpDeleteDC(hdcmem);
        lpDeleteObject(hbitmap);
 
        writeLog("GetScreenFrame lpCreateCompatibleBitmap error:%d\r\n", GetLastError());
        return FALSE;
    }
 
    int wbitcount = 0;
    if (ibits <= 1) {
        wbitcount = 1;
    }
    else if (ibits <= 4) {
        wbitcount = 4;
    }
    else if (ibits <= 8) {
        wbitcount = 8;
    }
    else if (ibits <= 16) {
        wbitcount = 16;
    }
    else if (ibits <= 24) {
        wbitcount = 24;
    }
    else {
        wbitcount = 32;
    }
 
    DWORD dwpalettesize = 0;
    if (wbitcount <= 8)
    {
        dwpalettesize = (1 << wbitcount) * sizeof(RGBQUAD);
    }
 
    DWORD dwbmbitssize = ((ScrnResolutionX * wbitcount + 31) / 32) * 4 * ScrnResolutionY;
 
    DWORD dwBufSize = dwbmbitssize + dwpalettesize + sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
 
    LPBITMAPFILEHEADER bmfhdr = (LPBITMAPFILEHEADER)lpBuf;
    bmfhdr->bfType = 0x4d42;
    bmfhdr->bfSize = dwBufSize;
    bmfhdr->bfReserved1 = 0;
    bmfhdr->bfReserved2 = 0;
    bmfhdr->bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwpalettesize;
 
    LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)(lpBuf + sizeof(BITMAPFILEHEADER));
    lpbi->biSize = sizeof(BITMAPINFOHEADER);
    lpbi->biWidth = ScrnResolutionX;
    lpbi->biHeight = ScrnResolutionY;
    lpbi->biPlanes = 1;
    lpbi->biBitCount = wbitcount;
    lpbi->biCompression = BI_RGB;
    lpbi->biSizeImage = 0;
    lpbi->biXPelsPerMeter = 0;
    lpbi->biYPelsPerMeter = 0;
    lpbi->biClrUsed = 0;
    lpbi->biClrImportant = 0;
 
    char* lpData = lpBuf + sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + dwpalettesize;
 
    iRes = lpGetDIBits(hdcmem, hbitmap, 0, ScrnResolutionY, lpData, (BITMAPINFO*)lpbi, DIB_RGB_COLORS);
 
    lpDeleteDC(hdcmem);
    lpDeleteObject(hbitmap);
    lpReleaseDC(0, hdc);
 
    if (iRes == 0)
    {
        writeLog("lpGetDIBits error:%d\r\n", GetLastError());
        return FALSE;
    }
 
    *lppixel = lpData;
    *pixelsize = dwbmbitssize;
    return dwBufSize;
}
int GetScreenFrame(int ibits, char* szScreenDCName, int left, int top, int ScrnResolutionX, int ScrnResolutionY, char* lpBuf, char** lppixel, int* pixelsize) {
 
    int iRes = 0;
 
    HWND hwnd = lpGetDesktopWindow();
 
    HDC hdc = lpGetDC(hwnd);
 
    //HDC hdc = lpCreateDCA(szScreenDCName, 0, 0, 0);
 
    //HDC hdc = lpGetDC(0);
    if (hdc == 0)
    {
        writeLog("GetScreenFrame lpCreateDCA error:%d\r\n", GetLastError());
        return FALSE;
    }
 
    HDC hdcmem = lpCreateCompatibleDC(hdc);
 
    HBITMAP hbitmap = lpCreateCompatibleBitmap(hdc, ScrnResolutionX, ScrnResolutionY);
 
    lpSelectObject(hdcmem, hbitmap);
 
    iRes = lpBitBlt(hdcmem, 0, 0, ScrnResolutionX, ScrnResolutionY, hdc, 0, 0, SRCCOPY);
 
    if (hbitmap == 0)
    {
        lpReleaseDC(0, hdc);
        lpDeleteDC(hdcmem);
        lpDeleteObject(hbitmap);
 
        writeLog("GetScreenFrame lpCreateCompatibleBitmap error:%d\r\n", GetLastError());
        return FALSE;
    }
 
    int wbitcount = 0;
    if (ibits <= 1) {
        wbitcount = 1;
    }
    else if (ibits <= 4) {
        wbitcount = 4;
    }
    else if (ibits <= 8) {
        wbitcount = 8;
    }
    else if (ibits <= 16) {
        wbitcount = 16;
    }
    else if (ibits <= 24) {
        wbitcount = 24;
    }
    else {
        wbitcount = 32;
    }
 
    DWORD dwpalettesize = 0;
    if (wbitcount <= 8)
    {
        dwpalettesize = (1 << wbitcount) * sizeof(RGBQUAD);
    }
 
    DWORD dwbmbitssize = ((ScrnResolutionX * wbitcount + 31) / 32) * 4 * ScrnResolutionY;
 
    DWORD dwBufSize = dwbmbitssize + dwpalettesize + sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
 
    LPBITMAPFILEHEADER bmfhdr = (LPBITMAPFILEHEADER)lpBuf;
    bmfhdr->bfType = 0x4d42;
    bmfhdr->bfSize = dwBufSize;
    bmfhdr->bfReserved1 = 0;
    bmfhdr->bfReserved2 = 0;
    bmfhdr->bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwpalettesize;
 
    LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)(lpBuf + sizeof(BITMAPFILEHEADER));
    lpbi->biSize = sizeof(BITMAPINFOHEADER);
    lpbi->biWidth = ScrnResolutionX;
    lpbi->biHeight = ScrnResolutionY;
    lpbi->biPlanes = 1;
    lpbi->biBitCount = wbitcount;
    lpbi->biCompression = BI_RGB;
    lpbi->biSizeImage = 0;
    lpbi->biXPelsPerMeter = 0;
    lpbi->biYPelsPerMeter = 0;
    lpbi->biClrUsed = 0;
    lpbi->biClrImportant = 0;
 
    char* lpData = lpBuf + sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + dwpalettesize;
 
    iRes = lpGetDIBits(hdcmem, hbitmap, 0, ScrnResolutionY, lpData, (BITMAPINFO*)lpbi, DIB_RGB_COLORS);
 
    lpDeleteDC(hdcmem);
    lpDeleteObject(hbitmap);
    lpReleaseDC(0, hdc);
 
    if (iRes == 0)
    {
        writeLog("lpGetDIBits error:%d\r\n", GetLastError());
        return FALSE;
    }
 
    *lppixel = lpData;
    *pixelsize = dwbmbitssize;
    return dwBufSize;
}
typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
 
typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
 
typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
else if (mapit->second->dataType == REMOTE_CLIENT_SCREEN)
{
    char* lpClientBitmap = mapit->second->lpClientBitmap;
    HDC  hdcScr = CreateDCA("DISPLAY", NULL, NULL, NULL);
    HDC hdcSource = CreateCompatibleDC(hdcScr);
    LPBITMAPFILEHEADER pBMFH = (LPBITMAPFILEHEADER)lpClientBitmap;
    void* pDibts = (void*)(lpClientBitmap + pBMFH->bfOffBits);
    LPBITMAPINFOHEADER pBMIH = (LPBITMAPINFOHEADER)(lpClientBitmap + sizeof(BITMAPFILEHEADER));
    DWORD dwDibtsSize = ((pBMIH->biWidth * pBMIH->biBitCount + 31) / 32) * 4 * pBMIH->biHeight;
    char* pRemoteSrnData = 0;
    HBITMAP hRemoteBM = CreateDIBSection(0, (LPBITMAPINFO)pBMIH, DIB_RGB_COLORS, (void**)&pRemoteSrnData, 0, 0);
    if (hRemoteBM)
    {
        memcpy(pRemoteSrnData, pDibts, dwDibtsSize);
        HBITMAP hSrcBM = (HBITMAP)SelectObject(hdcSource, hRemoteBM);
 
        int iX = pBMIH->biWidth;
        int iY = pBMIH->biHeight;
        RECT stRect = { 0 };
        int iRet = GetClientRect(hWnd, &stRect);
        //iRet = BitBlt(hdcDst, 0, 0, stRect.right - stRect.left, stRect.bottom - stRect.top,hdcSrc, 0, 0, SRCCOPY);
        iRet = StretchBlt(hdcDst, 0, 0, stRect.right - stRect.left, stRect.bottom - stRect.top, hdcSource, 0, 0, iX, iY, SRCCOPY);
        DeleteObject(hSrcBM);
    }
    else {
        WriteLog("RemoteControl CreateDIBSection error:%u\r\n", GetLastError());
    }
 
    DeleteObject(hRemoteBM);
    DeleteDC(hdcScr);
    DeleteDC(hdcSource);
 
    mapit->second->dataType = 0;
 
    EndPaint(hWnd, &stPS);
    return TRUE;
}
else if (mapit->second->dataType == REMOTE_CLIENT_SCREEN)
{
    char* lpClientBitmap = mapit->second->lpClientBitmap;
    HDC  hdcScr = CreateDCA("DISPLAY", NULL, NULL, NULL);
    HDC hdcSource = CreateCompatibleDC(hdcScr);
    LPBITMAPFILEHEADER pBMFH = (LPBITMAPFILEHEADER)lpClientBitmap;
    void* pDibts = (void*)(lpClientBitmap + pBMFH->bfOffBits);
    LPBITMAPINFOHEADER pBMIH = (LPBITMAPINFOHEADER)(lpClientBitmap + sizeof(BITMAPFILEHEADER));
    DWORD dwDibtsSize = ((pBMIH->biWidth * pBMIH->biBitCount + 31) / 32) * 4 * pBMIH->biHeight;
    char* pRemoteSrnData = 0;
    HBITMAP hRemoteBM = CreateDIBSection(0, (LPBITMAPINFO)pBMIH, DIB_RGB_COLORS, (void**)&pRemoteSrnData, 0, 0);
    if (hRemoteBM)
    {
        memcpy(pRemoteSrnData, pDibts, dwDibtsSize);
        HBITMAP hSrcBM = (HBITMAP)SelectObject(hdcSource, hRemoteBM);
 
        int iX = pBMIH->biWidth;
        int iY = pBMIH->biHeight;
        RECT stRect = { 0 };
        int iRet = GetClientRect(hWnd, &stRect);
        //iRet = BitBlt(hdcDst, 0, 0, stRect.right - stRect.left, stRect.bottom - stRect.top,hdcSrc, 0, 0, SRCCOPY);
        iRet = StretchBlt(hdcDst, 0, 0, stRect.right - stRect.left, stRect.bottom - stRect.top, hdcSource, 0, 0, iX, iY, SRCCOPY);
        DeleteObject(hSrcBM);
    }
    else {
        WriteLog("RemoteControl CreateDIBSection error:%u\r\n", GetLastError());
    }
 
    DeleteObject(hRemoteBM);
    DeleteDC(hdcScr);
    DeleteDC(hdcSource);
 
    mapit->second->dataType = 0;
 
    EndPaint(hWnd, &stPS);
    return TRUE;
}
typedef struct {
    int screenx;
    int screeny;
    int bitsperpix;
}STREMOTECONTROLPARAMS, * LPSTREMOTECONTROLPARAMS;
 
typedef struct {
    POINT pos;
    POINT size;
}REMOTECONTROLMOUSEPOS, * LPREMOTECONTROLMOUSEPOS;
 
typedef struct {
    unsigned char key;
    unsigned char shiftkey;
}REMOTECONTROLKEY, * LPREMOTECONTROLKEY;
 
typedef struct {
    int delta;
    int xy;
}REMOTECONTROLWHEEL, * LPREMOTECONTROLWHEEL;
 
 
typedef struct {
    DWORD       dwType;
    POINT       stPT;
    DWORD       dwTickCnt;
    int         iDelta;
}STMOUSEACTION, * LPMOUSEACTION;
typedef struct {
    int screenx;
    int screeny;
    int bitsperpix;
}STREMOTECONTROLPARAMS, * LPSTREMOTECONTROLPARAMS;
 
typedef struct {
    POINT pos;
    POINT size;
}REMOTECONTROLMOUSEPOS, * LPREMOTECONTROLMOUSEPOS;
 
typedef struct {
    unsigned char key;
    unsigned char shiftkey;
}REMOTECONTROLKEY, * LPREMOTECONTROLKEY;
 
typedef struct {
    int delta;
    int xy;
}REMOTECONTROLWHEEL, * LPREMOTECONTROLWHEEL;
 
 
typedef struct {
    DWORD       dwType;
    POINT       stPT;
    DWORD       dwTickCnt;
    int         iDelta;
}STMOUSEACTION, * LPMOUSEACTION;
if (dwCommand == REMOTE_MOUSE_POS)
{
    DWORD dwDataSize = lphdr->packlen;
    if (dwDataSize = sizeof(POINT) + sizeof(POINT))
    {
        LPREMOTECONTROLPOS pos = (LPREMOTECONTROLPOS)(lpBuf + sizeof(NETWORKPACKETHEADER));
        POINT stServerCurrent = pos->pos;
        POINT stPtServerMax = pos->size;
 
        int iXLocalMax = ScrnResolutionX;
        int iYLocalMax = ScrnResolutionY;
 
        POINT stPtLocalCurrent = { 0 };
        if (stPtServerMax.x != 0 && stPtServerMax.y != 0)
        {
            stPtLocalCurrent.x = (iXLocalMax * stServerCurrent.x) / stPtServerMax.x;
            stPtLocalCurrent.y = (iYLocalMax * stServerCurrent.y) / stPtServerMax.y;
            //mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,stPtLocalCurrent.x,stPtLocalCurrent.y,0,0);
            lpSetCursorPos(stPtLocalCurrent.x, stPtLocalCurrent.y);
        }
    }
 
    actionInterval(&dwSleepTimeValue);
 
    //checkTime(&dwSleepTimeValue);
    continue;
}
else if (dwCommand == REMOTE_KEYBOARD)
{
    if (lphdr->packlen == 2)
    {
        LPREMOTECONTROLKEY lpkey = (LPREMOTECONTROLKEY)(lpBuf + sizeof(NETWORKPACKETHEADER));
        unsigned char key = lpkey->key;
        unsigned char keyshift = lpkey->shiftkey;
 
        //unsigned char szKeyboardState[256];
        //memmove(szKeyboardState,pData,256);
        //iRet = SetKeyboardState(pData);
 
        if (keyshift)
        {
            keybd_event(VK_SHIFT, 0, 0, 0);
            keybd_event(key, 0, 0, 0);
            keybd_event(key, 0, KEYEVENTF_KEYUP, 0);
            keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
        }
        else {
            keybd_event(key, 0, 0, 0);
            keybd_event(key, 0, KEYEVENTF_KEYUP, 0);
        }
    }
    actionInterval(&dwSleepTimeValue);
 
    //checkTime(&dwSleepTimeValue);
 
    continue;
}
else if (dwCommand == REMOTE_LEFTBUTTONDOWN || dwCommand == REMOTE_LEFTBUTTONDOUBLECLICK ||
    dwCommand == REMOTE_RBUTTONDOWN || dwCommand == REMOTE_RBUTTONDOUBLECLICK)
{
    DWORD dwDataSize = lphdr->packlen;
    if (dwDataSize = sizeof(POINT) + sizeof(POINT))
    {
        LPREMOTECONTROLPOS pos = (LPREMOTECONTROLPOS)(lpBuf + sizeof(NETWORKPACKETHEADER));
        POINT stServerCurrent = pos->pos;
        POINT stPtServerMax = pos->size;
 
        int iXLocalMax = ScrnResolutionX;
        int iYLocalMax = ScrnResolutionY;
 
        POINT stPtLocalCur = { 0 };
        if (stPtServerMax.x != 0 && stPtServerMax.y != 0)
        {
            stPtLocalCur.x = (iXLocalMax * stServerCurrent.x) / stPtServerMax.x;
            stPtLocalCur.y = (iYLocalMax * stServerCurrent.y) / stPtServerMax.y;
            lpSetCursorPos(stPtLocalCur.x, stPtLocalCur.y);
            if (dwCommand == REMOTE_LEFTBUTTONDOWN)
            {
                mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
            }
            else if (dwCommand == REMOTE_RBUTTONDOWN)
            {
                mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
                mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
            }
            else if (dwCommand == REMOTE_RBUTTONDOUBLECLICK)
            {
                mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
                lpSleep(0);
                mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
            }
            else if (dwCommand == REMOTE_LEFTBUTTONDOUBLECLICK)
            {
                mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                lpSleep(0);
                mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
            }
        }
    }
 
    actionInterval(&dwSleepTimeValue);
    //checkTime(&dwSleepTimeValue);
    continue;
}
else if (dwCommand == REMOTE_MOUSEWHEEL)
{
    DWORD dwDataSize = lphdr->packlen;
    if (dwDataSize = sizeof(DWORD) + sizeof(DWORD))
    {
        LPREMOTECONTROLWHEEL wheel = (LPREMOTECONTROLWHEEL)(lpBuf + sizeof(NETWORKPACKETHEADER));
        int key = wheel->delta & 0xffff;
        int delta = wheel->delta >> 16;
        int x = wheel->xy & 0xffff;
        int y = wheel->xy & 0xffff0000;
 
        mouse_event(MOUSEEVENTF_WHEEL, x, y, delta, 0);
        lpSleep(0);
    }
 
    actionInterval(&dwSleepTimeValue);
 
    //checkTime(&dwSleepTimeValue);
    continue;
    //to action more faster not to sleep
}
else if (dwCommand == RECV_DATA_OK || dwCommand == REMOTE_DUMMY_PACKET)
{
    freeInterval(&dwSleepTimeValue);
}
else if (dwCommand == REMOTECONTROL_END)
{
    writeLog("remotecontrol shutdown by server\r\n");
    break;
}
else
{
    writeLog("RemoteControlProc unrecognized command:%u\r\n", dwCommand);
    //break;
}
 
checkTime(&dwSleepTimeValue);
if (dwCommand == REMOTE_MOUSE_POS)
{
    DWORD dwDataSize = lphdr->packlen;
    if (dwDataSize = sizeof(POINT) + sizeof(POINT))
    {
        LPREMOTECONTROLPOS pos = (LPREMOTECONTROLPOS)(lpBuf + sizeof(NETWORKPACKETHEADER));
        POINT stServerCurrent = pos->pos;
        POINT stPtServerMax = pos->size;
 
        int iXLocalMax = ScrnResolutionX;
        int iYLocalMax = ScrnResolutionY;
 
        POINT stPtLocalCurrent = { 0 };
        if (stPtServerMax.x != 0 && stPtServerMax.y != 0)
        {
            stPtLocalCurrent.x = (iXLocalMax * stServerCurrent.x) / stPtServerMax.x;
            stPtLocalCurrent.y = (iYLocalMax * stServerCurrent.y) / stPtServerMax.y;
            //mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,stPtLocalCurrent.x,stPtLocalCurrent.y,0,0);
            lpSetCursorPos(stPtLocalCurrent.x, stPtLocalCurrent.y);
        }
    }
 
    actionInterval(&dwSleepTimeValue);
 
    //checkTime(&dwSleepTimeValue);
    continue;
}
else if (dwCommand == REMOTE_KEYBOARD)
{
    if (lphdr->packlen == 2)
    {
        LPREMOTECONTROLKEY lpkey = (LPREMOTECONTROLKEY)(lpBuf + sizeof(NETWORKPACKETHEADER));
        unsigned char key = lpkey->key;
        unsigned char keyshift = lpkey->shiftkey;
 
        //unsigned char szKeyboardState[256];
        //memmove(szKeyboardState,pData,256);
        //iRet = SetKeyboardState(pData);
 
        if (keyshift)
        {
            keybd_event(VK_SHIFT, 0, 0, 0);
            keybd_event(key, 0, 0, 0);
            keybd_event(key, 0, KEYEVENTF_KEYUP, 0);
            keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
        }
        else {
            keybd_event(key, 0, 0, 0);
            keybd_event(key, 0, KEYEVENTF_KEYUP, 0);
        }
    }
    actionInterval(&dwSleepTimeValue);
 
    //checkTime(&dwSleepTimeValue);
 
    continue;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2023-6-26 18:16 被satadrover编辑 ,原因:
收藏
免费 17
支持
分享
最新回复 (25)
雪    币: 6213
活跃值: (4216)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
2
感谢大佬分享 学习一下
2023-6-12 21:39
0
雪    币: 2788
活跃值: (2816)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
DC方式截图在win10 win11下很慢,win7也慢,可以设置下变快。
2023-6-13 08:37
0
雪    币: 3004
活跃值: (30861)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-6-13 09:48
1
雪    币: 3624
活跃值: (2703)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
一直有个疑问。都是远控,为什么灰鸽子之类的会被杀,而向日葵这些却没事呢?
2023-6-13 09:51
0
雪    币: 223
活跃值: (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
vcdemon 一直有个疑问。都是远控,为什么灰鸽子之类的会被杀,而向日葵这些却没事呢?
代码中的特征被杀软标记了。
2023-6-13 10:29
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
7
xuezhimeng DC方式截图在win10 win11下很慢,win7也慢,可以设置下变快。
能指教一下该怎么设置吗?
2023-6-13 10:48
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
8
vcdemon 一直有个疑问。都是远控,为什么灰鸽子之类的会被杀,而向日葵这些却没事呢?
杀毒软件有一个对软件的认证机制,提交给杀毒认证过的就不杀了
2023-6-13 10:49
0
雪    币: 8188
活跃值: (2842)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
9

这个文章里,很多描述都是错的或者带误导性的。我挑两个:

1、从测试中发现,微软自带的远程控制软件mstsc.exe,在100kb以内的网速下,画面显示依然清晰、控制依然保持流畅,这就不是第三方开发包可以轻易达到的。

建议作者去看看freerdp这个github仓库,里面有rdp协议的具体描述,包括引用于msdn的内容。

2、采用截图发送的方式,这应该是上个世纪九十年代的做法,即使开源的木马,都有一些基于差分方式获取前后两帧截图差异,然后传输差异的方式,来简单减少流量,知名的“黑洞”在这一块基本做到了极致。同时代的rdp协议,没remoterfx(win7)和提供dxgi接口之前,性能没有比这种远程工具要好,即使默认使用mirror驱动,因为rdp工作在虚拟显卡下;


当然还有一些常识性的误导,“远控软件,除非算法上的突破,无论理论和工程技术,恐怕都无法为设计开发者赢得博士学位”,如果要实现一个商业级别的远控工具,我只举windows系统做例子,你要解决如下问题:

1、最基础的,屏幕的差分获取和编码,不管是通过系统接口(dxgi、gdi+,mirror),当然现在很多方案都基于webrtc魔改,但一上了2k和4k,这就不是只抄代码能抄来的,涉及到太多的优化;例如延时要求在30ms下;

2、网络传输架构,这不是多布几个机房就能解决的,当然机房不够,一定是不行;

还有其他很多工程上和技术上需要解决的问题,你真的能实现一个商业级别的远控工具,水几篇博士论文,一点问题都没有。



最后于 2023-6-13 14:34 被layerfsd编辑 ,原因: mei
2023-6-13 14:19
1
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
10
layerfsd 这个文章里,很多描述都是错的或者带误导性的。我挑两个:1、从测试中发现,微软自带的远程控制软件mstsc.exe,在100kb以内的网速下,画面显示依然清晰、控制依然保持流畅,这就不是第三方开发包可以 ...
多谢指点
2023-6-13 15:07
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
11
layerfsd 这个文章里,很多描述都是错的或者带误导性的。我挑两个:1、从测试中发现,微软自带的远程控制软件mstsc.exe,在100kb以内的网速下,画面显示依然清晰、控制依然保持流畅,这就不是第三方开发包可以 ...
mstsc网速低时表现确实可以,可能是我测试不充分。
2023-6-13 15:35
0
雪    币: 590
活跃值: (618)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
不错,值得借鉴,要是网络部分用UDP传输就更好了
2023-6-13 22:57
0
雪    币: 2788
活跃值: (2816)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
satadrover 能指教一下该怎么设置吗?
DwmEnableComposition  不过在win10 win11下没效果的。
2023-6-14 08:23
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
14
zhusg 不错,值得借鉴,要是网络部分用UDP传输就更好了
thanks
2023-6-14 09:39
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
15
xuezhimeng DwmEnableComposition 不过在win10 win11下没效果的。
2023-6-14 09:39
0
雪    币: 40
活跃值: (145)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
看看各大云计算厂家的VDI云桌面图像传输协议 ,比如阿里云用的是思杰的云桌面,用的是VDP协议,图像传输不仅仅是压缩差异对比了,还是智能识别桌面图像哪些是文字,哪些是图像,用的压缩算法也不一样,办公场景,微软rdp和云桌面vdp一般有256KB就足够了。
2023-6-14 11:48
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
17
CCDmichael 看看各大云计算厂家的VDI云桌面图像传输协议 ,比如阿里云用的是思杰的云桌面,用的是VDP协议,图像传输不仅仅是压缩差异对比了,还是智能识别桌面图像哪些是文字,哪些是图像,用的压缩算法也不一样,办公场 ...
2023-6-14 14:04
0
雪    币: 5573
活跃值: (2153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
(GPT)常见的远程桌面技术协议有以下几种:

RDP协议:RDP是Remote Desktop Protocol(远程桌面协议)的缩写,是微软开发的一种远程桌面协议,常用于Windows系统下的远程桌面访问。

VNC协议:VNC是Virtual Network Computing(虚拟网络计算)的缩写,也是一种远程桌面协议,跨平台支持较好。

SPICE协议:SPICE是Simple Protocol for Independent Computing Environments(独立计算环境的简单协议)的缩写,是用于虚拟化环境的远程协议。

Citrix协议:Citrix是一种商用的远程桌面协议,主要用于虚拟化环境以及企业级应用。
2023-6-17 08:52
0
雪    币: 117
活跃值: (3017)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
网速低时表现确实可以,主要是看你希望的FRAME RATE 是多少,不整DESKTOP 傳,傳差異部分,在FRAME RATE 低的情況下,CPU 不吃太多。
但若想看影片。就有問題了,這東西跟用什麼PROTOCOL 無關,主要是
你找出差異的方式有關,mstsc.exe 好,又不吃CPU,主因為是它有mirror driver當底,而不是GDI+ , DXGI 這類笨方式。
其實解決了mirror driver, 你走了7成的路了。

2023-6-18 06:00
0
雪    币: 4520
活跃值: (889)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
20
感谢分享。
2023-6-18 17:40
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
21
powerpcer 网速低时表现确实可以,主要是看你希望的FRAME RATE 是多少,不整DESKTOP 傳,傳差異部分,在FRAME RATE 低的情況下,CPU 不吃太多。 但若想看影片。就有問題了,這東西跟用什 ...
多谢指教
2023-6-18 18:35
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
2023-6-24 20:08
0
雪    币: 222
活跃值: (315)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
23

被控端截图bitmap -> bmp  ->  avi -> mp4编码 -> tcp/ip传输  -> mp4解码  -> bmp  ->控制端显示出来
我在改版的大灰狼里见过这种方式,远控的作者起了个名字叫“家庭娱乐影音算法”

最后于 2023-6-25 14:35 被fgfxf编辑 ,原因:
2023-6-25 14:33
0
雪    币: 222
活跃值: (315)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
24
作者的文章虽然有不足,但也是技术含量很高,很不错的学习资料。
2023-6-25 14:34
0
雪    币: 1669
活跃值: (3202)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
25
fgfxf 作者的文章虽然有不足,但也是技术含量很高,很不错的学习资料。
多谢夸奖,愿共同进步。
2023-6-25 16:55
0
游客
登录 | 注册 方可回帖
返回
//