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

news2024/11/6 7:28:49

目录

  • 功能
  • 结构
  • adSever.pro
  • main.cpp
  • tcp_MSG.h 共用Tcp传输信息
  • adsever.h 服务器
  • adsever.cpp 服务器
    • addate.h 时间处理
    • addate.cpp 时间处理
    • adtcp.h 客户端Socket处理
    • adtcp.cpp 客户端Socket处理
    • client.h 客户端信息类
    • client.cpp 客户端信息类
    • admsglist.h 信息记录模块
    • admsglist.cpp 信息记录模块
    • weather.h 天气信息模块
    • weather.cpp 天气信息模块
  • ui
  • 效果
  • 源码
  • 难点

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

功能

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

可以显示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();
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/398065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jupyter的使用

1.安装 安装过程看这篇记录。 安装 2.如何启动 环境搭建好后&#xff0c;本机输⼊jupyter notebook命令&#xff0c;会⾃动弹出浏览器窗⼝打开 Jupyter Notebook # 进⼊虚拟环境 workon ai(这个是虚拟环境的名称) # 输⼊命令 jupyter notebook本地notebook的默认URL为&…

宝藏级BI数据可视化功能|图表联动分析

在浏览其他人的BI数据可视化报表时&#xff0c;经常会发现这样的一个现象&#xff0c;点一下上一张数据可视化图表中的某个门店&#xff0c;下一张图表将立即针对该门店展开数据可视化分析。这是什么效果&#xff1f;怎么实现&#xff1f;BI软件中还有多少宝藏级BI数据可视化功…

Oracle表分区的创建、新增、拆分

Oracle中为了方便管理、查询数据当数据量大于500w或者2G时最好用分区表&#xff0c;常见的一种是使用时间作为分区。 分区表添加新的分区有 2 种情况&#xff1a; (1) 原分区里边界是 maxvalue 或者 default。 这种情况下&#xff0c;我们需要把边界分区 drop 掉&#xff0c;加…

好的计划是成功的一半,如何制定项目计划?

好的计划是成功的一半&#xff0c;不好的计划会使项目一步步失败&#xff0c;任何事情&#xff0c;要取得成功&#xff0c;离不开一个科学合理的计划。 计划是为了实现项目所提出的各项目标&#xff0c;每一项任务都是针对某一个特定目标的&#xff0c;因此&#xff0c;一项计划…

计算机视觉手指甲标注案例

关键点标注是指识别和标注图像或视频中特定的相关点或区域的过程。在机器学习行业&#xff0c;它经常被用来训练计算机视觉模型&#xff0c;以执行诸如物体检测、分割和跟踪等任务。 关键点注释可用于以下应用&#xff1a; 面部关键点检测&#xff1a;识别图像中人脸上的眼睛…

12.SpringSecurity中OAuth2.0的实现

一、OAuth2.0介绍 1.概念说明 https://oauth.net/2/ 先说OAuth&#xff0c;OAuth是Open Authorization的简写。   OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAuth的授权不会使第三方触及到用户的帐号信息&#xff08;如…

[league/glide]两行代码实现一套强大的图片处理HTTP服务

只要两行代码&#xff0c;就能实现类似对象存储云提供的基于参数的图片处理&#xff0c;比如裁剪、放大、水印、旋转等等。 我们经常使用第三方的对象存储服务&#xff0c;比如七牛云或阿里云&#xff0c;他们都提供了“智能媒体服务”&#xff0c;其实就是在链接上加上各种参…

Vue:(三十四)Vuex及其属性

Vuex的学习更多是代码了&#xff0c;所以就放在一起了&#xff0c;接下来大概说一下吧。概念&#xff1a;专门在Vue中实现集中式状态&#xff08;数据&#xff09;管理的一个Vue插件&#xff0c;对Vue应用中多个组件的共享状态进行集中式的管理&#xff08;读/写&#xff09;&a…

【Git】P4 Git 远程仓库(2)克隆,抓取与拉取

Git 克隆&#xff0c;拉取与抓取git 克隆 clonegit 拉取 fetch、合并 mergegit 抓取 pullgit 克隆 clone 克隆的使用场景很少&#xff0c;举个例子&#xff0c;老板给你一个任务&#xff1a;这个服务的 bug 由你来解决&#xff1a;那么你的第一步就是从云端克隆到本地&#xf…

VMware 搭建 Linux 系统

前言 使用 VMware Workstation 17 Pro 基于CentOS 7.9 镜像搭建 K8S 一主多从本地虚拟服务器环境 主机名IP配置k8s-master192.168.179.214核CPU 8G内存 20G硬盘k8s-node1192.168.179.224核CPU 8G内存 20G硬盘k8s-node2192.168.179.234核CPU 8G内存 20G硬盘VMware 下载安装 VMw…

Spark读取JDBC调优

Spark读取JDBC调优&#xff0c;如何调参一、场景构建二、参数设置1.灵活运用分区列实际问题&#xff1a;工作中需要读取一个存放了三四年历史数据的pg数仓表&#xff08;缺少主键id&#xff09;&#xff0c;需要将数据同步到阿里云 MC中&#xff0c;Spark在使用JDBC读取关系型数…

案例13-localStorage的使用分析

1、背景介绍 大家看下边的逻辑是否能看明白呢&#xff1f; 前端在调用后端接口获取某一个人的评论次数、获赞次数、回复次数。调用之后判断后端返回过来的值。如果返回回来的值是0的话&#xff0c;从缓存中获取对应的值&#xff0c;如果从缓存中获取的评论次数为空那么其他两…

数据结构——线性数据结构(C语言实现单链表详解)

什么是单链表&#xff1f; 单链表就是一种线性的链式数据结构。单链表通过节点来存储线性数据的&#xff0c;单链表不要求连续的物理空间来存储数据。但是&#xff0c;单链表在逻辑结构上是连续的。通常&#xff0c;会有一个头指针指向单链表的首结点因为单链表的结点会存储一…

【云原生】持久化存储之NFS

文章目录介绍一、NFS1. 部署nfs1.1 找一台服务器作为nfs服务端1.2 检查&#xff1a;1.3 创建挂载路径1.4 在nfs服务器启动nfs服务2. 所有node节点部署nfs服务3. 测试—部署nginx应用&#xff0c;使用nfs持久网络存储二、 PV和PVC2.1 PV2.2 PVC2.3 实现流程2.4 PV&PVC挂载步…

day61-day62【代码随想录】二刷数组

文章目录前言一、有效三角形的个数【二分法】二、Pow(x, n)&#xff08;力扣50&#xff09;方法一方法二三、在 D 天内送达包裹的能力&#xff08;力扣1011&#xff09;【二分法】四、制作 m 束花所需的最少天数&#xff08;力扣1482&#xff09;【二分法】每日一题&#xff1a…

你真的知道MySQL索引组织数据的形式吗??

MySQL索引背后的数据结构前言MySQLMySQL背后的数据结构B树B树前言 好久不见,困扰了我许久的阴霾终于散去了,但是随之而来的是学校堆积如山的任务考试,这段时间不可否认我的学习效率和学习效果不是很佳,但是我之前就说过学习是需要贯穿程序猿一生的事情,流水不争先,争的是滔滔不…

Python基础 | Miniconda的安装

文章目录什么是Miniconda3Miniconda安装JupyterMiniconda运行JupyterMiniconda安装SpyderMiniconda和Anaconda对比Miniconda安装第三方库什么是Miniconda3 Miniconda是conda的免费的最小安装包。它是Anaconda的小型引导程序版本&#xff0c;仅包含了conda&#xff0c;Python&a…

【架构师】跟我一起学架构——Serverless

博客昵称&#xff1a;架构师Cool 最喜欢的座右铭&#xff1a;一以贯之的努力&#xff0c;不得懈怠的人生。 作者简介&#xff1a;一名Coder&#xff0c;软件设计师/鸿蒙高级工程师认证&#xff0c;在备战高级架构师/系统分析师&#xff0c;欢迎关注小弟&#xff01; 博主小留言…

个人收集的网站,可以参考(程序员可收藏)

程序员是一个需要不断学习的职业&#xff0c;幸运的是&#xff0c;在这个互联网时代&#xff0c;有很多渠道可以获取知识。 1在线教程 1、how2j.cn 地 址&#xff1a;https://how2j.cn/ 简 介&#xff1a;一个Java全栈开发教程网站&#xff0c;内容全面&#xff0c;简洁…

Docker Desktop安装本地Kubernetes集群

目录 下载安装说明 下载Docker Desktop windows需要开启Hyper-v 启用kubernetes kubectl配置 设置path环境变量 验证安装是否成功 实现Nginx容器的部署 按顺序进行nginx创建 Nginx的相关信息 Setup local Kubernetes cluster with Docker Desktop - DEV Community 上面…