QT应用编程:开发同屏(屏幕共享)软件(HTTP协议)

一、环境介绍

QT版本: 5.12.6
操作系统: win10 64位
软件下载地址

二、软件介绍

该软件是一个桌面同屏软件,使用HTTP协议将桌面的图像数据传输给浏览器进行显示。
采用多线程方式处理浏览器请求。

  1. 支持多个浏览器页面同时访问
  2. 软件界面支持最小化到托盘系统
  3. 图片采用jpg格式传输
  4. HTTP协议采用长连接方式
    软件界面
    最小化到托盘提示
    登录浏览器效果
    访问自己屏幕效果

三、源代码

代码工程布局

3.1 widget.h代码

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QIcon>
#include <QMouseEvent>
#include <QMessageBox>
#include <QTcpServer>
#include <QHostInfo>  //获取计算机网络信息
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
#include <QScrollBar>
#include <QDesktopWidget>
#include <QSystemTrayIcon>
#include "tcpserverthread.h"
#include "tcpserver.h"
#include <QTimer>
#include "get_lcd_image.h"

#include <QSystemTrayIcon>  //t托盘类
#include <QDesktopServices> //桌面事件类
#include <QAction>
#include <QMenu>

//定义软件版本号
#define VersionNumber 1.2

QT_BEGIN_NAMESPACE
namespace Ui {
    
     class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    
    
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    TcpServer server; //TCP服务器
     QTimer *timer;

     //获取屏幕图像数据线程
     GetLCDImage thread_GetLcdImage;

    QMenu *trayMenu;//托盘菜单
    QSystemTrayIcon *tray;//托盘图标添加成员
    QAction *restoreAction;//托盘图标右键点击时弹出选项
    QAction *quitAction;//托盘图标右键点击时弹出选项
protected:
    void mousePressEvent(QMouseEvent *e);
    void mouseMoveEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *e);
    void closeEvent(QCloseEvent *event); //窗口关闭
private slots:
    void show_Widget(QSystemTrayIcon::ActivationReason reason);
    void timer_update();
    void on_toolButton_zoom_clicked();

    void on_toolButton_close_clicked();

    void on_pushButton_clicked();

    void on_toolButton_help_clicked();

private:
    Ui::Widget *ui;
    QPoint last;
};

extern bool select_file; //选择外部资源文件  =1表示选择外部文件  =0表示选择内部文件
#endif // WIDGET_H

3.2 widget.cpp代码

#include "widget.h"
#include "ui_widget.h"

bool select_file=false; //选择外部资源文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    
    
    ui->setupUi(this);

    /*基本设置*/
    this->setWindowIcon(QIcon(":/log.ico")); //设置图标
    this->setWindowTitle("屏幕共享");
    //隐藏标题栏
    this->setWindowFlags(Qt::Dialog|Qt::FramelessWindowHint);

    QStringList qss;
    //设置按钮全局样式
    qss.append(QString(".QToolButton{"
                       "border-style:none;"
                       "padding:8px 18px;"
                       "border-radius:8px;"
                       "background-color:#CCFFFF"  //默认颜色
                       "}"));
    qss.append(QString("QToolButton:hover{background-color:#FF9966}"  //鼠标停留颜色
                       "QToolButton:pressed{background-color:#00F5FF}"//按下颜色
                       ));
    //设置按钮全局样式
    qss.append(QString(".QPushButton{"
                       "border-style:none;"
                       "padding:8px 18px;"
                       "border-radius:8px;"
                       "background-color:#FF6666"  //默认颜色
                       "}"));
    qss.append(QString("QPushButton:hover{background-color:#FF9966}"  //鼠标停留颜色
                       "QPushButton:pressed{background-color:#00F5FF}"//按下颜色
                       ));
    //主窗体背景
    qss.append(QString("QWidget{background-color:#CCFFFF}"));

    setStyleSheet(qss.join(""));

    /*获取本机IP地址添加到列表进行显示*/
    QList<QHostAddress> list = QNetworkInterface::allAddresses();
    for(int i=0;i<list.count();i++)
    {
    
    
        QHostAddress addr=list.at(i);
        if(addr.protocol() == QAbstractSocket::IPv4Protocol)
        {
    
    
            ui->plainTextEdit->insertPlainText(addr.toString());
            ui->plainTextEdit->insertPlainText("\n");
        }
    }

    //刷新在线人数
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(timer_update()));
    timer->start(1000);

    //qDebug()<<"current applicationDirPath: "<<QCoreApplication::applicationDirPath();
    //qDebug()<<"current currentPath: "<<QDir::currentPath();

    //***托盘***
    tray= new QSystemTrayIcon(this);//初始化托盘对象tray

    connect(tray,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),this,SLOT(show_Widget(QSystemTrayIcon::ActivationReason)));

    tray->setIcon(QIcon(QPixmap(":/log.png")));//设定托盘图标,引号内是自定义的png图片路径
    tray->setToolTip("局域网屏幕共享"); //提示文字
    restoreAction = new QAction("打开", this);
    connect(restoreAction, SIGNAL(triggered()), this, SLOT(show()));
    quitAction = new QAction("退出", this);
    connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));

    trayMenu = new QMenu(this);
    trayMenu->addAction(restoreAction);
    trayMenu->addSeparator();
    trayMenu->addAction(quitAction);
    tray->setContextMenu(trayMenu);
}

Widget::~Widget()
{
    
    
    delete ui;
}


void Widget::show_Widget(QSystemTrayIcon::ActivationReason reason)
{
    
    
    switch(reason)
    {
    
    
    case QSystemTrayIcon::Trigger://单击托盘图标
        this->showNormal(); //还原界面
        break;
    case QSystemTrayIcon::DoubleClick://双击托盘图标
        this->showNormal();//还原界面
        break;
    default:
        break;
    }
}

void Widget::mousePressEvent(QMouseEvent *e)
{
    
    
    last=e->globalPos();
}

void Widget::mouseMoveEvent(QMouseEvent *e)
{
    
    
    int dx = e->globalX() - last.x();
        int dy = e->globalY() - last.y();
        last = e->globalPos();
        move(x()+dx, y()+dy);
}
void Widget::mouseReleaseEvent(QMouseEvent *e)
{
    
    
    int dx = e->globalX() - last.x();
    int dy = e->globalY() - last.y();
    move(x()+dx, y()+dy);
}


//窗口关闭事件
void Widget::closeEvent(QCloseEvent *event)
{
    
    
    int ret = QMessageBox::question(this, tr("提示"),
    tr("是否需要退出程序?"),QMessageBox::Yes | QMessageBox::No);

    if(ret==QMessageBox::Yes)
    {
    
    
        thread_run_flag=false;//视频传输线程
        get_image_flag=false; //停止屏幕采集线程运行.
        thread_GetLcdImage.quit(); //退出线程
        thread_GetLcdImage.wait(); //等待线程结束
        event->accept();
    }
    else
    {
    
    
        event->ignore();
    }
    /*
    其中accept就是让这个关闭事件通过并顺利关闭窗口,
    ignore就是将其忽略回到窗口本身。这里可千万得注意在每一种可能性下都对event进行处理,
    以免遗漏。
    */
}

/**
 * @brief Widget::on_toolButton_zoom_clicked
 * 最小化窗口
 */
void Widget::on_toolButton_zoom_clicked()
{
    
    
    //this->showMinimized();

    //设置窗口最小化到托盘
    this->hide();
    QString title="DS小龙哥";
    QString text="局域网屏幕共享";
    tray->show();//让托盘图标显示在系统托盘上
    tray->showMessage(title,text,QSystemTrayIcon::Information,2000); //最后一个参数为提示时长,默认10000,即10s
}

/**
 * @brief Widget::on_toolButton_close_clicked
 * 关闭窗口
 */
void Widget::on_toolButton_close_clicked()
{
    
    
    this->close();
}

/**
 * @brief Widget::on_pushButton_clicked
 * 开始共享
 */
void Widget::on_pushButton_clicked()
{
    
    
    //如果已经按下
    if(ui->checkBox->checkState()!=Qt::Unchecked)
    {
    
    
        select_file=true;  //外部文件
    }
    else
    {
    
    
         select_file=false;  //内部文件
    }

    if(ui->pushButton->text()=="启动屏幕共享")
    {
    
    
        //监听服务器
        if(server.listen(QHostAddress::Any,8888)!=true)
        {
    
    
            QMessageBox::question(this, tr("提示"),tr("服务器监听设置失败.\n"),QMessageBox::Ok);
            return;
        }
        thread_run_flag=true; //视频传输线程
        get_image_flag=true; //停止屏幕采集线程运行.
        //thread_GetLcdImage.start(); //开始运行屏幕数据采集线程
        ui->pushButton->setText("停止屏幕共享");
    }
    else
    {
    
    
        server.close(); //关闭服务器

        thread_run_flag=false;//视频传输线程
        get_image_flag=false; //停止屏幕采集线程运行.
        thread_GetLcdImage.quit(); //退出线程
        thread_GetLcdImage.wait(); //等待线程结束

        //设置按钮文本
        ui->pushButton->setText("启动屏幕共享");
    }
}

/**
 * @brief Widget::timer_update
 * 在线人数
 */
void Widget::timer_update()
{
    
    
    //显示在线人数
    ui->lcdNumber->display(fd_list.fd_count());
}

/**
 * @brief Widget::on_toolButton_help_clicked
 * 帮助提示
 */
void Widget::on_toolButton_help_clicked()
{
    
    
    QString text="欢迎使用局域网屏幕共享软件.\n";
    text+="软件开启之后,点击屏幕上的启动按钮,即可打开共享功能.\n";
    text+="软件采用HTTP协议方式,将图片以jpg格式传输给浏览器进行显示,启动共享功能"
          "之后,打开浏览器,输入正确IP地址和端口号(固定8888)即可访问屏幕画面.\n"
          "如果发现浏览器显示的画面尺寸不合理,可以先停止屏幕共享。选中软件上方的<选择外部"
          "文件>选项,打开软件目录下的index.html文件,修改图片尺寸宽和高,保存之后再重新启动共享即可.\n";

    text+=QString("\n\n软件版本号:%1").arg(VersionNumber);
    QMessageBox::about(this,"帮助信息",text);
}

3.3 tcp_server.h代码

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QReadWriteLock>
#include <QList>

/*
存放连接上服务器的节点
*/
class SocketFdList
{
    
    
private:
    QReadWriteLock lock;
    QList<qintptr> fd;
public:
    SocketFdList()
    {
    
    
        fd.clear();
    }

    //向队列里插入一条数据
    void add_fd(qintptr data)
    {
    
    
        lock.lockForWrite();
        fd.append(data);
        lock.unlock();
    }
    //卸载数据
    void Del_fd(qintptr data)
    {
    
    
        lock.lockForWrite();
        for(int i=0;i<fd.count();i++)
        {
    
    
            if(fd.at(i)==data)
            {
    
    
                fd.removeAt(i); //卸载节点
            }
        }
        lock.unlock();
    }
    //返回数量
    int fd_count(void)
    {
    
    
        int cnt;
        lock.lockForRead();
        cnt=fd.count();
        lock.unlock();
        return cnt;
    }
};

class TcpServer : public QTcpServer
{
    
    
    Q_OBJECT
public:
    TcpServer();
protected:
    //Q_DECL_OVERRIDE 宏的作用: 防止写了错误的虚函数名称,方便编译器报错
    void incomingConnection(qintptr socketDescriptor) Q_DECL_OVERRIDE;

};

extern class SocketFdList fd_list;
#endif // TCPSERVER_H

3.4 tcp_server.cpp代码

#include "tcpserver.h"
#include "tcpserverthread.h"
class SocketFdList fd_list;

TcpServer::TcpServer()
{
    
    

}

//重写TCP服务器的虚函数,处理新连接上的客户端
void TcpServer::incomingConnection(qintptr socketDescriptor)
{
    
    
    TcpServerThread *thread = new TcpServerThread(socketDescriptor,this);
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); //销毁对象
    thread->start();
}

3.5 tcp_thread.h代码

#ifndef TCPSERVERTHREAD_H
#define TCPSERVERTHREAD_H
#include <QThread>
#include <QtNetwork>
#include "tcpserver.h"
#include <QFile>
#include "get_lcd_image.h"
#include "widget.h"
extern bool thread_run_flag;

class TcpServerThread : public QThread
{
    
    
    Q_OBJECT
public:
    TcpServerThread(int socketDescriptor, QObject *parent);
    ~TcpServerThread();
    void run() Q_DECL_OVERRIDE;
     QTcpSocket *tcpSocket;
     int SendFileData(char *buff, QString file_path, const char *type);
     int SendImageData(char *buff);
     char buff[1024];
private:
    int socketDescriptor;
    QString text;
};

#endif // TCPSERVERTHREAD_H

3.6 tcp_thread.cpp代码

#include "tcpserverthread.h"

//线程运行标志
bool thread_run_flag=true;


TcpServerThread::TcpServerThread(int socketDescriptor,QObject *parent)
:QThread(parent), socketDescriptor(socketDescriptor)
{
    
    
    //保存连接的套接字
    fd_list.add_fd(socketDescriptor);
}

TcpServerThread::~TcpServerThread()
{
    
    
    //卸载连接的套接字
    fd_list.Del_fd(socketDescriptor);
    //关闭连接
    tcpSocket->close();
    delete  tcpSocket;
}

void TcpServerThread::run()
{
    
    
    tcpSocket=new QTcpSocket;
    /*使用socketDescriptor套接字初始化QAbstractSocket*/
    if(!tcpSocket->setSocketDescriptor(socketDescriptor))
    {
    
    
        return;
    }

    //读取接收的数据
    QString text;
    if(tcpSocket->waitForReadyRead())
    {
    
    
        text=tcpSocket->readAll();
    }

    //处理浏览器的请求
    if(text.contains("GET / HTTP/1.1", Qt::CaseInsensitive))
    {
    
    
        SendFileData(buff,":/login.html","text/html");
    }
    else if(text.contains("GET /image.jpg HTTP/1.1", Qt::CaseInsensitive))
    {
    
    
        SendImageData(buff);
    }
    else if(text.contains("GET /favicon.ico HTTP/1.1", Qt::CaseInsensitive))
    {
    
    
        SendFileData(buff,":/logo.ico","image/x-icon");
    }
    else if(text.contains("GET /three.min.js", Qt::CaseInsensitive))
    {
    
    
        SendFileData(buff,":/three.min.js","application/x-javascript");
    }
    else if(text.contains("GET /style.css", Qt::CaseInsensitive))
    {
    
    
        SendFileData(buff,":/style.css","text/css");
    }
    else if(text.contains("GET /Stats.min.js", Qt::CaseInsensitive))
    {
    
    
        SendFileData(buff,":/Stats.min.js","application/x-javascript");
    }
    else if(text.contains("GET /logo.png HTTP/1.1", Qt::CaseInsensitive))
    {
    
    
        SendFileData(buff,":/logo.png","image/png");
    }
    else if(text.contains("userName=wbyq&passWord=12345678", Qt::CaseInsensitive))
    {
    
       
        if(select_file)
        {
    
    
            //选择外部资源文件
            QString text=QCoreApplication::applicationDirPath();
            text+="/index.html";
            SendFileData(buff,text,"text/html");
        }
        else
        {
    
    
            //选择内部资源文件
            SendFileData(buff,":/index.html","text/html");
        }
    }
}

/**
 * 向浏览器响应请求数据
 */
int TcpServerThread::SendFileData(char *buff,QString file_path,const char *type)
{
    
    
    qint64 size=0;
    /*1. 读取文件*/
    QFile file(file_path);
    if(file.open(QIODevice::ReadOnly)!=true)return -1;
    size=file.size(); //得到文件大小
    QByteArray byte=file.readAll(); //读取所有数据
    file.close();//关闭文件
    /*2. 构建响应格式字符串*/
    sprintf(buff,"HTTP/1.1 200 OK\r\n"
                 "Content-type:%s\r\n"
                 "Content-Length:%lld\r\n"
                 "\r\n",type,size);
    /*3. 向客户端发送响应请求*/
    if(tcpSocket->write(buff,strlen(buff))<=0)return -2;
    tcpSocket->waitForBytesWritten();  //等待写
    /*4. 向客户端发送响应实体数据*/
    if(tcpSocket->write(byte)<=0)return -3;
    tcpSocket->waitForBytesWritten();  //等待写
    return 0;
}

/*
向客户端循环发送图片数据流
*/
int TcpServerThread::SendImageData(char *buff)
{
    
    
    /*1. 构建响应格式字符串*/
    sprintf(buff,"HTTP/1.0 200 OK\r\n"
                "Server: wbyq\r\n"
                "Content-Type:multipart/x-mixed-replace;boundary=boundarydonotcross\r\n"
                "\r\n"
                "--boundarydonotcross\r\n");
    /*2. 向客户端发送响应请求*/
    if(tcpSocket->write(buff,strlen(buff))<=0)return -2;
    tcpSocket->waitForBytesWritten();  //等待写
    /*3. 循环发送数据*/
    while(thread_run_flag)
    {
    
    
//        //从全局缓冲区取数据
//        mutex.lock();
//        cond_wait.wait(&mutex);
//        QByteArray image_data; //保存一帧图像数据
//        image_data=lcd_image_data;
//        mutex.unlock();

        QBuffer data_buff;
        QPixmap pixmap;
        QScreen *screen = QGuiApplication::primaryScreen();
        pixmap=screen->grabWindow(0); //获取当前屏幕的图像
       // pixmap = pixmap.scaled(1024,576, Qt::KeepAspectRatio);
        pixmap.save(&data_buff,"jpg",80);
        QByteArray image_data=data_buff.data();


        /*4. 向浏览器发送响应头*/
        sprintf(buff,"Content-type:image/jpeg\r\n"
                     "Content-Length:%d\r\n"
                     "\r\n",image_data.size());
        if(tcpSocket->write(buff,strlen(buff))<=0)break;
        tcpSocket->waitForBytesWritten();  //等待写

        /*5. 发送实体数据*/
         if(tcpSocket->write(image_data)<=0)break;
         tcpSocket->waitForBytesWritten();  //等待写

        /*6. 发送边界符*/
        sprintf(buff,"\r\n--boundarydonotcross\r\n");
        if(tcpSocket->write(buff,strlen(buff))<=0)break;
        tcpSocket->waitForBytesWritten();  //等待写

        //等待一段时间
        msleep(5);
    }
    return 0;
}

下面公众号里有全套QT、C、C++、单片机基础教程–欢迎关注:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiaolong1126626497/article/details/111411062