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

下位机:Qt广告机客户端(下位机)

功能

  1. 客户端列表(下位机)
  2. 广告图片广播
  3. 天气信息多选点播
  4. 消息提醒广播
  5. 日期显示模块

时间显示模块:使用自定义的AdDate类实现时钟的显示,支持动态更新时间;

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

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

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

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

在这里插入图片描述

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

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

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

结构

在这里插入图片描述

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

在这里插入图片描述

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码

有道云:

难点

  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();
	}
}

猜你喜欢

转载自blog.csdn.net/qq_47355554/article/details/129402432