Qt 系统相关 - 网络与音视频

news2024/11/22 22:26:33

目录

一、Qt 网络

1. UDP Socket

1.1 核心 API 概览

1.2 回显服务器

1.3 回显客户端

2. TCP Socket

2.1 核心 API 概览

2.2 回显服务器

2.3 回显客户端

3. HTTP Client

3.1 核心 API

3.2 代码示例

二、Qt 音视频

1. Qt 音频

1.1 核心API概览

1.2 示例

2. Qt 视频

2.1 核心API概览

2.2 示例


一、Qt 网络

  • 和多线程类似, Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装.
  • 在进行网络编程之前, 需要在项目中的 .pro 文件中添加 network 模块.
  • 添加之后要手动编译一下项目, 使 Qt Creator 能够加载对应模块的头文件.

1. UDP Socket

1.1 核心 API 概览

主要的类有两个. QUdpSocket QNetworkDatagram

  • QUdpSocket 表示一个 UDP 的 socket 文件.
名称类型说明对标原生 API
bind(const QHostAddress&, quint16)方法绑定指定的端口号.bind
receiveDatagram()方法返回 QNetworkDatagram . 读取⼀个 UDP 数据报.recvfrom
writeDatagram(const QNetworkDatagram&)方法发送⼀个 UDP 数据报.sendto
readyRead信号在收到数据并准备就绪后触发.无 (类似于 IO 多路复用的通知机制)
  • QNetworkDatagram 表示一个 UDP 数据报.
名称类型说明对标原生 API
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 )构造函数通过 QByteArray , 目标 IP 地址, 目标端口号 构造⼀个 UDP 数据报. 通常用于发送数据时.
data()方法获取数据报内部持有的数据. 返回 QByteArray
senderAddress()方法获取数据报中包含的对端的 IP 地址.无, recvfrom 包含了该功能.
senderPort()方法获取数据报中包含的对端的端口号.无, recvfrom 包含了该功能.

1.2 回显服务器

1) 创建界面, 包含⼀个 QListWidget 用来显示消息.

2) 创建 QUdpSocket 成员

  • 修改 widget.h(注意:先在 .pro 文件中添加 network 模块.)
#include <QUdpSocket>

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    
    QUdpSocket* socket;

    void processRequest();
    
    QString process(const QString& request);
};
  • 修改 widget.cpp, 完成 socket 后续的初始化
  1. 一般来说, 要先连接信号槽, 再绑定端口.
  2. 如果顺序反过来, 可能会出现端口绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了.
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 创建出这个对象
    socket = new QUdpSocket(this);
    
    // 设置窗口标题
    this->setWindowTitle("服务器");
    
    // 连接信号槽,处理收到的请求
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
    
    // 绑定端口号
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if (!ret){
        QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
        return;
    }
}

3) 实现 processRequest , 完成处理请求的过程

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
#include <QNetworkDatagram>

// 这个函数完成的逻辑,就是服务器的最核心逻辑
void Widget::processRequest()
{
    // 1. 读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    
    // 2. 根据请求计算响应(由于是回显服务器,响应不需要计算,就是请求本身)
    const QString& response = process(request);
    
    // 3. 把响应写回到客户端
    QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);
    
    // 显示打印日志
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
            QString::number(requestDatagram.senderPort())
            + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
    
}

4) 实现 process 函数

  • 由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.
QString Widget::process(const QString &request)
{
    // 由于当前是回显服务器,响应就是和请求完全一样
    // "根据请求处理响应" 是服务器开发中的最核心的步骤.
    //一个商业服务器程序, 这里的逻辑可能是几万行几十万行代码量级的.
    return request;
}

此时, 服务器程序编写完毕.

但是直接运行还看不出效果. 还需要搭配客户端来使用.

1.3 回显客户端

1) 创建界面. 包含⼀个 QLineEdit , QPushButton , QListWidget

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的sizePolicy Expanding
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (尺寸比例根据个人喜好微调).

2) 在 widget.cpp 中, 先创建两个全局常量, 表示服务器的 IP 和 端口

// 提前定义好服务器的 IP 和 端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

3) 创建 QUdpSocket 成员

  • 修改 widget.h, 定义成员
#include <QUdpSocket>

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    
    // 创建 socket 成员
    QUdpSocket* socket;
};

修改 widget.cpp, 初始化 socket

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 1. 设置窗⼝名字
    this->setWindowTitle("客⼾端");
    
    // 2. 实例化 socket
    socket = new QUdpSocket(this);
}

4) 给发送按钮 slot 函数, 实现发送请求.

#include <QNetworkDatagram>

void Widget::on_pushButton_clicked()
{
    // 1. 获取到输⼊框的内容
    const QString& text = ui->lineEdit->text();
    // 2. 构造 UDP 的请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    // 3. 发送请求数据
    socket->writeDatagram(requestDatagram);
    // 4. 把发送的请求也添加到列表框中
    ui->listWidget->addItem("客⼾端说: " + text);
    // 5. 清空输⼊框
    ui->lineEdit->setText("");
}

5) 再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应.

// 通过信号槽,来处理服务器返回的数据
connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);

void Widget::processResponse()
{
    // 通过这个函数来处理收到的响应
    // 1.读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    // 2.把响应数据显示到界面上
    ui->listWidget->addItem("服务器说:" + response);
}

6) 最终执行效果(启动多个客户端都可以正常工作.

  • 客户端服务器程序测试时候的基本原则,一定是先启动服务器,后启动客户端

2. TCP Socket

2.1 核心 API 概览

核心类是两个: QTcpServer QTcpSocket

  • QTcpServer 用于监听端口, 和获取客户端连接.
名称类型说明对标原生 API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号, 并开始监听.bind 和 listen
nextPendingConnection()方法从系统中获取到⼀个已经建立好的 tcp 连接. 返回⼀个 QTcpSocket , 表示这个客户端的连接. 通过这个 socket 对象完成和客户端之间的通信.accept
newConnection信号有新的客户端建立连接好之后触发.无 (但是类似于 IO 多路复用中的通知机制)
  • QTcpSocket 用于客户端和服务器之间的数据交互.
名称类型说明对标原生 API
readAll()方法读取当前接收缓冲区中的所有数据. 返回 QByteArray 对象.read
write(const QByteArray& )方法把数据写入 socket 中.write
deleteLater方法暂时把 socket 对象标记为无效. Qt 会在下个事件循环中析构释放该对象.无 (但是类似于 "半自动化的垃圾回收")
readyRead信号有数据到达并准备就绪时触发.无 (但是类似于 IO 多路复用中的通知机制)
disconnected信号连接断开时触发.无 (但是类似于 IO 多路复用中的通知机制)

QByteArray 用于表示一个字节数组. 可以很方便的和 QString 进行相互转换.

例如:

  • 使用 QString 的构造函数即可把 QByteArray 转成 QString.
  • 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray.
     

2.2 回显服务器

1) 创建界面. 包含一个 QListWidget , 用于显示收到的数据.

2) 创建 QTcpServer 并初始化

  • 修改 widget.h, 添加 QTcpServer 指针成员.
#include <QTcpServer>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    
    void processConnection();
    QString process(const QString& request);

private:
    Ui::Widget *ui;

    // 创建 QTcpServer
    QTcpServer* tcpServer;
};

修改 widget.cpp, 实例化 QTcpServer 并进行后续初始化操作.

  • 设置窗口标题
  • 实例化 TCP server. (父元素设为当前控件, 会在父元素销毁时被一起销毁).
  • 通过信号槽, 处理客户端建立的新连接.
  • 监听端口
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 1.修改窗口标题
    this->setWindowTitle("服务器");
    
    // 2.创建 QTcpServer 的实例
    tcpServer = new QTcpServer(this);
    
    // 3.通过信号槽,指定如何处理连接
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
    
    // 4.绑定并监听端口号
    bool ret = tcpServer->listen(QHostAddress::Any, 9090);
    if (!ret){
        QMessageBox::critical(this, "服务器启动失败", tcpServer->errorString());
        exit(1);
    }
}

3) 继续修改 widget.cpp, 实现处理连接的具体方法 processConnection

  • 获取到新的连接对应的 socket.
  • 通过信号槽, 处理收到请求的情况
  • 通过信号槽, 处理断开连接的情况
void Widget::processConnection()
{
    // 1. 通过 tcpServer 拿到一个 socket 对象,通过这个对象来和客户端进行通信
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log = QString("[") + clientSocket->peerAddress().toString()
            + ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线!";
    ui->listWidget->addItem(log);
    
    // 2. 通过信号槽, 处理收到请求的情况
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
        // a) 读取请求
        QString request = clientSocket->readAll();
        // b) 根据请求处理响应
        const QString& response = process(request);
        // c) 把响应写回客户端
        clientSocket->write(response.toUtf8());
        // d) 把上述信息记录到日志中
        QString log = QString("[") + clientSocket->peerAddress().toString()
                + ":" + QString::number(clientSocket->peerPort()) + "] req: " +
                request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });
    
    // 3. 通过信号槽, 处理断开连接的情况
    connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
        // a) 把断开连接的信息通过日志显示出来
        QString log = QString("[") + clientSocket->peerAddress().toString()
                + ":" + QString::number(clientSocket->peerPort()) + "] 客户端下线!";
        ui->listWidget->addItem(log);
        // b) 手动释放 clientSocket。直接使用 delete 是下策,使用 deleteLater 更加合适 
        clientSocket->deleteLater();
    });        
}

4) 实现 process 方法, 实现根据请求处理响应.

  • 由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.
// 此处写的是回显服务器
QString Widget::process(const QString &request)
{
    return  request;
}

此时, 服务器程序编写完毕.

但是直接运行还看不出效果. 还需要搭配客户端来使用.

2.3 回显客户端

1) 创建界面. 包含一个 QLineEdit , QPushButton , QListWidget

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的sizePolicyExpanding
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (尺寸比例根据个人喜好微调).

2) 创建 QTcpSocket 并实例化

  • 修改 widget.h, 创建成员.
class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    // 新增 QTcpSocket
    QTcpSocket* socket;
};

修改 widget.cpp, 对 QTcpSocket 进行实例化.

  • 设置窗口标题
  • 实例化 socket 对象 (父元素设为当前控件, 会在父元素销毁时被一起销毁).
  • 和服务器建立连接.
  • 等待并确认连接是否出错.
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗口标题.
    this->setWindowTitle("客户端");
    // 2. 实例化 socket 对象.
    socket = new QTcpSocket(this);
    // 3. 和服务器建⽴连接.
    socket->connectToHost("127.0.0.1", 9090);
    // 4. 等待并确认连接是否出错.
    if (!socket->waitForConnected()) {
        QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
        exit(1);
    }
}

3) 修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器.

void Widget::on_pushButton_clicked()
{
    // 获取输入框的内容
    const QString& text = ui->lineEdit->text();
    // 发送消息给服务器
    socket->write(text.toUtf8());
    // 把消息显示到界⾯上
    ui->listWidget->addItem(QString("客户端说: ") + text);
    // 清空输入框内容
    ui->lineEdit->setText("");
}

4) 修改 widget.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应.

// 连接信号槽,处理响应
connect(socket, &QTcpSocket::readyRead, this, [=](){
    // a) 读取出响应内容
    QString response = socket->readAll();
    // b) 把响应内容显示到界面上
    ui->listWidget->addItem("服务器说:" + response);
});

6) 最终执行效果(先启动服务器, 再启动客户端,可以启动多个

  • 由于我们使用信号槽处理同一个客户端的多个请求, 不涉及到循环, 也就不会使客户端之间相互影响了.

3. HTTP Client

进行 Qt 开发时, 和服务器之间的通信很多时候也会用到 HTTP 协议.

  • 通过 HTTP 从服务器获取数据.
  • 通过 HTTP 向服务器提交数据.

3.1 核心 API

关键类主要是三个:QNetworkAccessManager , QNetworkRequest , QNetworkReply .

  • QNetworkAccessManager 提供了 HTTP 的核心操作.
方法说明
get(const QNetworkRequest& )发起⼀个 HTTP GET 请求. 返回 QNetworkReply 对象.
post(const QNetworkRequest& , const QByteArray& )发起⼀个 HTTP POST 请求. 返回 QNetworkReply 对象.
  • QNetworkRequest 表示一个 HTTP 请求(不含 body).

如果需要发送一个带有 body 的请求(比如 post), 会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body.

方法说明
QNetworkRequest(const QUrl& )通过 URL 构造⼀个 HTTP 请求.
setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)设置请求头.

其中的 QNetworkRequest::KnownHeaders 是一个枚举类型, 常用取值:

取值说明
ContentTypeHeader描述 body 的类型.
ContentLengthHeader描述 body 的长度.
LocationHeader用于重定向报文中指定重定向地址. (响应中使用, 请求用不到)
CookieHeader设置 cookie
UserAgentHeader设置 User-Agent
  • QNetworkReply 表示一个 HTTP 响应. 这个类同时也是 QIODevice 的子类.
方法说明
error()获取出错状态.
errorString()获取出错原因的文本.
readAll()读取响应 body.
header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值.

此外, QNetworkReply 还有一个重要的信号 finished 会在客户端收到完整的响应数据之后触发.

3.2 代码示例

给服务器发送⼀个 GET 请求.

1) 创建界面. 包含一个 QLineEdit , QPushButton

  • 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的 sizePolicy 为 Expanding
  • 再使用垂直布局把 QPlainTextEdit 和上面的水平布局放好. ( QPlainTextEdit 的 readOnly 设为 true )
  • 设置垂直布局的 layoutStretch 为 5, 1 (尺寸比例根据个人喜好微调).

🌵此处建议使用 QPlainTextEdit 而不是 QTextEdit . 主要因为 QTextEdit 要进行富文本解析, 如果得到的 HTTP 响应体积很大, 就会导致界面渲染缓慢甚至被卡住.

2) 修改 widget.h, 创建 QNetworkAccessManager 属性

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    QNetworkAccessManager* manager;
};

3) 修改 widget.cpp, 创建实例

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    this->setWindowTitle("客户端");

    // 实例化属性
    manager = new QNetworkAccessManager(this);
}

4) 编写按钮的 slot 函数, 实现发送 HTTP 请求功能.

void Widget::on_pushButton_clicked()
{
    // 1. 获取到输⼊框中的 URL, 构造 QUrl 对象
    QUrl url(ui->lineEdit->text());
    // 2. 构造 HTTP 请求对象
    QNetworkRequest request(url);
    // 3. 发送 GET 请求
    QNetworkReply* response = manager->get(request);
    // 4. 通过信号槽来处理响应
    connect(response, &QNetworkReply::finished, this, [=]() {
        if (response->error() == QNetworkReply::NoError) {
            // 响应正确
            QString html(response->readAll());
            ui->plainTextEdit->setPlainText(html);
            // qDebug() << html;
        } else {
            // 响应出错
            ui->plainTextEdit->setPlainText(response->errorString());
        }
        // 还需要对 response 进行释放
        response->deleteLater();
    });
}

5) 执行程序, 观察效果

发送 POST 请求代码也是类似. 使用 manager->post() 即可.

二、Qt 音视频

1. Qt 音频

        在 Qt 中,音频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只只持播放 wav 格式的音频文件。也就是说如果想要添加音频效果,那么首先需要将 非wav格式 的音频文件转换为 wav 格式。

通过帮助手册查看 QSound 类如下:

注意:

  • 使用 QSound 类时,需要添加模块:multimedia

1.1 核心API概览

play()开始或继续播放当前源。

1.2 示例

/********************************* SoundTest.pro
*********************************/

QT       += core gui multimedia //添加⾳频模块
        
/********************************* widget.cpp
*********************************/
#include "widget.h"
#include "ui_widget.h"
#include <QSound> //添加⾳频头⽂件
        
Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    //实例化对象
    QSound *sound = new QSound(":/1.wav",this);
    
    connect(ui->btn,&QPushButton::clicked,[=](){
        sound->play(); //播放
    });
}

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

2. Qt 视频

        在 Qt 中,视频播放的功能主要是通过 QMediaPlayer类QVideoWidget类 来实现。在使用这两个类时要添加对应的模块 multimedia multimediawidgets

2.1 核心API概览

setMedia()设置当前媒体源。
setVideoOutput()将QVideoWidget视频输出附加到媒体播放器。 如果媒体播放器已经附加了视频输出,将更换⼀个新的。

2.2 示例

首先在 .pro 文件中添加 multimedia 和 multimediawidgets 两个模块;如下图示:

/********************************* widget.h *********************************/
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QHBoxLayout> //⽔平布局
#include <QVBoxLayout> //垂直布局
#include <QVideoWidget> //显⽰视频
#include <QMediaPlayer> //播放声⾳
#include <QPushButton> //按钮
#include <QStyle> //设置图标
#include <QFileDialog> //选择⽂件/⽂件夹

class Widget : public QWidget
{
    Q_OBJECT
    
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    
public slots:
    void chooseVideo();
    
private:
    QMediaPlayer *mediaPlayer;
    QVideoWidget *videoWidget;
    QVBoxLayout *vbox;
    
    //创建两个按钮:选择视频按钮和开播放按钮
    QPushButton *chooseBtn,*playBtn;
};

#endif // WIDGET_H
    
/********************************* widget.cpp
*********************************/
#include "widget.h"
#include <QMediaPlayer>
#include <QSlider>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    //对象实例化
    mediaPlayer = new QMediaPlayer(this);
    videoWidget = new QVideoWidget(this);
    
    //设置播放画⾯的窗⼝
    videoWidget->setMinimumSize(600,600);
    
    //实例化窗⼝布局---垂直布局
    this->vbox = new QVBoxLayout(this);
    this->setLayout(this->vbox);
    
    //实例化选择视频按钮
    chooseBtn = new QPushButton("选择视频",this);
    
    //实例化播放按钮
    playBtn = new QPushButton(this);
    
    //设置图标代替⽂件
    playBtn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
    
    //实例化⼀个⽔平布局,将以上控件放⼊⽔平布局中
    QHBoxLayout *hbox = new QHBoxLayout;
    
    //添加控件
    hbox->addWidget(chooseBtn);
    hbox->addWidget(playBtn);
    
    //将播放窗⼝和⽔平布局都添加到垂直布局中
    vbox->addWidget(videoWidget);
    
    //布局中添加布局
    vbox->addLayout(hbox);
    
    //将选择视频对应的按钮和槽函数进⾏关联
    connect(chooseBtn,&QPushButton::clicked,this,&Widget::chooseVideo);
}

void Widget::chooseVideo()
{
    //选择视频,返回⼀个播放视频的名字
    QString name = QFileDialog::getSaveFileName(this,"选择视频",".","WMV(*.wmv)");
    
    //设置媒体声⾳
    mediaPlayer->setMedia(QUrl(name));
    
    //输出视频画⾯
    mediaPlayer->setVideoOutput(videoWidget);
    
    //播放
    mediaPlayer->play();
}

Widget::~Widget()
{ }

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

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

相关文章

Java面试八股之请简述消息队列的发布订阅模式

请简述消息队列的发布订阅模式 发布订阅&#xff08;Publish-Subscribe&#xff0c;简称 Pub/Sub&#xff09;模型是一种消息传递模式&#xff0c;它在组件之间提供了高度的解耦和灵活性。这种模式广泛应用于分布式系统、事件驱动架构以及消息队列系统中。下面是发布订阅模型的…

什么是核心交换机?这样回答太到位了

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 你们好&#xff0c;我的网工朋友。 无论是企业内部通信还是对外服务&#xff0c;稳定高效的数据传输都是成功的关键。 在这个背景下&#xff0c…

A-CSPO课程概念澄清和实操:假定(Assumptions)、实验(Experiments)、假设(Hypotheses)

“确保你把这当作一个实验。” “我们的工作假设是客户想要这个。” 这些场景熟悉吗&#xff1f;你的团队&#xff08;或整个组织&#xff09;可能会经常混淆假定(Assumptions)、实验(Experiments)和假设(Hypotheses)等术语&#xff0c;这会造成混乱。 让我们澄清一下每一个…

JAVA社会校招人力资源招聘系统小程序源码

解锁职场新篇章&#xff0c;揭秘“社会校招人力资源招聘系统”的奥秘 一、引言&#xff1a;为何社会校招需要数字化升级&#xff1f; 在这个信息爆炸的时代&#xff0c;企业面临着前所未有的招聘挑战&#xff1a;如何从海量简历中精准筛选出合适的人才&#xff1f;如何高效管…

SQLAlchemy 中使用 GroupBy 和 Sum 导致重复计数的问题及解决方法

在 SQLAlchemy 中使用 GroupBy 和 Sum 时&#xff0c;有时会遇到重复计数或意外的查询结果。这通常是因为在聚合查询中没有正确地指定聚合函数或 GroupBy 条件&#xff0c;导致结果集没有按预期方式分组。 1、问题背景 在使用 SQLAlchemy 进行数据查询时&#xff0c;用户在尝试…

入门 - vue整个过程的生命周期详解

生命周期概念 Vue的生命周期就是vue实例从创建到销毁的全过程&#xff0c;也就是new Vue()开始就是vue生命周期的开始。Vue 实例有⼀个完整的⽣命周期&#xff0c;也就是从开始创建、初始化数据、编译模版、挂载Dom->渲染、更新->渲染、卸载 等⼀系列过程&#xff0c;称…

无人机灯光含义的详解!!!

一、LED指示灯和状态指示灯 LED指示灯&#xff1a;通常位于飞行器的头部机臂上&#xff0c;用于显示无人机的当前状态。 状态指示灯&#xff1a;位于尾部机臂上&#xff0c;提供更多关于无人机状态的信息。 红绿黄灯交替闪烁 表示无人机正在进行系统自检。稍等片刻&#xf…

Mybatis获取主键自增的方法

原本的方法 使用原本的JDBC去获取主键自增的方法如下图所示&#xff1a; 在这段代码中&#xff0c;通过连接对象 conn 的 prepareStatement 方法创建了一个PreparedStatement对象&#xff0c;并将SQL语句和 RETURN_GENERATED_KEYS 常量作为参数传递给该方法。这意味着执行SQL…

使用 Python 创建 Windows 程序列表生成器:从安装程序到配置文件

在当今的数字时代&#xff0c;我们的计算机上安装了数不胜数的程序。但是&#xff0c;您是否曾想过如何快速获取所有已安装程序的列表&#xff0c;并将其转化为可用的配置文件&#xff1f;今天&#xff0c;我们将探讨如何使用 Python 创建一个强大的工具&#xff0c;它不仅可以…

StarRocks Lakehouse 快速入门——Apache Paimon

StarRocks Lakehouse 快速入门指南为您提供了湖仓技术概览&#xff0c;旨在帮助您迅速掌握其核心特性、独特优势和应用场景。本指南将指导您如何高效地利用 StarRocks 构建解决方案。文章末尾&#xff0c;我们集合了来自阿里云、饿了么、喜马拉雅和同程旅行等行业领导者在 Star…

【私有云场景案例分享①】高效的集群管理能力

一、前言 设备的管理对企业至关重要&#xff0c;会影响生产效率、成本控制和竞争力。然而&#xff0c;企业在设备管理上面临设备数量多、设备分布广、维护成本高等挑战。DeviceKeeper设备管理网站作为解决方案&#xff0c;可以通过远程设备监控、远程设备维护和包体共享等功能…

制造业MES系统源码,前端框架:vue.js,后端框架:springboot 功能模块包括:生产计划管理、物料管理、工艺管理、设备管理、

MES系统功能模块解析&#xff0c;MES系统源码 MES系统是一种用于协调和优化制造过程的信息管理系统&#xff0c;可以帮助企业实现生产计划的顺利执行&#xff0c;并提供全面的生产监控和数据分析功能。 MES系统常见的功能模块包括生产计划管理、物料管理、工艺管理、设备管理…

如何把Phalcon 集成到PhpStorm里面

一 背景 按照上一篇文章里面写的Phalcon 创建项目过程中的一些坑, 最终我们在终端可以基于Phalcon命令创建对应的开发项目。但在这个过程中,存在一个问题:那就是写代码的时候,发现Phalcon对应的依赖提示都没有,如下: 从上面这个截图来看,就能发现,Phalcon的啥…

音频剪辑在线工具哪个好?分享5款简单易上手的音频剪辑工具

暑期的泰山人山人海&#xff0c;游客们纷纷涌向这座名胜古迹。站在巍峨的泰山之巅&#xff0c;望着脚下绵延的群山和眼前无边的云海&#xff0c;人们不禁会想要记录下这一刻的声音。 但泰山的风声、游客的喧哗声、还有自然与人文的杂音交织在一起&#xff0c;要想将这声音中的…

【人工智能】 使用线性回归预测波士顿房价 paddlepaddle 框架 飞桨

一、简要介绍 经典的线性回归模型主要用来预测一些存在着线性关系的数据集。 回归模型可以理解为:存在一个点集,用一条曲线去拟合它分布的过程。如果拟合曲线是一条直线,则称为线性回归。 如果是一条二次曲线,则被称为二次回归。 线性回归是回归模型中最简单的一种。 本…

机房监控系统,全面监控机房动力环境实时报警@卓振思众

在现代企业运营中&#xff0c;机房作为计算机系统的核心支撑平台&#xff0c;承载着关键数据和应用的稳定运行。因此&#xff0c;保障机房环境的安全和设备的正常运行至关重要。【卓振思众】机房监控系统&#xff0c;作为一种先进的智能管理工具&#xff0c;正是为了实现这一目…

启发式算法之模拟退火算法

文章目录 1. 模拟退火算法概述1.1 算法起源与发展1.2 算法基本原理 2. 算法实现步骤2.1 初始化过程2.2 迭代与降温策略 3. 模拟退火算法的优化策略3.1 冷却进度表的设计3.2 参数调整与策略 4. 模拟退火算法的应用领域4.1 组合优化问题4.1.1 旅行商问题&#xff08;TSP&#xff…

Halcon阈值处理的几种分割方法

Halcon阈值处理的几种分割方法 文章目录 Halcon阈值处理的几种分割方法1. 全局阈值2. 基于直方图的自动阈值分割方法3. 自动全局阈值分割方法4. 局部阈值分割方法5. var_threshold算子6 . char_threshold 算子7. dual_threshold算子 在场景中选择物体或特征是图像测量或识别的重…

982200419控制燃烧器可面价

982200419控制燃烧器可面价 982200419控制燃烧器可面价 982200419控制燃烧器可面价 982200419控制燃烧器接线图 982200419控制燃烧器说明书 982200419控制燃烧器线路图 982200419燃烧机也叫燃烧器&#xff0c;按照燃料可分为燃油燃烧机和燃气燃烧机、生物质燃烧机&#x…

胡玫导演《红楼梦之金玉良缘》今日公映 李越版“琏二爷”实力不凡

今日&#xff0c;由胡玫执导&#xff0c;何燕江编剧&#xff0c;林鹏、卢燕、边程、张淼怡、李越等主演的电影《红楼梦之金玉良缘》全国公映。电影改编自曹雪芹不朽名著《红楼梦》&#xff0c;以“宝、黛、钗”三人的情缘纠葛入手&#xff0c;从“木石前盟”看“金玉良缘”&…