一、环境介绍
QT版本: 5.12.6
操作系统: win10 64位
软件下载地址
完整源码下载(下载即可编译运行,不懂可以私信):https://download.csdn.net/download/xiaolong1126626497/19354865
二、软件介绍
该软件是一个桌面同屏软件,使用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; }