Qt广告机服务器(上位机)

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 客户端列表(下位机)广告图片广播天气信息多选点播消息提醒广播日期显示模块
下位机: Qt广告机客户端(下位机)

功能

  1. 客户端列表(下位机)
  2. 广告图片广播
  3. 天气信息多选点播
  4. 消息提醒广播
  5. 日期显示模块
时间显示模块:使用自定义的AdDate类实现时钟的显示,支持动态更新时间;

客户端管理模块:使用自定义的AdTcp类实现客户端管理(即下位机),新增客户端时自动添加到客户端列表中,在客户端断开连接时自动删除客户端,支持广播消息和多选单独发送消息;

天气查询模块:使用第三方API实现通过城市编码获取天气信息,通过Weather类实现,并将获取的天气信息显示在界面的一个标签中;

广告管理模块:使用QListWidget实现广告列表,支持拖拽添加、删除广告,支持双击播放广告,使用QPixmap显示图片,支持缩放,自适应窗口大小变化;

广告命令发送模块:当客户端需要更新广告列表时,可以发送特定的命令请求,主机收到后会根据命令类型执行相应的操作,如添加、删除广告等,通过Ad_SendAction函数实现。

image.png

可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开文件选择

重载实现dragEnterEvent(拖拽)、dropEvent(拖拽放下)、resizeEvent(窗口大小改变)

发送消息历史记录及右键复制消息

结构

image.png

adSever.pro

QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

TARGET = adSever
TEMPLATE = app


SOURCES += main.cpp\
    addate.cpp \
    admsglist.cpp \
    adsever.cpp \
    adtcp.cpp \
    client.cpp \
    weather.cpp

HEADERS  += adsever.h \
    addate.h \
    admsglist.h \
    adtcp.h \
    client.h \
    tcp_MSG.h \
    weather.h

FORMS    += adsever.ui

RESOURCES += \
    res.qrc

main.cpp

#include "adsever.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // 判断当前运行环境是否为Linux或Windows。
#ifdef Q_OS_LINUX
    qDebug() << "Current OS is Linux";
#elif defined(Q_OS_WIN)
    qDebug() << "Current OS is Windows";
#else
    qDebug() << "Unknown OS";
#endif

    AdSever w;
    w.show();

    return a.exec();
}

tcp_MSG.h 共用Tcp传输信息

#ifndef TCP_MSG_H
#define TCP_MSG_H
#include<QMetaType>
#define tcp_MSG_txt_NUM 256
#define tcp_MSG_city_NUM 32
#define tcp_MSG_weather_NUM 128
#define tcp_MSG_path_NUM 128
#define tcp_MSG_photo_NUM 1280*800
#pragma pack(1)     //设置结构体为1字节对齐
typedef struct
{
    int type;// 消息类型
    char txt[tcp_MSG_txt_NUM];// 文字信息
    char city[tcp_MSG_city_NUM];// 城市
    char area[tcp_MSG_city_NUM];// 地区
    char weather[tcp_MSG_weather_NUM];// 天气
    char fileName[tcp_MSG_path_NUM];// 文件名
    int index; // 编号
    int allAd_Num;// 总数
    int fileSize;// 文件大小
}tcp_MSG;
#pragma pack()        //结束结构体对齐设置
Q_DECLARE_METATYPE(tcp_MSG)
// 实现对象的元编程功能。它可以用来定义宏,类型和函数,以支持将元数据与类型关联起来。它还可以用来实现类型安全性,类型转换和序列化功能。
//宏来注册tcp_MSG类型

enum MsgType{
    Init=0, // 初始化
    WEATHER, //天气
    MASSEGE,// 留言
    VIDEO, // 视频
    AD_add, // 添加广告
    AD_delete, // 删除广告
    Write_back,//回复
};

#pragma pack(1)     //设置结构体为1字节对齐
typedef struct
{
    int type;// 消息类型
    int state;// 状态  0不发送 1发送
    char id[32];// id
}tcp_backMSG;
#pragma pack()        //结束结构体对齐设置
Q_DECLARE_METATYPE(tcp_backMSG)
//宏来注册tcp_backMSG类型

#pragma pack(1)     //设置结构体为1字节对齐
typedef struct
{
    int type;// 消息类型
    char photo[tcp_MSG_photo_NUM];
}tcp_Image;
#pragma pack()        //结束结构体对齐设置
Q_DECLARE_METATYPE(tcp_Image)
//宏来注册tcp_Image类型

#endif // TCP_MSG_H

adsever.h 服务器

#ifndef ADSEVER_H
#define ADSEVER_H

#include <QMainWindow>
#include <QClipboard>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QMovie>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include "adtcp.h"
#include "addate.h"
#include "tcp_MSG.h"
#include "string.h"
#include "admsglist.h"
#include "weather.h"

namespace Ui {
class AdSever;
}

class AdSever : public QMainWindow
{
    Q_OBJECT

public:
    explicit AdSever(QWidget *parent = 0);
    ~AdSever();

    void dragEnterEvent(QDragEnterEvent *event)override;//拖进事件
    void dropEvent(QDropEvent *event)override;// 拖进放下事件
    void resizeEvent(QResizeEvent *event)override;//用于在窗口大小改变时处理事件
    QString GetLocalIP();// 获取本地IP
    void InitStatusBar();// 初始化底部状态栏

private slots:
    void on_clear_msg_bt_clicked();// 清空

    void on_broast_msg_bt_clicked();// 广播

    void on_set_city_bt_clicked();//设置地区

    void GUI_WarningMsg(QString title,QString text,QString buttons,QString defaultButton);//设置警报

    void on_add_ad_bt_clicked();// 添加广告

    void on_delete_ad_bt_clicked();// 删除广告

    void qListWidget_clicked(const QModelIndex &index);//广告列表点击

    void on_ad_sendAdd_bt_clicked();// 发送添加

    void on_ad_sendDelete_bt_clicked();// 发送删除

private:
    Ui::AdSever *ui;
    AdTcp *tcpsever;
    AdDate *date;
    AdMsgList *msgList;

    QPixmap pixmap;
    QVector<QString> photoPath;//存放照片相对路径的容器
    int num; //照片张数

    QLabel *mLocalIP;
};

#endif // ADSEVER_H

adsever.cpp 服务器

#include "adsever.h"
#include "ui_adsever.h"

AdSever::AdSever(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::AdSever)
{
    ui->setupUi(this);
    this->setWindowIcon(QIcon(":/server.webp"));
    ui->centralWidget->setLayout(ui->horizontalLayout_5);
    ui->horizontalLayout_5->setContentsMargins(5,5,5,5);

    date = new AdDate(ui->date_lb);//时间
    date->start();//更新时间

    tcpsever = new AdTcp(ui->client_lw);// 客户端列表(下位机)
    connect(tcpsever, SIGNAL(GUI_WarningSignal(QString,QString,QString,QString)), this, SLOT(GUI_WarningMsg(QString,QString,QString,QString)));

    msgList=new AdMsgList(ui->msg_lw);// 已经发送消息列表,实现右键复制

    {// 广告模块
        this->setAcceptDrops(true);//设置允许向窗口拖入图片
        ui->video_lb->setAlignment(Qt::AlignCenter);  //居中显示
        //自适应的label+pixmap充满窗口后,无法缩小只能放大
        ui->video_lb->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);// Ignored忽略
        //不显示行向滚动条,子项文本过长自动显示...
        ui->ad_lw->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

        num=0; //照片张数
        connect(ui->ad_lw,SIGNAL(clicked(const QModelIndex &)),this,SLOT(qListWidget_clicked(const QModelIndex &)));
        // 内容是否自动缩放,参数true自动缩放
        ui->video_lb->setScaledContents(true);//显示图片的全部

    }

    InitStatusBar();// 初始化底部状态栏
}

AdSever::~AdSever()
{
    delete ui;
}

//拖进事件
void AdSever::dragEnterEvent(QDragEnterEvent *event)
{
    // 如果文件的后缀名是jpg、jpeg、bmp或png,则接受拖放事件,否则忽略拖放事件
    QStringList acceptedFileTypes;
    acceptedFileTypes.append("jpg");
    acceptedFileTypes.append("jpeg");
    acceptedFileTypes.append("bmp");
    acceptedFileTypes.append("png");
    // 用于检查拖放的数据是否包含URL,并且获取拖放事件中的URL数量==1
    if(event->mimeData()->hasUrls()&&event->mimeData()->urls().count()==1)
    {
        // 获取拖放事件中的第一个URL的本地文件路径
        QFileInfo file(event->mimeData()->urls().at(0).toLocalFile());
        // 检查文件的后缀名是否在接受的文件类型列表中;(获取文件的后缀名,并将其转换为小写字母)
        if(acceptedFileTypes.contains(file.suffix().toLower()))
        {
            event->acceptProposedAction();//表明用户可以在窗口部件上拖放对象[接受拖放事件的操作]
        }
    }
}

// 拖进放下事件
void AdSever::dropEvent(QDropEvent *event)
{
    // 获取拖放事件中的第一个URL的本地文件路径
    QFileInfo file(event->mimeData()->urls().at(0).toLocalFile());

    qDebug()<<"绝对路径:"<<file.absoluteFilePath();
    //从文件中加载图片,参数file.absoluteFilePath()表示文件的绝对路径,load()返回一个bool值,表示是否加载成功
    if(pixmap.load(file.absoluteFilePath()))
    {
        // 将图片缩放到指定大小,参数ui->label->size()表示缩放的大小,Qt::KeepAspectRatio表示保持图片的宽高比,Qt::SmoothTransformation表示使用平滑缩放算法
        ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));

        ui->videoname_lb->setText(file.absolutePath());//显示打开的文件的绝对路径,这不包括文件名。
        photoPath.append(file.absoluteFilePath());// 把图片的路径装到容器中

        QListWidgetItem *item = new QListWidgetItem(QIcon(file.absoluteFilePath()),file.fileName());//建立文件缩小图标
        item->setToolTip(file.fileName());// tip提示
        item->setTextAlignment(Qt::AlignCenter);//设置item项中的文字位置
        ui->ad_lw->addItem(item);//把图片相对路径显示到窗口中
    }else
    {
        // 错误消息框
        QMessageBox::critical(this,tr("Error"),tr("The image file count not be read"));
    }
}

//用于在窗口大小改变时处理事件
int i=0;
void AdSever::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);//忽略编译器发出的警告,表明变量event未使用
    qDebug()<<"窗口大小改变:"<<i++;
    if(!pixmap.isNull())
    {
        qDebug()<<"";
        ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
    }
}

// 获取本地IP
QString AdSever::GetLocalIP()
{
    QList<QHostAddress> list=QNetworkInterface::allAddresses();
    foreach(QHostAddress address,list)
    {
        if(address.protocol()==QAbstractSocket::IPv4Protocol)
        {
            qDebug()<<address.toString();
            return address.toString();
        }
    }
    return "";
}

// 初始化底部状态栏
void AdSever::InitStatusBar()
{
    mLocalIP=new QLabel(this);
    mLocalIP->setMinimumWidth(230);
    QString ip = GetLocalIP();
    mLocalIP->setText("本地IP:"+tr("<font color=\"red\">%1</font>").arg(ip));

    ui->statusBar->addWidget(mLocalIP);
}

// 清空
void AdSever::on_clear_msg_bt_clicked()
{
    ui->msg_te->clear();
}

// 广播
void AdSever::on_broast_msg_bt_clicked()
{
    // 获取文本内容
    QString info = ui->msg_te->toPlainText();
    if(!info.isEmpty())// 是否空的
    {
        tcp_MSG msg={};
        msg.type=MsgType::MASSEGE;
        strcpy(msg.txt,info.toUtf8().data());// memcpy(msg.txt,info.toUtf8().data(),info.toUtf8().length());

        memset(msg.city,0,sizeof(msg.city));
        memset(msg.area,0,sizeof(msg.area));
        memset(msg.weather,0,sizeof(msg.weather));

        qDebug()<<"广播发送信息:"<<info;
        tcpsever->broadcastMsg(msg);

        QListWidgetItem *item = new QListWidgetItem(QString(QTime::currentTime().toString("hh:mm:ss")+"\t"+info));
        item->setToolTip(info);// tip提示
        item->setTextAlignment(Qt::AlignLeft);//设置item项中的文字位置
        ui->msg_lw->addItem(item);

        ui->msg_te->clear();
    }
}

//设置地区
void AdSever::on_set_city_bt_clicked()
{
    if(ui->client_lw->selectedItems().isEmpty())
    {
        QMessageBox::warning(this,"提示","请选择下位机");
        return;
    }
    if(ui->city_cb->currentText().isEmpty()||ui->distict_cb->currentText().isEmpty())
    {
        QMessageBox::warning(this,"提示","请选择城市和地区");
        return;
    }
    // 获取文本内容
    QString city = ui->city_cb->currentText();
    QString area = ui->distict_cb->currentText();
    QString weather = ui->weather_lb->text();

    tcp_MSG msg={};
    msg.type=MsgType::WEATHER;

    memset(msg.txt,0,sizeof(msg.txt));

    strcpy(msg.city,city.toUtf8().data());
    strcpy(msg.area,area.toUtf8().data());
    strcpy(msg.weather,weather.toUtf8().data());

    qDebug()<<"发送天气";
    tcpsever->MultiSelectUnicastMsg(msg);

}

//设置警报
void AdSever::GUI_WarningMsg(QString title, QString text, QString buttons, QString defaultButton)
{
    Q_UNUSED(buttons)//忽略编译器发出的警告,表明变量event未使用
    Q_UNUSED(defaultButton)
    QMessageBox::warning(this,title,text);
    return;
}

// 添加广告
void AdSever::on_add_ad_bt_clicked()
{
    QFileDialog dialog(this);//文件选择窗口
    dialog.setNameFilter(tr("Images (*.jpg *.jpeg *.bmp *.png)"));// 过滤器
    dialog.setFileMode(QFileDialog::AnyFile);//设置文件模式(文件/文件夹):任意文件,无论是否存在
    QStringList fileNames;
    if (dialog.exec())
        fileNames = dialog.selectedFiles();// 存所有选择的文件
    if(!fileNames.isEmpty())
    {
        if(pixmap.load(fileNames[0]))
        {
            qDebug()<<"文件名:"<<fileNames[0];
            ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));

            QFileInfo file(fileNames[0]);
            ui->videoname_lb->setText(file.absolutePath());//显示打开的文件的绝对路径,这不包括文件名。
            photoPath.append(file.absoluteFilePath());// 把图片的路径装到容器中
            QListWidgetItem *item = new QListWidgetItem(QIcon(file.absoluteFilePath()),file.fileName());//建立文件缩小图标
            item->setToolTip(file.fileName());// tip提示
            item->setTextAlignment(Qt::AlignCenter);//设置item项中的文字位置
            ui->ad_lw->addItem(item);//把图片相对路径显示到窗口中
        }
    }
}
// 删除广告
void AdSever::on_delete_ad_bt_clicked()
{
    int deleteNum = ui->ad_lw->row(ui->ad_lw->currentItem()); //获取当前点击的内容的行号
    if(deleteNum<0)
        return;
    QListWidgetItem *item=ui->ad_lw->takeItem(deleteNum);//删除该列表项
    delete item;//手工再释放该列表项占用的资源
    photoPath.takeAt(deleteNum);
    ui->video_lb->clear();
    qDebug()<<"删除图片:"<<deleteNum;
}

//广告列表点击
void AdSever::qListWidget_clicked(const QModelIndex &index)
{
    Q_UNUSED(index);//忽略编译器发出的警告,表明变量event未使用

    num = ui->ad_lw->row(ui->ad_lw->currentItem()); //获取当前点击的内容的行号
    qDebug()<<"点击播放图片:"<<num;
    QString tempDir;
    tempDir.clear();
    tempDir=photoPath.at(num); //从容器中找到要播放的照片的相对路径

    pixmap.load(tempDir);// 更新全局图片
    ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));//显示图片
}

// 发送添加
void AdSever::on_ad_sendAdd_bt_clicked()
{
    if(ui->client_lw->selectedItems().isEmpty())
    {
        QMessageBox::warning(this,"提示","请选择下位机");
        return;
    }
    if(ui->ad_lw->selectedItems().isEmpty())
    {
        QMessageBox::warning(this,"提示","请选择图片");
        return;
    }
    tcpsever->Ad_SendAction(MsgType::AD_add,photoPath.at(num),num,photoPath.count());
}

// 发送删除
void AdSever::on_ad_sendDelete_bt_clicked()
{
    if(ui->client_lw->selectedItems().isEmpty())
    {
        QMessageBox::warning(this,"提示","请选择下位机");
        return;
    }
    if(ui->ad_lw->selectedItems().isEmpty())
    {
        QMessageBox::warning(this,"提示","请选择图片");
        return;
    }

    tcpsever->Ad_SendAction(MsgType::AD_delete,photoPath.at(num),num,photoPath.count());
}

addate.h 时间处理

#ifndef ADDATE_H
#define ADDATE_H

#include <QTime>
#include <QDate>
#include <QLabel>
#include <QTimer>

class AdDate : public QObject
{
    Q_OBJECT
public:
    AdDate(QLabel *_mlabel, QObject *parent = 0);
    ~AdDate();
    void start();// 定时器开启
public slots:
    void updateTime();// 定时器1s超时执行一次
private:
    QLabel *mlabel;
    QTimer *mtimer;
};

#endif // ADDATE_H

addate.cpp 时间处理

#include "addate.h"

AdDate::AdDate(QLabel *_mlabel, QObject *parent):
    QObject(parent)
{
    mlabel = _mlabel;
    mtimer = new QTimer;
    connect(mtimer, SIGNAL(timeout()), this, SLOT(updateTime()));
    mlabel->setAlignment(Qt::AlignCenter);// 居中
    QFont font;
    font.setFamilies(QStringList("微软雅黑"));
    font.setPixelSize(30);
    mlabel->setFont(font);

}

AdDate::~AdDate()
{
    delete mtimer;
}

void AdDate::start()
{
     mtimer->start(1000);
}

void AdDate::updateTime()
{
    QString time = QTime::currentTime().toString("hh:mm:ss")+"\n"
            +QDate::currentDate().toString("yy/MM/dd ddd");

    mlabel->setText(time);
}

adtcp.h 客户端Socket处理

#ifndef ADTCP_H
#define ADTCP_H

#include <QTcpServer>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
#include <QList>
#include <QListWidget>
#include <QBuffer>
#include <QFileInfo>
#include <QTime>
#include <QNetworkInterface>
#include "client.h"
#include "tcp_MSG.h"




class AdTcp : public QTcpServer
{
    Q_OBJECT
public:
     AdTcp(QListWidget *_client_lw, QObject *parent = 0);
     ~AdTcp();
     void broadcastMsg(tcp_MSG msg);// 文字 广播下发
     void MultiSelectUnicastMsg(tcp_MSG msg);// 天气  多选单播下发

     void Ad_SendAction(int action,QString path,int index,int allAd_Num);// 广告发送操作

signals:
     void GUI_WarningSignal(QString title,QString text,QString buttons,QString defaultButton);//设置警报
public slots:
    void newClient();// 新的客户端连接
    void read_back();//读取客户端上传ID
    void rmClient();//删除客户端

private:
    QList<Client *> *client_list;
    QListWidget *client_lw;
    QByteArray  sendImage;
};

#endif // ADTCP_H\

adtcp.cpp 客户端Socket处理

#include "adtcp.h"
#include <QDebug>
#include<QVariant>

// QTcpSocket会自动处理大小端问题

AdTcp::AdTcp(QListWidget *_client_lw, QObject *parent) :
    QTcpServer(parent)
{
    //注册tcp_MSG类型
    qRegisterMetaType<tcp_MSG>("tcp_MSG");

    // 监听任意地址8888端口
    if(!listen(QHostAddress::Any, 8888))
    {
        //QMessageBox::warning(this, "服务器启动失败");
        close();
    }

    client_list = new QList<Client *>;
    client_lw =_client_lw;
    //设置多选项
    client_lw->setSelectionMode(QAbstractItemView::ExtendedSelection);

    // 每当有新的连接可用
    connect(this, SIGNAL(newConnection()), this, SLOT(newClient()));
    qDebug()<<"init tcp";
  //  connect(this, SIGNAL(), this, SLOT(newClient()));
  //  connect(this, SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(newClient()));
}

AdTcp::~AdTcp()
{
    delete client_list;
    close();
}

// 新的客户端连接
void AdTcp::newClient()
{
    Client *new_client = new Client;
    // 与客户端链接,动态创建socket对象
    new_client->msocket = this->nextPendingConnection();// 等待连接的//作为已连接的QTcpSocket对象,返回下一个挂起连接

    /* 每当有新的输入数据时,就会发出这个信号。
    请记住,新传入的数据只报告一次;如果您不读取所有数据,这个类会缓冲数据,您可以稍后读取它,但是除非新数据到达,否则不会发出信号。*/
    connect(new_client->msocket, SIGNAL(readyRead()), this, SLOT(read_back()));

    // 该信号在套接字断开连接时发出
    connect(new_client->msocket, SIGNAL(disconnected()), this, SLOT(rmClient()));
    client_list->append(new_client);
}

//读取客户端上传ID
void AdTcp::read_back()
{
    // 返回此信号的 发送对象
    QTcpSocket *getSocket=qobject_cast<QTcpSocket *>(sender());
    //读取缓冲区数据
    QByteArray  buffer = getSocket->readAll();//client_list->last()->msocket->readAll();// 读取最后客户端(也就是最新的)
    tcp_backMSG *msg=(tcp_backMSG *)buffer.data();        //强转为结构体,需要用结构体指针接收
    qDebug()<<"消息类型"<<msg->type;

    if(msg->type==MsgType::Init&&msg->state==0)
    {
        QString id(msg->id);
        client_list->last()->id = id;
        client_lw->addItem(id);// 添加项到 客户端列表(下位机)
    }
    else if(msg->type==MsgType::Write_back&&msg->state==1)
    {
        qDebug()<<"收到回复";
        // 记录开始时间
        QTime startTime = QTime::currentTime();

        getSocket->write(sendImage);
        // 用于等待发送的数据被写入到网络套接字描述符中,这样就可以确保数据完全被发送出去,
        //该函数会阻塞程序继续执行,直到数据被完全发送出去
        if (getSocket->waitForBytesWritten())
        {
            getSocket->flush(); //释放socket缓存
        }
        // 记录结束时间
        QTime endTime = QTime::currentTime();
        // 计算运行时间
        int milsec = startTime.msecsTo(endTime);
        qDebug()<<"发送消耗时间"<<milsec<<"毫秒";
    }
}

// 文字 广播下发
void AdTcp::broadcastMsg(tcp_MSG msg)
{
    QByteArray  sendTcpData;
    //使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
    //直接sizeof(senddata)内存会变小,设置了对齐方式解决
    sendTcpData.resize(sizeof(tcp_MSG));

    //将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
    memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));
    int count = client_list->size();
    qDebug()<<"广播下发数量:"<<count;
    for(int i = 0; i< count; i++)
    {
        client_list->at(i)->msocket->write(sendTcpData);//往套接字缓存中写入数据,并发送
        client_list->at(i)->msocket->flush(); //释放socket缓存
    }
}

// 天气 多选单播下发
void AdTcp::MultiSelectUnicastMsg(tcp_MSG msg)
{
    QByteArray  sendTcpData;
    //使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
    //直接sizeof(senddata)内存会变小,设置了对齐方式解决
    sendTcpData.resize(sizeof(tcp_MSG));

    //将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
    memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));

    if(client_list->size()!=client_lw->count())
    {
        emit GUI_WarningSignal("提示","客户端存储数量出错",NULL,NULL);
        return;
    }
    QList<QListWidgetItem *> selectedItems = client_lw->selectedItems();
    for (auto item : selectedItems)
    {
        int index = client_lw->row(item);
        qDebug() << "选定 item:" << item->text();
        client_list->at(index)->msocket->write(sendTcpData);
        client_list->at(index)->msocket->flush();
    }
}

// 广告发送操作
void AdTcp::Ad_SendAction(int action, QString path, int index, int allAd_Num)
{
    QByteArray  sendTcpData;
    //使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
    //直接sizeof(senddata)内存会变小,设置了对齐方式解决
    sendTcpData.resize(sizeof(tcp_MSG));
    tcp_MSG msg={};
    msg.type=action;

    QFileInfo file(path);
    strcpy(msg.fileName,file.fileName().toUtf8().data());

    msg.index=index;
    msg.allAd_Num=allAd_Num;

    if(action==MsgType::AD_add)
    {
        QImage image(path);
        QByteArray byteArray;
        QBuffer buffer(&byteArray);
        buffer.open(QIODevice::WriteOnly);
        //获取文件的后缀名,并将其转换为大写字母
        qDebug()<<"图片格式:"<<file.suffix().toUpper().toStdString().c_str();
        image.save(&buffer,file.suffix().toUpper().toStdString().c_str()); //将图片保存为PNG/JPG等格式

        sendImage = byteArray.toBase64();
         qDebug()<<"图片size:"<<sendImage.size();
        msg.fileSize=sendImage.size();
        buffer.close();
    }
    else if(action==MsgType::AD_delete)
    {

    }

    //将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
    memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));

    QList<QListWidgetItem *> selectedItems = client_lw->selectedItems();
    for (auto item : selectedItems)
    {
        int index = client_lw->row(item);
        qDebug() << "发送 item:" << item->text();
        client_list->at(index)->msocket->write(sendTcpData);
        client_list->at(index)->msocket->flush();
    }
}


// 删除客户端
void AdTcp::rmClient()
{
    qDebug()<<"客户端断开";
    // 返回此信号的 发送对象
    QTcpSocket *getSocket=qobject_cast<QTcpSocket *>(sender());

//    if(client_list->first()->msocket->isSequential())
//    {
//        client_lw->clear();
//    }
    for(int i=0;i<client_list->count();i++)
    {
        if(getSocket==client_list->at(i)->msocket)
        {
            if(client_list->count()==client_lw->count())
            {
                QListWidgetItem *item=client_lw->takeItem(i);//删除该列表项
                delete item;//手工再释放该列表项占用的资源
            }
            client_list->takeAt(i);
            getSocket->deleteLater();//延时释放
        }
    }
}

client.h 客户端信息类

#ifndef CLIENT_H
#define CLIENT_H

#include <QObject>
#include <QString>
#include <QTcpSocket>

class Client
{
 //   Q_OBJECT
public:
    explicit Client(QObject *parent = 0);
    QString id;
    QTcpSocket *msocket;


};

#endif // CLIENT_H

client.cpp 客户端信息类

#include "client.h"

Client::Client(QObject *parent)
{
    Q_UNUSED(parent)//忽略编译器发出的警告,表明变量event未使用
    msocket = new QTcpSocket;
}

admsglist.h 信息记录模块

#ifndef ADMSGLIST_H
#define ADMSGLIST_H

#include <QListWidget>
#include <QObject>
#include <QAction>
#include <QClipboard>
#include <QDebug>
#include <QApplication>
class AdMsgList : public QObject
{
    Q_OBJECT
public:
    AdMsgList(QListWidget *_mlistWidget, QObject *parent = 0);
    ~AdMsgList();
private slots:
    void onCopyTriggered();// 触发Copy
private:
    QListWidget *mlistWidget;

};

#endif // ADMSGLIST_H

admsglist.cpp 信息记录模块

#include "admsglist.h"

AdMsgList::AdMsgList(QListWidget *_mlistWidget, QObject *parent):
    QObject(parent)
{
    mlistWidget=_mlistWidget;
    //不显示行向滚动条,子项文本过长自动显示...
    mlistWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    mlistWidget->setContextMenuPolicy(Qt::ActionsContextMenu);//设置右键菜单
    QAction *copyAction = new QAction("Copy", mlistWidget);
    copyAction->setShortcut(QKeySequence::Copy); //设置快捷键
    mlistWidget->addAction(copyAction);
    //连接action的triggered信号和槽函数
    connect(copyAction, SIGNAL(triggered()), this, SLOT(onCopyTriggered()));
}

AdMsgList::~AdMsgList()
{

}
// 触发Copy
void AdMsgList::onCopyTriggered()
{
    //获取当前action
    QAction *action = qobject_cast<QAction*>(sender());
    if (action&&mlistWidget->currentItem())
    {
        //获取要复制的内容
        QString content = mlistWidget->currentItem()->toolTip();
        qDebug()<<mlistWidget->currentRow()<<"行,获取要复制的内容:"<<content;
        //将内容复制到剪贴板
        QClipboard *clipboard = QApplication::clipboard();
        clipboard->setText(content);
    }
}

weather.h 天气信息模块

#ifndef WEATHER_H
#define WEATHER_H

#include <QObject>
#include <QLabel>
#include <QComboBox>

class Weather : public QObject
{
    Q_OBJECT
public:
    explicit Weather(QLabel *_wlabel,QComboBox *_cityComboBox,QComboBox *_areaComboBox,QObject *parent = 0);

signals:

public slots:
    void showWeather(QString weather);

private:
    QLabel *weather_label;
    QComboBox *city_comboBox;
    QComboBox *area_comboBox;
};

#endif // WEATHER_H

weather.cpp 天气信息模块

#include "weather.h"

Weather::Weather(QLabel *_wlabel,QComboBox *_cityComboBox,QComboBox *_areaComboBox,QObject *parent) :
    QObject(parent)
{
    weather_label = _wlabel;
    city_comboBox = _cityComboBox;
    area_comboBox = _areaComboBox;
}

void Weather::showWeather(QString weather)
{
    weather_label->setText(weather);
}

ui

效果

image.png
image.png

image.png

源码

Gitee:08Qt-Advertising-Machine Qt广告机

难点

  1. QTcpSocket发送和接收使用 自定义 信息结构体,
结构体需要1字节对齐 ,参考 Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)
  • 发送
QByteArray  sendTcpData;
//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
//直接sizeof(senddata)内存会变小,设置了对齐方式解决
sendTcpData.resize(sizeof(tcp_MSG));

//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));

socket->write(sendTcpData);
  • 接收
//读取缓冲区数据
QByteArray  buffer = readAll();

tcp_MSG *msg=(tcp_MSG *)buffer.data();        //强转为结构体,需要用结构体指针接收
  1. 发送图片
QTcpSocket 的默认缓存区大小是 64KB(65536字节)

图片一般比较大,需要循环接收,校验发送长度和接收长度

因为QTcpSocket是一个基于字节流的套接字,它只能传输二进制数据。而图片文件是一种二进制文件,不能直接传输。因此,需要将图片文件转换为一种可传输的文本格式,如Base64编码。

Base64编码是一种将二进制数据转换为ASCII字符的编码方式。它将每3个字节转换为4个字符,因此可以将任何二进制数据转换为一种文本格式,方便传输

本项目发送图片,使用 服务器下发消息类型,客户端回复并开启图片接收; 服务器 把图片发给 回复的客户端;

  • 发送
*在adtcp.cpp的Ad_SendAction()中先下发消息类型
{
    QFileInfo file(path);
    QImage image(path);
    QByteArray byteArray;
    QBuffer buffer(&byteArray);
    buffer.open(QIODevice::WriteOnly);
    
    //获取文件的后缀名,并将其转换为大写字母
    image.save(&buffer,file.suffix().toUpper().toStdString().c_str()); //将图片保存为PNG/JPG等格式
    
    sendImage = byteArray.toBase64();
    
    msg.fileSize=sendImage.size();
    
    buffer.close();
}

*在adtcp.cpp的read_back()中先下发消息类型
{
    // 返回此信号的 发送对象
    QTcpSocket *getSocket=qobject_cast<QTcpSocket *>(sender());
    //读取缓冲区数据
    QByteArray  buffer = getSocket->readAll();//client_list->last()->msocket->readAll();// 读取最后客户端(也就是最新的)
    tcp_backMSG *msg=(tcp_backMSG *)buffer.data();        //强转为结构体,需要用结构体指针接收
    
    else if(msg->type==MsgType::Write_back&&msg->state==1)
    {
        getSocket->write(sendImage);
        // 用于等待发送的数据被写入到网络套接字描述符中,这样就可以确保数据完全被发送出去,
        //该函数会阻塞程序继续执行,直到数据被完全发送出去
        if (getSocket->waitForBytesWritten())
        {
            getSocket->flush(); //释放socket缓存
        }
    }
 }
  • 接收
* 在adsocket.cpp的readMsg()先回复并开启图片接收
{
    //读取缓冲区数据
    QByteArray  buffer = readAll();
    tcp_MSG *msg=(tcp_MSG *)buffer.data();        //强转为结构体,需要用结构体指针接收
    needFileSize=msg->fileSize;// 需要接收图片大小
    
    QByteArray  sendTcpData;
    //使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
    //直接sizeof(senddata)内存会变小,设置了对齐方式解决
    sendTcpData.resize(sizeof(tcp_backMSG));
    tcp_backMSG backMsg={};
    strcpy(backMsg.id,id.toUtf8().data());
    backMsg.state=1;
    backMsg.type=MsgType::Write_back;
    //将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
    memcpy(sendTcpData.data(),&backMsg,sizeof(tcp_backMSG));
    
    this->write(sendTcpData);// 回复
}

* 在adsocket.cpp的readMsg()图片接收
{
    QByteArray  buffer = readAll();
    qDebug()<<"需要接收大小:"<<needFileSize;
    currentReceiveSize+=buffer.size();
    currentReceiveByte+=buffer;//当前累计接收大小
    
    if(needFileSize==currentReceiveSize)
    {qDebug()<<"图片接收完成";
       QByteArray Ret_bytearray = QByteArray::fromBase64(currentReceiveByte);
       QBuffer buffer(&Ret_bytearray);
       buffer.open(QIODevice::WriteOnly);
       QPixmap imageresult;
       imageresult.loadFromData(Ret_bytearray);

       QImage pic=imageresult.toImage();
    }
}
相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
5月前
|
编解码 监控 网络协议
采用Qt+Live555搭建RTSP服务器
Live555是一个跨平台的流媒体开发库,支持多种流媒体协议,包括RTSP、SIP、RTP等,可以帮助我们快速实现视频流的传输和处理。
352 1
采用Qt+Live555搭建RTSP服务器
|
4月前
【qt】客户端连接到服务器
【qt】客户端连接到服务器
68 0
|
4月前
|
网络协议
【qt】TCP服务器如何停止监听?
【qt】TCP服务器如何停止监听?
45 0
|
4月前
|
网络协议
【qt】TCP的监听 (设置服务器IP地址和端口号)
【qt】TCP的监听 (设置服务器IP地址和端口号)
243 0
|
7月前
|
JSON 网络协议 开发工具
基于Qt实现的TCP端口数据转发服务器
基于Qt实现的TCP端口数据转发服务器
78 0
基于Qt实现的TCP端口数据转发服务器
|
7月前
|
网络协议
qt5-Tcp端口转发服务器更新
qt5-Tcp端口转发服务器更新
33 0
|
Ubuntu 网络安全 Docker
[笔记]Qt5+FFMpeg+Opencv 实现实时美颜直播推流《一》基础知识以及直播服务器配置
[笔记]Qt5+FFMpeg+Opencv 实现实时美颜直播推流《一》基础知识以及直播服务器配置
171 0
|
编解码 开发框架 监控
Qt搭建RTSP服务器
一、项目背景 随着物联网技术不断发展,视频监控系统在各个领域的应用越来越广泛。其中,RTSP(Real Time Streaming Protocol)是一种常用的流媒体传输协议,可以实现对实时音视频数据的传输和播放。为了实现视频监控系统的网络化和智能化,需要开发一个基于RTSP协议的视频流服务器,能够接收前端设备的视频流,并提供RTSP协议的服务,方便客户端进行实时的视频浏览、回放等操作。 在开发过程中,为了提高开发效率、减少开发难度和成本,同时具备良好的可扩展性和可维护性,我选择使用Qt和Live555库来搭建RTSP服务器。Qt是一个跨平台的C++应用程序开发框架,具有完善的GUI界
260 0
|
网络协议 索引
|
5月前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
191 1
Qt(C++)开发一款图片防盗用水印制作小工具