一、环境介绍
QT版本: 5.12.6
操作系统: win10 64位
软件下载地址
二、软件介绍
该软件是一个桌面同屏软件,使用HTTP协议将桌面的图像数据传输给浏览器进行显示。
采用多线程方式处理浏览器请求。
- 支持多个浏览器页面同时访问
- 软件界面支持最小化到托盘系统
- 图片采用jpg格式传输
- 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;
}