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月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
137 2
|
10天前
|
安全 网络协议 应用服务中间件
内网ip申请SSL证书实现https访问
内网IP地址虽不能直接申请公网SSL证书,但可通过IP SSL证书保障数据安全。流程包括:确定固定内网IP,选择支持内网IP的CA,注册申请证书,生成CSR,验证IP所有权,下载部署证书至Web服务器,测试HTTPS访问,确保配置正确及证书有效。此方法适用于内网环境,提升数据传输安全性。
内网ip申请SSL证书实现https访问
|
18天前
|
Web App开发 算法 应用服务中间件
nginx开启局域网https访问
【10月更文挑战第22天】为了调试WebRTC功能,需要在局域网内搭建HTTPS协议。具体步骤包括:在已部署Nginx和安装OpenSSL的环境中生成私钥、证书签名请求和自签名证书;将生成的文件放置到Nginx的证书目录并修改Nginx配置文件,最后重启Nginx服务。注意,自签名证书不受第三方机构认可,如需正式使用,需向CA申请签名。
|
22天前
|
安全 网络安全 数据安全/隐私保护
政务内网实现https访问教程
政务内网实现HTTPS访问需经过多个步骤:了解HTTPS原理,选择并申请适合的SSL证书,配置SSL证书至服务器,设置端口映射与访问控制,测试验证HTTPS访问功能,注意证书安全性和兼容性,定期备份与恢复。这些措施确保了数据传输的安全性,提升了政务服务的效率与安全性。
|
18天前
|
安全 网络安全 数据安全/隐私保护
内网IP地址实现HTTPS加密访问教程
在内网环境中,为确保数据传输的安全性,绑定SSL证书搭建HTTPS服务器至关重要。本文介绍了内网IP地址的前期准备、申请SSL证书的步骤以及客户端配置方法。具体包括选择合适的CA、注册账号、提交申请、下载证书,并在客户端导入根证书,确保通信数据的安全加密。推荐使用JoySSL提供的技术解决方案,确保内网设备通信安全。
内网IP地址实现HTTPS加密访问教程
|
23天前
|
Web App开发 定位技术 iOS开发
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
24 1
|
1月前
|
存储 网络安全 对象存储
缺乏中间证书导致通过HTTPS协议访问OSS异常
【10月更文挑战第4天】缺乏中间证书导致通过HTTPS协议访问OSS异常
89 4
|
1月前
|
存储 缓存 安全
https访问提示不安全,证书密钥验证上如何解决
【10月更文挑战第4天】访问提示不安全,证书密钥验证上如何解决
323 2
|
1月前
|
编解码 JSON 安全
使用search-guard加固安全为https访问
使用search-guard加固安全为https访问
|
3月前
|
机器学习/深度学习 存储 前端开发
实战揭秘:如何借助TensorFlow.js的强大力量,轻松将高效能的机器学习模型无缝集成到Web浏览器中,从而打造智能化的前端应用并优化用户体验
【8月更文挑战第31天】将机器学习模型集成到Web应用中,可让用户在浏览器内体验智能化功能。TensorFlow.js作为在客户端浏览器中运行的库,提供了强大支持。本文通过问答形式详细介绍如何使用TensorFlow.js将机器学习模型带入Web浏览器,并通过具体示例代码展示最佳实践。首先,需在HTML文件中引入TensorFlow.js库;接着,可通过加载预训练模型如MobileNet实现图像分类;然后,编写代码处理图像识别并显示结果;此外,还介绍了如何训练自定义模型及优化模型性能的方法,包括模型量化、剪枝和压缩等。
53 1

推荐镜像

更多
下一篇
无影云桌面