<Qt> 系统 - 网络编程 | 音视频

news2024/9/23 1:31:57

目录

前言:

一、QUdpSocket

(一)核心 API 概览

 (二)设计一个UDP回显服务器

二、QTCPSocket

(一)核心 API 概览

(二)设计一个TCP回显服务器

三、HTTP Client

四、Qt音视频

(一)Qt 音频

(二)Qt 视频


前言:

Qt 为了支持跨平台,对网络编程的API也重新封装了。 网络编程其实编写的是应用层代码,但是需要传输层的支持,传输层的核心协议有UDP和TCP,Qt 也提供了两套API,分别是QUdpSocketQTcpSocket。实际 Qt 开发中进行网络编程,也不一定使用 Qt 封装的网络 API,也有一定可能使用的是系统原生 API 或者其他第三方框架的 API。 

 还有一点要注意的是,要想实现网络编程,还要在.pro文件中添加network模块。我们之前提到过的各种控件都包含在QtCore模块中,为了不让可执行程序变得过于庞大,导致一些性能不够好的机器承受太大的压力,所以就进行了模块化的处理,默认情况下额外的模块不会参与编译,有需要就在.pro文件中添加:

一、QUdpSocket

(一)核心 API 概览

主要的类有两个:QUdpSocket 和 QNetworkDatagram

QUdpSocket 表示一个 UDP 的 socket 文件

名称类型说明
bind(const QHostAddress&,quint16)方法绑定指定的端⼝号
receiveDatagram()方法返回 QNetworkDatagram,读取⼀个 UDP 数据报.
writeDatagram(const QNetworkDatagram&)方法发送⼀个 UDP 数据报
readyRead信号在收到数据并准备就绪后触发

QNetworkDatagram 表示一个 UDP 数据报:

名称类型说明
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 )构造函数通过 QByteArray,⽬标 IP 地址,⽬标端⼝号 构造⼀个 UDP 数据报,通常⽤于发送数据时
data()方法获取数据报内部持有的数据,返回QByteArray
senderAddress()方法获取数据报中包含的对端的 IP 地址
senderPort()方法获取数据报中包含的对端的端⼝号

 (二)设计一个UDP回显服务器

代码示例:设计一个UDP回显服务器

在ui界面中设置一个 QListWidget 来显示客户端消息日志:

在写代码之前一定要在.pro文件中添加network模块。也记得要在头文件声明成员和成员函数(这里就不显示出来了)。

写一个服务器首先就要有一个Socket对象,之后就要连接信号和槽,捕捉readyRead信号,对应的槽函数就要完成服务器的核心逻辑,之后就是bind端口号,一个Udp服务器就做好了。 

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>

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, 8080);
    if(!ret)
    {
        QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
        return;
    }
}

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

完成处理请求的过程:

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
// 完成处理请求的过程
void Widget::processRequest()
{
    // 1.读取请求并解析
    const QNetworkDatagram &requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();// 返回的是一个QByteArray,可以赋值给QString
    
    // 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);
}

QString Widget::process(const QString &request)
{
    // 由于当前是回显服务器,响应和请求完全一样
    return request;
}

Udp使用的是数据报的形式,所以接收要接收一个数据报对象,这个数据报中有对端发来的数据和其他属性字段。给客户端进行响应的时候,也要响应一个数据报,构建一个数据报对象,再填充数据,使用toUtf8就可以把QString转换成QByteArray。最后再显示到服务器的QListWidget中就可以了。


下面就是客户端的界面了,给客户端设计一个界面。有一个回显框、输入框和发送按钮,再使用布局管理器修饰一下,调整一下垂直布局管理器,让下面的发送栏宽一点:

没有变宽就是因为没有调整下面两个控件的sizePolicy,都设置成Expanding就可以了:

我们想要实现的功能是现在输入框输入内容,点击发送按钮发送给服务端,所以先写一个按钮的槽函数:

void Widget::on_pushButton_clicked()
{
    // 1. 获取输入框的内容
    const QString &text = ui->lineEdit->text();
    ui->lineEdit->setText("");

    // 2. 构造 UDP 的请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);

    // 3. 发送请求数据
    socket->writeDatagram(requestDatagram);

    // 4. 把发送的请求也添加到列表框中
    ui->listWidget->addItem("客户请求: " + text);

    // 5. 清空输入框内容
    ui->lineEdit->setText("");
}

现在客户端就有了发送的能力,接下来就要写接收服务端数据的代码了:

#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>

const QString &SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8080;

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::processReponse);
}

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

void Widget::processReponse()
{

    // 通过这个槽函数处理收到的响应

    // 读取响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    const QString& response = responseDatagram.data();

    // 把响应数据显示到界面中
    ui->listWidget->addItem(response);
}

端口到本质上是一个 2 字节的无符号整数。

quint16:本质上就是一个 unsigned short(虽然 short 通常都是 2 个字节,但是 C++ 标准中没有明确规定这一点,只是说 short 不应该少于 2 个字节)。

最终执行效果:

客户端服务器测试的基本原则:一定是先启动服务器,后启动客户端。

启动多个客户端都可以正常工作,但是不能在界面选择直接运行,否则会覆盖上一个客户端:

二、QTCPSocket

(一)核心 API 概览

核心类是两个:QTcpServer 和 QTcpSocket

QTcpServer 用于监听端口,和获取客户端连接。

名称类型说明对标原生API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号并开始监听bind和listen
nextPendingConnection()方法

从系统中获取一个已经建立好的tcp连接

返回一个TcpSocket,表示这个连接

通过这个socket对象完成与客户端之间的通信

accept
newConnection信号有新的客户端建立好连接后触发无(类似与IO多路复用中的通知机制)

QTcpSocket 用户客户端和服务器之间的数据交互。

API类型说明对标原生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。

(二)设计一个TCP回显服务器

代码示例:设计一个UDP回显服务器

在ui界面中设置一个 QListWidget 来显示客户端消息日志:

在写代码之前一定要在.pro文件中添加network模块。也记得要在头文件声明成员和成员函数(这里就不显示出来了)。

客户端和服务端的界面都是不变的,变得是这是一个TCP服务器,除了bind还需要设置成监听状态,使用listen方法就可以完成,只要有新的连接就会触发newConnection信号。

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>

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, 8080);
    if(!ret)
    {
        QMessageBox::critical(nullptr, "服务器启动失败", tcpServer->errorString());
        exit(1);
    }
}

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

接下来就是设置好listen状态后,触发了newConnection信号之后执行processConnection的操作:

void Widget::processConnection()
{
    // 1. 通过tcpServer拿到一个socket对象,通过这个对象来和客户端进行通信
    QTcpSocket *clientSocket = tcpServer->nextPendingConnection();

    // peerAddress 表示对端的IP地址
    QString log = "[" + 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 = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort())
                      + "] req: " + request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });

    // 3. 通过信号槽,处理客户端断开连接的情况
    connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
        // a.把断开连接的信息通过日志显示出来
        QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort())  + "] 客户端下线";
        ui->listWidget->addItem(log);
        // b.手动释放 clientSocket
        clientSocket->deleteLater();
    });
}

QString Widget::process(const QString request)
{
    // 因为这里写的是回显服务器,所以请求和响应完全一样
    return request;
}

上述代码其实不够严谨,但在这里作为回显服务器已经够了。实际在使用 TCP 的过程中,TCP 是面向字节流的,一个完整的请求可能会分成多段字节数组进行传输。虽然 TCP 已经帮我们处理了很多棘手的问题,但是 TCP 本身并不负责区分从哪里到哪里是一个完整的应用层数据(粘包问题)。更严谨的做法:每次收到的数据都给它放到一个字节数组缓冲区中,并且提前约定好应用层协议的格式(分隔符 / 长度 / 其他办法),再按照协议格式对缓冲区数据进行更细致的解析处理。


下面就是客户端的界面了,同UDP界面一致:

下面就是客户端的代码了,除了要维护连接,编写上和Udp客户端没有太大的差别:

#include "widget.h"
#include "ui_widget.h"

#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", 8080);

    // 4. 链接信号槽,处理响应
    connect(socket, &QTcpSocket::readyRead, this, [=](){
        // a. 读取相应内容
        QString response = socket->readAll();
        // b. 把相应内容显示到界面上
        ui->listWidget->addItem(QString("服务器说: ") + response);
    });
    // 5. 等待连接确立的结果,并确认是否连接成功
    bool ret = socket->waitForConnected();
    if(!ret)
    {
        QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
        exit(1);
    }
}

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

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

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

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

三、HTTP Client

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

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

TTP相比TCP/UDP还要使用的更多一点,而HTTP协议本质上是基于TCP协议实现的,也就是封装了TcpSocketQt 只是提供了 HTTP客户端,并没有提供服务端

下面是核心API,三个类,分别是QNetworkAccessManager,QNetworkRequest,QNetworkReply

 QNetworkAccessManager 提供了HTTP的核心操作:

方法说明
get(const QNetworkRequest& )发起⼀个 HTTP GET 请求,返回 QNetworkReply 对象
post(const QNetworkRequest& , const QByteArray& )发起⼀个 HTTP POST 请求,返回 QNetworkReply 对象

QNetworkRequest 表示一个 HTTP 请求(不包含请求正文 body),想要发送一个带有body的请求需要再QNetworkAccessManager的post方法中的参数传入body:

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

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

取值说明
ContentTypeHeader描述 body 的类型
ContentLengthHeaderContentLengthHeader
ContentLengthHeader⽤于重定向报⽂中指定重定向地址(响应中使⽤,请求⽤不到)
CookieHeaderCookieHeader
UserAgentHeader设置 User-Agent

QNetworkReply 表示一个 HTTP响应,这个类同时也是 QIODevice 的子类。QNetworkReply 还有一个重要的信号 finishied,在客户端收到完整的响应数据后触发:

常用方法说明
error()获取出错状态
errorString()获取出错原因的⽂本
readAll()读取响应 body
header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值

下面就来写一个HTTP客户端,使用的界面与上面的差不多,通过指定一个Url发送请求,响应的结构大概率是一个 HTML,这里使用的是 QPlainTextEdit 来表示:

注意:此处建议使用 QPlainTextEdit,而不是 QTextEdit。主要是因为 QTextEdit 要进行富文本解析,最终显示的结果就不是原始的 HTML 了,如果得到的 HTTP 响应体积很大,会导致界面渲染缓慢甚至被卡住。


在写代码之前一定要在.pro文件中添加network模块。也记得要在头文件声明成员和成员函数(这里就不显示出来了)。

在构造函数中设置一下标题,并new一个 QNetworkAccessManager 对象,之后就可以写槽函数了: 

#include "widget.h"
#include "ui_widget.h"
#include <QNetworkReply>

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

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

    manager = new QNetworkAccessManager(this);
}

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

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

运行效果如下,输入一个Url就会返回一个html格式的文本: 

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

实际开发中,HTTP Client 获取到的的数据也不一定非得是 HTML,更大的可能性是客户端开发和服务器开发约定好交互的数据格式。按照约定的格式,客户端拿到之后进行解析,并显示到界面上。

四、Qt音视频

(一)Qt 音频

在 Qt 中,音频主要通过 QSound 类来实现。但是需要注意的是 QSound 类只支持播放 wav 格式的音频文件。在这之前也需要先引入 multimedia 模块,最核心的API就是play方法,用来播放音频。

在界面中添加一个按钮,命名为播放,当我们点击按钮,就会播放音乐。首先要有一个wav后缀的文件,像这种文件还是使用qrc来保存。

class MainWindow : public QMainWindow
{
    Q_OBJECT
 
private slots:
    void on_pushButton_clicked();
 
private:
    QSound* sound;
};
 
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 
    sound = new QSound(":/music/zjl_qingtian.wav", this);
}
 
void MainWindow::on_pushButton_clicked()
{
    // 在这里进行音频播放
    sound->play();
}

(二)Qt 视频

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

方法说明
setMedia()设置当前媒体源。
setVideoOutput()

将 QVideoWidget 视频输出附加到媒体播放器。

如果媒体播放器已经附加了视频输出,将更换一个新的。

class Widget : public QWidget
{
    Q_OBJECT
public:
    // ...
private:
    Ui::Widget *ui;
 
    QMediaPlayer *mediaPlayer; // 播放声音
    QVideoWidget *videoWidget; // 显示视频
 
    //创建两个按钮:选择视频按钮和播放按钮
    QPushButton *chooseBtn, *playBtn;
};

 接下来就是设置视频播放窗口的代码:

#include "widget.h"
#include "ui_widget.h"

#include <QMediaPlayer>
#include <QSlider>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 对象实例化
    mediaPlayer = new QMediaPlayer(this);
    videoWidget = new QVideoWidget(this);
 
    // 设置播放窗口
    videoWidget->setMinimumSize(600, 600);
 
    // 垂直布局
    QVBoxLayout *vbox = new QVBoxLayout();
    this->setLayout(vbox);
 
    // 实例化按钮
    chooseBtn = new QPushButton("选择视频", this);
    playBtn = new QPushButton(this);
 
    // 设置图标
    playBtn->setIcon(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, [=](){
       // 选择视频,返回视频的路径
       QString url = QFileDialog::getOpenFileName(this, "选择视频");
 
       // 设置声音
       mediaPlayer->setMedia(QUrl(url));
 
       // 输出画面
       mediaPlayer->setVideoOutput(videoWidget);
 
       // 播放
       mediaPlayer->play();
 
    });
}

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

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

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

相关文章

javaer快速入门 goweb框架 gin

gin 入门 前置条件 安装环境 配置代理 # 配置 GOPROXY 环境变量&#xff0c;以下三选一# 1. 七牛 CDN go env -w GOPROXYhttps://goproxy.cn,direct# 2. 阿里云 go env -w GOPROXYhttps://mirrors.aliyun.com/goproxy/,direct# 3. 官方 go env -w GOPROXYhttps://goproxy.…

【自动驾驶】自定义消息格式的话题通信(C++版本)

目录 新建消息文件更改包xml文件中的依赖关系更改cmakelist文件中的配置执行时依赖改变cmakelist编译顺序发布者程序调用者程序新建launch文件程序测试 新建消息文件 在功能包目录下&#xff0c;新建msg文件夹&#xff0c;下面新建mymsg.msg文件&#xff0c;其内容为 string …

机械行业数字化生产供应链产品解决方案(十六)

我们的机械行业数字化生产供应链产品解决方案通过全面应用物联网、人工智能和大数据技术&#xff0c;构建了一个高效的智能生产与供应链系统&#xff0c;能够在设计、生产和物流全环节中实现实时数据监控与动态优化。系统通过智能分析和预测&#xff0c;优化了生产计划和资源配…

Linux系统驱动(十九)块设备驱动

文章目录 一、块设备驱动简介&#xff08;一&#xff09;简介&#xff08;二&#xff09;块设备驱动相关概念 二、块设备驱动&#xff08;一&#xff09;框架图1. 虚拟文件系统&#xff08;VFS&#xff09;2. Disk Cache&#xff1a;硬盘的高速缓存3. 映射层&#xff08;mappin…

IP代理如何增强网络安全性?

在当今的数字时代&#xff0c;网络安全已成为一个关键问题&#xff0c;而使用 IP 代理可以成为增强网络安全的有效方法。根据请求信息的安全性&#xff0c;IP 代理服务器可分为三类&#xff1a;高级匿名代理、普通匿名代理和透明代理。此外&#xff0c;根据使用的用途&#xff…

NT35510的LCD函数详解01(洋桃电子-触摸屏开发者笔记)

NT35510的LCD函数详解01&#xff08;洋桃电子-触摸屏开发者笔记&#xff09; 资料下载&#xff1a; 洋桃电子 YoungTalk 探索最好的 STM32 教学 (doyoung.net) 接口类型 NT35510 数据手册&#xff08;英文&#xff09;.pdf NT35510 应用手册&#xff08;英文&#xff09;.…

Jenkins持续集成工具学习

一、从装修厨房看项目开发效率优化 二、持续集成工具 三、JavaEE项目部署方式对比 四、Jenkins+SVN持续集成环境搭建

WebGoC题解(18) 630.电线杆(2019NHOI小乙)

题目描述 小C在农场的附近看到有n颗电线杆排成一行&#xff0c;相邻之间距离为20。它们高度可能不一样&#xff0c;但高度相同的电线杆顶端有电线连接。如下面示意图中&#xff0c;电线杆用粗细为6的垂直直线画&#xff0c;电线用粗细为2的水平直线画。给定每个电线杆的高度&am…

Linux-Haproxy搭建Web群集

LVS在企业应用中抗负载能力强 不支持正则处理&#xff0c;不能实现动静分离对于大型网格&#xff0c;LVS的实施配置复杂&#xff0c;维护成本较高 Haproxy是一款可提供高可用性、负载均衡、及基于TCP和HTTP应用的代理的软件 适用于负载大的Web站点运行在硬件上可支持数以万计的…

AI大模型开发——4.transformer模型(0基础也可懂)(1)

无论是想怎样学习大模型&#xff0c;transformer都是一个绕不开的话题。transformer的出现彻底改变了nlp领域&#xff0c;进一步推动了大模型的产生&#xff0c;可以说&#xff0c;transformer就是大模型开发的鼻祖。 可能只通过说大家会有些不理解。大家可以看下方的大语言模型…

打卡第四十四天:最长公共子序列、不相交的线、最大子序和、判断子序列

一、最长公共子序列 题目 文章 视频 本题和最长重复子数组区别在于这里不要求是连续的了&#xff0c;但要有相对顺序&#xff0c;即&#xff1a;"ace" 是 "abcde" 的子序列&#xff0c;但 "aec" 不是 "abcde" 的子序列。 确定dp数…

4个快捷高效的ai在线写作工具推荐。

ai在线写作因其快速的创作方式&#xff0c;高效的写作效率以及能够为我们带来无限的灵感而被广泛应用。如果你还不会使用ai进行写作的话&#xff0c;就看看下面这4款AI写作工具吧。 1、笔灵在线创作 直通车 :https://ibiling.cn 这是个在线的AI工具网站&#xff0c;在内容创作…

个人可识别信息(PII) AI 去除 API 数据接口

个人可识别信息(PII) AI 去除 API 数据接口 ai / 隐私保护 基于 AI 模型自动去除个人识别信息&#xff08;PII&#xff09; 个人信息保护 / AI 模型 。 1. 产品功能 基于自有专业模型进行 PII 自动去除高效处理敏感信息全接口支持 HTTPS&#xff08;TLS v1.0 / v1.1 / v1.2 /…

地质灾害评估和治理工程勘查设计资质乙级资质办理标准

地质灾害评估和治理工程勘查设计资质乙级资质的办理标准主要包括单位条件、专业技术人员条件、仪器设备要求以及申请材料等方面。以下是详细的办理标准&#xff1a; 一、单位条件 **1、法人资格&#xff1a;**申请单位应具有企业法人或者事业单位法人资格。 **2、管理体系&a…

龙良曲pytorch课时1-课时13

前言 这篇是个人学习龙曲良老师的pytorch课程的笔记&#xff0c;疑惑地方自己加的内容 一、pytorch引入 1. 自动求导 在深度学习中&#xff0c;我们通常需要训练一个模型来最小化损失函数。这个过程可以通过梯度下降等优化算法来实现。梯度是函数在某一点上的变化率&#x…

Linux基础入门--目录结构之基本目录操作及注意事项

&#x1f600;前言 本篇博文是关于Linux基础入门–目录结构的基本介绍、基本目录和操作命令&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&…

滴答定时器笔记

SysTick介绍 1.1 什么是SysTick&#xff1f; Systick&#xff0c;即滴答定时器&#xff0c;是内核中的一个特殊定时器&#xff0c;用于提供系统级的定时服务。该定时器是一个24位的 递减计数器&#xff0c;具有自动重载值寄存器的功能。当计数器到达自动重载值时&#xff0c;它…

无人机中的温度/湿度/气压传感器详解!!!

一、温度传感器 温度传感器是一种用来测量物体或环境的温度变化的传感器。在无人机中&#xff0c;温度传感器通常采用红外线热成像技术&#xff0c;通过红外线相机获取物体表面的温度数据&#xff0c;实现对环境和物体温度的监测和测量。该技术具有响应速度快、无需接触、测量…

100个练习学习Rust!构文・整数・变量

前一篇文章 【0】准备 【1】构文・整数・变量 ← 本次全部文章列表 《100 Exercise To Learn Rust》第2回&#xff0c;也就是实际演习的第1回&#xff01;从这次开始&#xff0c;我们会适度减少前置说明&#xff0c;直接进入问题的解决&#xff01; 本次的相关页面 1.1. Syn…

SpringBoot入门第一篇

目录 SpringBootSpring Boot核心特性Spring Boot应用结构目录结构样例目录结构讲解 Spring Boot版本更新概览升级建议总结 常见使用场景社区和资源常见疑问**Spring Boot的自动配置机制是如何工作的&#xff1f;****Spring Boot支持哪些常见的配置文件格式&#xff1f;****为什…