目录
屏幕共享的实现与应用
一、简介
屏幕共享包括屏幕采集和视频流推送这两部分功能。此次软件只在客户端方面做了优化,采用HTTP协议解决用户的软件安装下载困难,最大程度节省用户的安装软件。下面以 QT为例,介绍整体的设计框架;
二、设计软件界面
设计代码:
#include "widget.h"
#include "ui_widget.h"
#include <QGraphicsOpacityEffect>
#include <QMouseEvent>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
setWindowFlags(Qt::FramelessWindowHint);
ui->label->setPixmap(QPixmap(":/ll.bmp"));
ui->label->setScaledContents(true);
ui->toolButton->setIcon(QIcon(":/close.png"));
ui->toolButton_2->setIcon(QIcon(":/minus.png"));
/*基本设置*/
this->setWindowIcon(QIcon(":/log.png")); //设置图标
this->setWindowTitle("屏幕共享");
//隐藏边框
ui->textEdit_ip->setFrameStyle(QFrame::NoFrame);
QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect;
opacity->setOpacity(0.4); //透明范围同窗口透明度
ui->pushButton_start->setGraphicsEffect(opacity);
QGraphicsOpacityEffect *opacity3 = new QGraphicsOpacityEffect;
opacity3->setOpacity(0.5); //透明范围同窗口透明度
ui->textEdit_ip->setGraphicsEffect(opacity3);
/*获取本机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->textEdit_ip->append(addr.toString());
}
}
//刷新在线人数
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(timer_update()));
timer->start(100);
//***托盘***
tray= new QSystemTrayIcon(this);//初始化托盘对象tray
connect(tray,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),this,SLOT(show_Widget(QSystemTrayIcon::ActivationReason)));
tray->setIcon(QIcon(QPixmap(":/log.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);
}
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 *event) {
if(event->button() == Qt::LeftButton){ //如果有鼠标左键按下
m_dragPosition = event->globalPos() //鼠标相对于屏幕左上角的位置
- frameGeometry().topLeft(); //时钟窗体左上角的位置
event->accept(); //接受事件
}
}
void Widget::mouseMoveEvent(QMouseEvent *event) {
if(event->buttons() & Qt::LeftButton){ //有按键按下 同时是鼠标左键
move(event->globalPos() - m_dragPosition);
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_start_clicked()
{
if(ui->pushButton_start->text()=="启动屏幕共享")
{
//监听服务器
if(server.listen(QHostAddress::Any,8888)!=true)
{
QMessageBox::question(this, tr("提示"),tr("服务器监听设置失败.\n"),QMessageBox::Ok);
return;
}
thread_run_flag=true; //视频传输线程
ui->pushButton_start->setText("停止屏幕共享");
}
else
{
server.close(); //关闭服务器
thread_run_flag=false;//视频传输线程
//设置按钮文本
ui->pushButton_start->setText("启动屏幕共享");
}
}
/*
* @brief Widget::timer_update
* 在线人数
*/
void Widget::timer_update()
{
//显示在线人数
ui->lcdNumber->display(fd_list.fd_count());
}
void Widget::on_toolButton_clicked()
{
close();
}
void Widget::on_toolButton_2_clicked()
{
//设置窗口最小化到托盘
this->hide();
QString title="小昌伟";
QString text="局域网屏幕共享";
tray->show();//让托盘图标显示在系统托盘上
tray->showMessage(title,text,QSystemTrayIcon::Information,1000); //最后一个参数为提示时长,默认10000,即10s
}
操作使用:
- 在需要共享屏幕设备上安装此软件,启动共享屏幕
- 其他用户连接设备输入网址如下:
本地设备测试:http://127.0.0.1:8888/?user=xlc&pass=12345678
用户设备: http://192.168.2.9:8888/?user=xlc&pass=12345678
忘记用户密码可以使用:http://127.0.0.1:8888/?GET/usr
用户获取到屏幕信息显示当前人数
用户只需在网页查看屏幕信息无需其他操作如图所示:
二、技术实现
- 使用S/c模型创建局域共享数据通道、搭建服务器模型。
#ifndef TCP_SERVER_H
#define TCP_SERVER_H
#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 tcp_server :public QTcpServer
{
public:
tcp_server();
protected:
void incomingConnection(qintptr socketDescriptor);
};
extern class SocketFdList fd_list;
#endif // TCP_SERVER_H
以上是头文件
//
#include "tcp_server.h"
#include "tcp_thread.h"
class SocketFdList fd_list;
tcp_server::tcp_server()
{
}
//重写TCP服务器的虚函数,处理新连接上的客户端
void tcp_server::incomingConnection(qintptr socketDescriptor)
{
tcp_thread *thread = new tcp_thread(socketDescriptor);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); //销毁对象
thread->start();
}
- 创建多线程任务,满足多个设备访问,满足并发实时性
#ifndef TCP_THREAD_H
#define TCP_THREAD_H
#include <QThread>
#include "tcp_server.h"
#include <QObject>
#include <QTcpSocket>
#include <QFile>
#include <QtNetwork>
#include "widget.h"
#include <QPixmap>
#include <QGuiApplication>
#include <QScreen>
extern QString pix_size;
extern bool thread_run_flag;
class tcp_thread : public QThread
{
Q_OBJECT
public:
tcp_thread(int socketDescriptor);
virtual ~tcp_thread();
void run();
QTcpSocket *tcpSocket;
int SendFileData(char *buff, QString file_path, const char *type);
int SendFileData(const char *buff,const char *type);
int SendImageData(char *buff);
char buff[1024];
private:
int socketDescriptor;
QString text;
};
#endif // TCP_THREAD_H
//
#include "tcp_thread.h"
#include <QDebug>
#include "ui_widget.h"
QString pix_size(100);
//线程运行标志
bool thread_run_flag=true;
tcp_thread::tcp_thread(int socketDescriptor)
:socketDescriptor(socketDescriptor)
{
//保存连接的套接字
fd_list.add_fd(socketDescriptor);
}
tcp_thread::~tcp_thread()
{
//卸载连接的套接字
fd_list.Del_fd(socketDescriptor);
// //关闭连接
tcpSocket->close();
delete tcpSocket;
}
void tcp_thread::run()
{
tcpSocket=new QTcpSocket;
/*使用socketDescriptor套接字初始化QAbstractSocket*/
if(!tcpSocket->setSocketDescriptor(socketDescriptor))
{
return;
}
//读取接收的数据
QString text;
if(tcpSocket->waitForReadyRead())
{
text=tcpSocket->readAll();
}
//处理浏览器的请求
if(text.contains("GET/usr", Qt::CaseInsensitive))
{
SendFileData(buff,":/usr.txt","text/html");
}
else if(text.contains("user=xlc&pass=12345678", Qt::CaseInsensitive))
{
//视频共享
SendImageData(buff);
}
}
/*
* 向浏览器响应请求数据
*/
int tcp_thread::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;
}
使用HTTP协议使客户端在浏览器获取共享设备视频流信息
/*
向客户端循环发送图片数据流
*/
int tcp_thread::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)
{
QBuffer data_buff;
QPixmap pixmap;
QScreen *screen = QGuiApplication::primaryScreen();
pixmap=screen->grabWindow(0); //获取当前屏幕的图像
//pixmap = pixmap.scaled(w,h, 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;
}
三、应用场景
在线教育:
适用于大班课、小班课等多种教育场景,老师端可以通过屏幕共享将上课需要的课件,资料共享给学生,让教学变得更加高效,提高效率节省时间。
远程演示:
在进行远程协作或操作演示时,可以通过采集屏幕和窗口的内容,直观演示具体操作步骤,帮助他人快速理解。屏幕共享允许的在线演示是组织与客户和客户互动的完美方法。借助屏幕共享应用程序,不仅销售人员可以展示他们的产品,而且他们还可以通过提供PC键盘或鼠标的远程控制来邀请客户体验软件程序等项目。这是屏幕共享工具的最佳好处之一。