QT下TCP协议实现数据网络传输

news2025/1/23 7:19:47

QT开发框架以其跨平台的优势,在全世界IT界如雷贯耳。其封装了功能齐全的各种类,大大的提高了开发者的效率。本篇内容将介绍如何使用QT 6.4.1框架开发服务器和客户端程序,让两端能够首发消息,服务端往客户端发送文件(客户端往服务器发送类似,没有实现)。

1.运行效果

 说明:首先运行同时运行客户端和服务端程序,服务绑定端口开启服务,客户端连接服务器。然后服务器和客户端互相打招呼,然后服务器给客户端发送一首唐诗。

2.关键代码:

2.1服务端代码

2.1.1 服务端主函数

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

2.1.2 QTcpServer实现类

头文件和实现:

#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QObject>
#include <QTcpServer>

class MyTcpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyTcpServer(QObject *parent = nullptr);
    //该函数是由框架调用
    void incomingConnection(qintptr socketDescriptor);

signals:
    void newClient(qintptr socket);

};

#endif // MYTCPSERVER_H
#include "mytcpserver.h"

MyTcpServer::MyTcpServer(QObject *parent)
    : QTcpServer{parent}
{

}

//该函数是由框架调用
void MyTcpServer::incomingConnection(qintptr socketDescriptor)
{
    emit newClient(socketDescriptor);
}

2.1.3 主窗口类

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "MyTcpServer.h"
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void start(QString file);
    void sendMsg(QByteArray msg);

private slots:
    void on_start_clicked();

    void on_selectFile_clicked();

    void on_sendMsg_clicked();

    void on_transferFile_clicked();

private:
    Ui::MainWindow *ui;
    MyTcpServer *m_server;
    QLabel *m_status;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QMessageBox>
#include <QRandomGenerator>
#include <qDebug>
#include <QFileDialog>
#include <QString>
#include "sendfile.h"

//alt +回车添加自动添加头文件
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->lineEditPort->setText("9521");
    ui->lineFile->setText("C:\\Users\\Administrator\\Desktop\\OKR设定.txt");
    ui->progressBar->setRange(0, 100);
    ui->progressBar->setValue(0);

    //创建服务器socket
    m_server = new MyTcpServer(this);
    connect(m_server, &MyTcpServer::newClient, this, [=](qintptr socket) {
        ui->textEditContent->append("检测到有新的客户端连接");
        //有新连接
        m_status->setPixmap(QPixmap(":/ok.png").scaled(20, 20));
        QThread *subThread = new QThread;
        SendFile* worker = new SendFile(socket);
        worker->moveToThread(subThread);

        //子线程工作是需要主线程给其发信号通知工作,此处用来通知子线程给客户端发送文件
        connect(this, &MainWindow::start, worker, &SendFile::working);

        connect(worker, &SendFile::done, this, [=](){
            ui->textEditContent->append("检测到客户端退出");
            //客户端断开
            m_status->setPixmap(QPixmap(":/error.png").scaled(20, 20));
            qDebug() << "销毁子线程资源";
            subThread->quit(); //通知退出
            subThread->wait(); //等任务做完
            subThread->deleteLater(); //相当于对new的对象进行delete操作
            worker->deleteLater();
        });
        //不能在子线程操作ui对象,读和写都不行,需要子线程通过信号通知ui线程间接操作ui对象
        connect(worker, &SendFile::text, this, [=](QByteArray msg){
            QVector<QColor> colors = {
                Qt::red, Qt::green, Qt::black, Qt::blue,
                Qt::darkRed, Qt::cyan, Qt::magenta
            };
            int index = QRandomGenerator::global()->bounded(colors.size());
            ui->textEditContent->setTextColor(colors.at(index));
            ui->textEditContent->append(msg);
        });
        //显示客户端发来的消息
        connect(worker, &SendFile::showRecvMsg, this, [=](QByteArray msg){
            ui->textEditContent->append("收到消息:" + msg);
        });

        //通知子线程发送一行文本
        connect(this, &MainWindow::sendMsg, worker, &SendFile::sendMsg);

        connect(worker, &SendFile::updateProgressBar, this, [=](int value){
            ui->progressBar->setValue(value);
        });

        subThread->start();
      });

    //处理状态栏,new一个QLabel,可以制定父对象,也可以不指定,因为需要把
    //它设置到状态栏中,设置后其父对象默认就是状态栏,状态栏析构后就会回收QLabel
    //不用delete
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/error.png").scaled(20, 20));
    ui->statusbar->addWidget(new QLabel("状态:"));
    ui->statusbar->addWidget(m_status);
}

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


void MainWindow::on_start_clicked()
{
    unsigned short port = ui->lineEditPort->text().toUShort();
    //绑定端口等待客户端连接,有客户端连接触发信号:[signal] void QTcpServer::newConnection()
    //QT中用于通信的QTcpSocket套接字对象不能跨线程访问
    m_server->listen(QHostAddress::Any, port);
    ui->start->setDisabled(true);
}


void MainWindow::on_selectFile_clicked()
{
    QString file = QFileDialog::getOpenFileName(this);
    if (!file.isEmpty()) {
        ui->lineFile->setText(file);
    }
}


void MainWindow::on_sendMsg_clicked()
{
   QString msg = ui->lineEditMsg->text();//.toUShort();
   emit sendMsg(msg.toUtf8());
   ui->textEditContent->append("发出消息:" + msg);
}

void MainWindow::on_transferFile_clicked()
{
    if (ui->lineFile->text().isEmpty()) {
        QMessageBox::information(this, "提示", "请选择要发送的文件");
        return;
    }
    emit start(ui->lineFile->text());
}

2.1.4 网络处理线程类

#ifndef SENDFILE_H
#define SENDFILE_H

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

class SendFile : public QObject
{
    Q_OBJECT
public:
    explicit SendFile(qintptr socket, QObject *parent = nullptr);
    void working(QString file);
    void sendMsg(QByteArray msg);

signals:
    void done();
    void text(QByteArray msg);
    void showRecvMsg(QByteArray msg);
    void updateProgressBar(int value);

private:
    qintptr m_socket;
    QTcpSocket* m_tcpConn;
};

#endif // SENDFILE_H
#include "sendfile.h"
#include <QThread>
#include <QFile>
#include <qDebug>
#include <QTextCodec>

SendFile::SendFile(qintptr socket, QObject *parent)
    : QObject{parent}
{
    //传递过来了用于通信的套接字,就可以和对端通信了
    m_socket = socket;
}

void SendFile::working(QString file) {
    qDebug() << "子线程ID: " << QThread::currentThread();
    //注意:m_tcpConn的创建要在子线程中
    m_tcpConn = new QTcpSocket;
    //用文件描述符初始化m_tcpConn
    m_tcpConn->setSocketDescriptor(m_socket);

    //检测客户端断开
    connect(m_tcpConn, &QTcpSocket::disconnected, this, [=]() {
        m_tcpConn->close();
        m_tcpConn->deleteLater();
        emit done();
        qDebug() << "检测到客户端退出,我的资源也进行了销毁,再见!";
    });

    //检测客户端发来数据
    connect(m_tcpConn, &QTcpSocket::readyRead, this, [=]() {
        //接收数据并把数据发送给主线程进行显示
        QByteArray data = m_tcpConn->readAll();
        emit showRecvMsg(data);
    });

    qDebug() << "传输的文件: " << file;
    QFile opFile(file);
    bool ret = opFile.open(QFile::ReadOnly);
    if (ret) {
        emit updateProgressBar(0);
        //此处打开的是windows下的文件,windows默认是GBK编码,所以此处指定编码GBK
        QTextCodec *codec = QTextCodec::codecForName("GBK");
        qint64 fileSize = opFile.size();
        qint64 sendSize = 0;
        while (!opFile.atEnd()) {
            //自定格式发包,将发送长度转换成大端存储发送到对端
            QByteArray line = opFile.readLine();
            sendSize += line.size();
            QString strUnicode = codec->toUnicode(line);
            QByteArray utf8line = strUnicode.toUtf8();
            //以下代码写成两行比较稳定,如果写成
            //uint32_t len = qToBigEndian(utf8line.size());在某些环境转换长度就为0
            uint32_t orgLen = utf8line.size();
            uint32_t len = qToBigEndian(orgLen);
            QByteArray data((char*)&len, 4);
            data.append(utf8line);
            //发送一行数据给对端
            m_tcpConn->write(data);
            emit text(utf8line);
            emit updateProgressBar(100 * sendSize / fileSize);
            //为了减轻对端压力,休眠一会
            QThread::sleep(1);
        }
    }
    opFile.close();
}

void SendFile::sendMsg(QByteArray msg) {
    //用文件描述符初始化m_tcpConn
    m_tcpConn = new QTcpSocket;
    m_tcpConn->setSocketDescriptor(m_socket);

    //检测客户端断开
    connect(m_tcpConn, &QTcpSocket::disconnected, this, [=]() {
        m_tcpConn->close();
        m_tcpConn->deleteLater();
        emit done();
    });

    //检测客户端发来数据
    connect(m_tcpConn, &QTcpSocket::readyRead, this, [=]() {
        //接收数据并把数据发送给主线程进行显示
        QByteArray data = m_tcpConn->readAll();
        emit showRecvMsg(data);
    });

    //QT默认是UTF8编码,所以输入框是UTF8编码,此处指定编码方式为UTF8
    QTextCodec *codec = QTextCodec::codecForName("UTF8");
    QString strUnicode = codec->toUnicode(msg);
    QByteArray utf8line = strUnicode.toUtf8();
    uint32_t orgLen = utf8line.size();
    uint32_t len = qToBigEndian(orgLen);
    //拼接头:表示长度
    QByteArray data((char*)&len, 4);
    //拼接发送内容
    data.append(utf8line);
    m_tcpConn->write(data);
}

2.1.5 pro工程文件

QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat
CONFIG += c++17 console

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp \
    mytcpserver.cpp \
    sendfile.cpp

HEADERS += \
    mainwindow.h \
    mytcpserver.h \
    sendfile.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

RESOURCES += \
    res.qrc

2.2客户端代码

2.2.1 主函数

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

2.2.2 主窗口类

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void startConnect(QString, unsigned short);
    void disConnect();
    void sendMsg(QByteArray msg);

private slots:
    void on_connServer_clicked();

    void on_sendMsg_clicked();

    void on_disconnServer_clicked();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QMessageBox>
#include <QRandomGenerator>
#include "recvfile.h"

//多线程中,主线程负责相应ui事件,创建子线程,子线程处理服务端发过来的数据
//QT中通信的套接字是不能跨线程访问的,所以客户端中用于通信的套接字是不能在
//主线程创建的,需要在子线程创建通讯套接字,主线程把ip和端口传输给子线程
//子线程new一个QTcpSocket的对象,然后子线程用这个通讯套接字接受服务端
//发过来的数据
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->IP->setText("127.0.0.1");
    ui->port->setText("9521");
    ui->disconnServer->setDisabled(true);
    //创建子线程
    QThread *subThread = new QThread;
    //重点说明:此处new RecvFile时候不能给其指定父对象
    //否则worker移动到子线程就会不生效,谨记谨记!!!
    RecvFile* worker = new RecvFile;
    worker->moveToThread(subThread);
    connect(this, &MainWindow::startConnect, worker, &RecvFile::connectServer);
    //连接服务端成功
    connect(worker, &RecvFile::connectOk, this, [=]() {
        //QMessageBox::information(this, "提示", "成功连接了服务器");
        ui->content->append("成功连接了服务器");
        ui->disconnServer->setDisabled(false);
        ui->connServer->setDisabled(true);
    });
    connect(worker, &RecvFile::message, this, [=](QByteArray msg) {
        QVector<QColor> colors = {
            Qt::red, Qt::green, Qt::black, Qt::blue,
            Qt::darkRed, Qt::cyan, Qt::magenta
        };
        int index = QRandomGenerator::global()->bounded(colors.size());
        ui->content->setTextColor(colors.at(index));
        ui->content->append("收到消息:" + msg);
    });
    connect(worker, &RecvFile::gameOver, this, [=]() {
        qDebug() << "主线程销毁子线程";
        subThread->quit();
        subThread->wait();
        subThread->deleteLater();
        worker->deleteLater();
    });
    connect(worker, &RecvFile::disconnected, this, [=] {
        ui->content->append("与服务器断开了连接");
        ui->disconnServer->setDisabled(true);
        ui->connServer->setDisabled(false);
    });
    //断开服务器连接
    connect(this, &MainWindow::disConnect, worker, &RecvFile::disConnect);
    //给服务器发消息
    connect(this, &MainWindow::sendMsg, worker, &RecvFile::sendMsg);
    //start之后,线程仍然不能正常工作,仍然需要信号触发
    subThread->start();
    qDebug() << "主线程id:" << QThread::currentThread();
}

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


void MainWindow::on_connServer_clicked()
{
    QString ip = ui->IP->text();
    unsigned short port = ui->port->text().toUShort();
    emit startConnect(ip, port);
}

void MainWindow::on_sendMsg_clicked()
{
    QString msg = ui->msg->text();
    emit sendMsg(msg.toUtf8());
    ui->content->append("发出消息:" + msg);
}

void MainWindow::on_disconnServer_clicked()
{
    emit disConnect();
    ui->disconnServer->setDisabled(true);
    ui->connServer->setDisabled(false);
}

2.2.3 处理网络数据的子线程类

#ifndef RECVFILE_H
#define RECVFILE_H

#include <QByteArray>
#include <QObject>
#include <QTcpSocket>

class RecvFile : public QObject
{
    Q_OBJECT
public:
    explicit RecvFile(QObject *parent = nullptr);
    void connectServer(QString ip, unsigned short port);
    void dealData();
    void disConnect();
    void sendMsg(QByteArray msg);

signals:
    void connectOk();
    void message(QByteArray msg);
    void gameOver();
    void disconnected();

private:
    QTcpSocket *m_tcpScoket;
};

#endif // RECVFILE_H
#include "recvfile.h"
#include <QThread>
#include <QtEndian>

RecvFile::RecvFile(QObject *parent)
    : QObject{parent}
{

}

void RecvFile::connectServer(QString ip, unsigned short port) {
    qDebug() << "子线程ID:" << QThread::currentThread();
    m_tcpScoket = new QTcpSocket;
    //该函数是非阻塞函数,所有需要connect来注册回调,告诉我连接ok
    m_tcpScoket->connectToHost(QHostAddress(ip), port);
    connect(m_tcpScoket, &QTcpSocket::connected, this, &RecvFile::connectOk);
    //连接之后怎么怎么知道有数据到来呢,继续connect连接来通知我,槽使用匿名槽函数
    connect(m_tcpScoket, &QTcpSocket::readyRead, this, [=](){
        //QByteArray all = m_tcpScoket->readAll();
        //emit message(all);
        dealData();
        //emit gameOver();
    });
    connect(m_tcpScoket, &QTcpSocket::disconnected, this, &RecvFile::disconnected);
}

void RecvFile::dealData() {
    unsigned int totalBytes = 0;
    unsigned int recvBytes = 0;
    QByteArray block;

    if (0 == m_tcpScoket->bytesAvailable()) {
        qDebug() << "没有数据";
        return;
    }
    if (m_tcpScoket->bytesAvailable() >= sizeof(int)) {
        QByteArray head = m_tcpScoket->read(sizeof(int));
        //网络字节序转小端
        totalBytes = qFromBigEndian(*(int*)head.data());
        qDebug() << "接收数据长度::" << totalBytes;
    }
    else {
        return;
    }
    while (totalBytes -recvBytes > 0 && m_tcpScoket->bytesAvailable()) {
        block.append(m_tcpScoket->read(totalBytes - recvBytes));
        recvBytes = block.size();
    }
    if (totalBytes == recvBytes) {
        emit message(block);
    }
    //如果还有数据继续读取
    if (m_tcpScoket->bytesAvailable() > 0) {
        dealData();
        qDebug() << "递归调用数据接收";
    }
}

void RecvFile::disConnect() {
    //关闭连接,销毁自己
    m_tcpScoket->close();
    //m_tcpScoket->deleteLater();
}

//给服务器发消息
void RecvFile::sendMsg(QByteArray msg) {
    m_tcpScoket->write(msg);
}

3.开发注意事项

(1)不能在子线程直接操作ui对象,而是通过发送信号给主线程间接操作ui对象

(2)QT处理大端和小端字节序的问题,提供了如下两组四个函数:

  组一(工作于小端机器):  qToBigEndian  (小端转大端)  qFromBigEndian (大端转小端)
  组二(工作于大端机器): qToLittleEndian  (大端转小端)  qFromLittleEndian (小端转大端)

其中组一用在小端机器上,组二用在大端机,简单记忆后缀是BigEndian工作在小段机器,后缀是LittleEndian工作在大端机器,工作机器刚好和后缀相反。

(3)QT有父子对象树机制来回收内存,父对象析构时会先析构子对象。

(4)多线程中,主线程负责相应ui事件,创建子线程,子线程处理服务端发过来的数据QT中通信的套接字是不能跨线程访问的,所以客户端中用于通信的套接字是不能在主线程创建的,需要在子线程创建通讯套接字,主线程把ip和端口传输给子线程子线程new一个QTcpSocket的对象,然后子线程用这个通讯套接字接受服务端发过来的数据

最后附上源码下载地址: https://download.csdn.net/download/hsy12342611/87178417

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

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

相关文章

Spark在Yarn集群的两种提交模式

目录 一.Yarn Client(yarn的客户端模式) 二.Yarn Cluster(yarn的集群节点模式) 三.两者的差异 一.Yarn Client(yarn的客户端模式) 第一步&#xff1a;Driver端会在提交的本地机上运行 第二步&#xff1a;Driver端启动后会跟ResourceManager(RM)进行通信,申请启动一个Applic…

Linux安装Samba服务,基于Fedora

Linux安装Samba服务&#xff0c;基于Fedora1 安装samba服务2 启动samba服务3 更改配置信息4 使用windows系统进行连接5 其他说明1 安装samba服务 1 关闭防火墙及关闭防火墙开机自启 [whs02fedora ~]$ &#xff1a;sudo systemctl stop firewalld.service [whs02fedora ~]$ &a…

splay树:hdu4453 Looploop

题目链接如下&#xff1a; Problem - 4453 主要是要对区间操作和这种splay树的性质比较清楚。 关于区间我们设立两个额外节点&#xff0c;用来设立最开始的左右区间。 性质方面&#xff0c;其实就是二叉搜索树的性质&#xff0c;这里的体现就是中序遍历就是顺时针访问输入数…

《统计学习方法》 第十四章 聚类方法

聚类方法 1.聚类是针对给定的样本&#xff0c;依据它们属性的相似度或距离&#xff0c;将其归并到若干个“类”或“簇”的数据分析问题。一个类是样本的一个子集。直观上&#xff0c;相似的样本聚集在同类&#xff0c;不相似的样本分散在不同类。 2.距离或相似度度量在聚类中…

压力传感器

压力传感器 压力传感器是最常用的一种传感器&#xff0c;其应用范围有各种工业互通环境&#xff0c;涉及航空&#xff0c;航天&#xff0c;军工&#xff0c;石化&#xff0c;电力等。按照不同的测试&#xff0c;压力类型可分表压传感器&#xff0c;差压传感器&#xff0c;绝压…

现代密码学导论-19-基于伪随机函数的CPA安全

目录 3.5.2 基于伪随机函数的CPA安全 基于伪随机函数的加密示意图 CONSTRUCTION 3.28 构造基于伪随机函数的CPA安全的加密方案 THEOREM 3.29 方案3.28是CPA安全的 THEOREM 3.29 的证明 3.5.2 基于伪随机函数的CPA安全 基于伪随机函数的加密示意图 CONSTRUCTION 3.28 构造…

历史中的密码

角色 发送者、接收者和窃听者 当某个人向另一个人发送信息时&#xff0c;发出信息的人称为发送者&#xff0c;而收到信息的人称为接收者&#xff0c;被发送的信息有时也统称为消息&#xff08; message )。 窃听者 Eve 并不一定是人类&#xff0c;有可能是安装在通信设备上的某…

【JVM】jvm中的方法区简介

jvm中的方法区简介一、JVM体系结构二、方法区是什么&#xff1f;三、方法区能干什么&#xff1f;四、方法区总结一、JVM体系结构 二、方法区是什么&#xff1f; 本文所讲内容在上图中处于运行时数据区内的左侧部分&#xff0c;即 Method Area&#xff08;方法区&#xff09;&a…

REHL7.6静默安装Oracle19C

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61…

【轨迹跟踪】基于matlab拓展卡尔曼滤波时序四旋翼无人机状态跟踪【含Matlab源码 2246期】

⛄一、拓展卡尔曼滤波时序四旋翼无人机状态跟踪 卡尔曼滤波算法为获得最优估计和最小误差方差&#xff0c;将从目标模型中得到的测量值一步步递推&#xff0c;实时获取新时刻的状态估计值。 假设目标状态方程和观测方程分别为&#xff1a; 其中&#xff0c;k为离散时间&…

投入产出公开数据集:世界投入产出表(1995-2014)、全国投入产出表(1990-2018)、分省市投入产出表(1997-2017)

一、数据介绍 数据名称&#xff1a;世界、全国、各省-投入产出表 数据年份&#xff1a;世界投入产出表(1995-2014)、全国投入产出表(1990-2018)、分省市投入产出表(1997-2017) 数据来源&#xff1a;WIOD、自计算 ① 世界投入产出表&#xff08;1995-2014&#xff09; downlo…

用JSX来写Vue3,瞬间找到React 的感觉

Ⅰ. vue3 的 JSX 写法 对于熟悉react 的小伙伴, 可以通过 jsx 来 做 vue3喜欢 jsx 写法做 vue&#xff0c;代码结构更加美观&#xff0c;让我们一起来踩坑 &#x1f447; 文章目录Ⅰ. vue3 的 JSX 写法Ⅱ. JSX 安装和配置1. 通过 webpack 构建的2. 通过 vite 构建的Ⅳ. JSX 的…

Yocto buildhistory介绍

Yocto buildhistory介绍 在yocto中会频繁的编译修改镜像&#xff0c;当多人多次修改镜像的时候会导致镜像难以维护&#xff0c;我们希望能有一个类似git一样的工具能够显示每次编译的差异性修改&#xff0c;这样当我想要回退到某个日期的某个镜像时能够清晰的知道镜像内部的具…

Dockerfile文件详解

组成部分 说明 基础镜像信息 使用 FROM 关键字指定基础镜像信息&#xff0c;必须是 Dockerfile 文件的第1条指令。 维护者信息 使用 MAINTAINER 关键字指定&#xff0c;可以使用 Dockerfile 文件创建者的姓名或者电子邮件作为维护者信息。 镜像操作指令 每执行一条镜像操…

vue3+Element-plus el-select 下拉选择 多选增加全选封装组件

一、效果图&#xff08;含适用于条件查询组件中使用&#xff09; 二、参数配置 1、代码示例&#xff1a; <t-selectplaceholder"请选择工序"v-model"selectVlaue":optionSource"state.stepList"valueKey"label"change"selec…

部署SpringBoot+Vue3 项目实战,打造企业级在线办公系统

文章目录一、安装docker二、安装2.1. 安装mysql2.2. 安装MongoDB2.3. 安装Redis程序2.4. 安装RabbitMQ2.5. 在云主机上面开放端口三、部署后端项目3.1. 下载JDK镜像3.2. 部署工作流项目3.3. 部署emos-api项目四、在Docker中部署前端项目4.1. 修改前端代码4.2. 打包VUE项目4.3. …

【Hack The Box】linux练习-- time

HTB 学习笔记 【Hack The Box】linux练习-- time &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月24日&#x1f334; &#x1f36d…

Android~Compose脚手架和Toast

系列文章目录 Android~Compose初探Android~Compose之自定义ViewAndroid~Compose相关概念总结Android~Compose脚手架和ToastAndroid~Compose路由Navigation和传参 文章目录系列文章目录目标脚手架基于Snackbar自定义Toast实现效果目标 熟悉Compose中脚手架使用自定义Toast样式…

人工智能轨道交通行业周刊-第24期(2022.11.21-11.27)

追风赶月莫停留&#xff0c;平芜尽处是春山。 --《田歆华夏说》 本期关键词&#xff1a;BIM应用、地铁控制中心、车辆检修智能化、模型轻量化、隧道通风 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交…

【树莓派不吃灰】Linux篇⑥ 正规表示法与文件格式化处理(核心概念)

目录1. 什么是正则表示法2. 基础正规表示法&#xff08;grep、sed&#xff09;3. 延伸正规表示法4. 文件的格式化与相关处理&#xff08;printf、awk&#xff09;5. 重点回顾❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2022-11…