QT应用编程:基于QT+HTTP协议设计的屏幕共享软件(只要有浏览器就可以访问)

简介: QT应用编程:基于QT+HTTP协议设计的屏幕共享软件(只要有浏览器就可以访问)

一、环境介绍

QT版本: 5.12.6

操作系统: win10 64位

软件下载地址

完整源码下载(下载即可编译运行,不懂可以私信):https://download.csdn.net/download/xiaolong1126626497/19354865


二、软件介绍

该软件是一个桌面同屏软件,使用HTTP协议将桌面的图像数据传输给浏览器进行显示。

采用多线程方式处理浏览器请求。


1.支持多个浏览器页面同时访问

2.软件界面支持最小化到托盘系统

3.图片采用jpg格式传输

4.HTTP协议采用长连接方式

image.png

image.png

image.png

image.png

三、源代码

image.png

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;
}
目录
相关文章
|
1月前
|
存储 网络协议 C语言
【C/C++ 串口编程 】深入探讨C/C++与Qt串口编程中的粘包现象及其解决策略
【C/C++ 串口编程 】深入探讨C/C++与Qt串口编程中的粘包现象及其解决策略
80 0
|
1月前
|
Linux 数据处理 C++
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(一)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
77 0
|
1月前
|
Linux API C语言
Qt串口编程探究:理论与实践
Qt串口编程探究:理论与实践
64 1
|
1月前
|
前端开发
webpack如何设置devServer启动项目为https协议
webpack如何设置devServer启动项目为https协议
141 0
|
1月前
|
存储 Linux API
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(三)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
31 1
|
1月前
|
消息中间件 Linux 数据处理
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(二)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
32 1
|
1月前
|
存储 并行计算 安全
【Qt 线程】探索Qt线程编程的奥秘:多角度深入剖析(二)
【Qt 线程】探索Qt线程编程的奥秘:多角度深入剖析
61 0
|
1月前
|
网络协议 Shell 网络安全
实验目的1.编译安装httpd2.优化路径3.并将鲜花网站上传到web服务器为网页目录4.在客户机访问网站http://www.bdqn.com
实验目的1.编译安装httpd2.优化路径3.并将鲜花网站上传到web服务器为网页目录4.在客户机访问网站http://www.bdqn.com
164 0
|
1月前
File和FTP和HTTP软件配置
File和FTP和HTTP软件配置
|
8天前
|
应用服务中间件 网络安全 Apache
解决跨域和https不能访问的问题
【4月更文挑战第10天】解决跨域和https不能访问的问题
40 2
解决跨域和https不能访问的问题

推荐镜像

更多