【Qt】探索Qt网络编程:构建高效通信应用

news2024/9/20 22:44:53

文章目录

  • 前言:
  • 1. Qt 网络编程介绍
    • 1.1 什么是网络编程?
    • 1.2 Qt的模块
  • 2. UDP Socket
    • 2.1 核心 API 概述
    • 2.2 写一个带有界面的 Udp 回显服务器
    • 2.3 写一个带有界面的 Udp 客户端
  • 3. TCP Socket
    • 3.1 核心 API 概述
    • 3.2 代码:
  • 4. HTTP Client
    • 4.1 核心API
    • 4.2 代码示例
  • 总结:

前言:

在当今信息化社会,网络编程已成为软件开发中不可或缺的一部分。Qt,作为一个跨平台的应用程序框架,提供了丰富的网络编程API,使得开发者能够便捷地实现客户端和服务器之间的通信。本文将深入探讨Qt网络编程的基本概念、核心API以及实际应用示例,帮助读者理解并掌握使用Qt进行网络编程的方法。

1. Qt 网络编程介绍

1.1 什么是网络编程?

网络编程,操作系统提供了一组API(Socket API)
C++标准库中,并没有提供网络编程的 api 的封装。
进行网络编程的时候,本质是在编写应用层代码,需要传输层进行支持。
传输层最核心的协议,有 UDP 和 TCP,并且这两协议,差别还很大。
Qt 也提供了两套 API。

1.2 Qt的模块

使用 Qt 网络编程的 API,需要在 .pro 文件中添加 network 模块!
之前我们学过的 Qt 的各种控件,各种内容,都是包含在QtCore 模块中(默认就添加的)

为什么Qt要划分出这些模块呢?
Qt本身是一个非常庞大,包罗万象的框架。
如果把所有的 Qt 的功能都放到一起,即使咱们就只写一个简单的 hello world, 此时生产的可执行文件也会非常庞大。(这里就包含了大量其实没有使用的功能)

模块化处理:
其他的功能分别封装成不同的模块,默认情况下这些额外的模块不会参与编译。
需要在.pro文件中引入对应的模块才能把对应功能给编译加载进来。

Qt 其实提供了静态库的版本和动态库的版本。

2. UDP Socket

2.1 核心 API 概述

主要有两个 QUdpSocket(一个文件) 和 QNetworkDatagram(数据包,UDP是面向数据报的)

QUdpSocket 表示一个 UDP 的 socket 文件。
在这里插入图片描述
readyRead:当socket 收到请求的时候,QUdpSocket 就会触发这个信号。
此时就可在槽函数里完成读取请求的操作了。

基于信号槽,就天然达成了"事件驱动"这样的一种网络编程的方式!

QNetWorkDatagram 表示一个UDP数据报
在这里插入图片描述

2.2 写一个带有界面的 Udp 回显服务器

一个正经的服务器很少会有图形化界面(一般都是命令行)
Qt 也是完全可以编写控制台程序的。

在 .pro 文件中添加一个network
在这里插入图片描述

注意:一定是先连接信号槽,后绑定端口号。 一旦绑定端口了,意味着请求就可以被收到了!
如果在绑定之后,在连接信号槽之前,有客户端把请求发过来了,此时就可能读不到这样的请求(就没了)

// 绑定端口号
socket->bind(QHostAddress::Any, 9090);

一个端口号只能被一个socket绑定,万一9090被别人绑定了呢? 返回绑定失败信息

代码:

#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 是用于通过对象树自动delete掉对象的
                                   // 不然就要在析构中去手动delete掉

    // 设置窗口标题
    this->setWindowTitle("服务器");

    // 连接信号槽
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // 绑定端口号
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if (!ret) {
        // 绑定失败!
        QMessageBox::critical(this, "服务器启动出错", socket->errorString());
        // socket->本质上也是对系统的errno机制进行封装, 相当与Linux中的perror
        return;
    }

}

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

// 这个函数完成的逻辑,就是服务器的最核心逻辑了
void Widget::processRequest()
{
    // 1. 读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data(); // .data() 返回的是一个QByteArray
                                              // QByteArray 是可以赋值给QString
    // 2. 根据请求计算响应(由于是回显服务器,响应不需要计算,就是请求本身)
    const QString& response = process(request);
    // 3. 把响应写回客户端
    QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
                  // .toUtf8 取出QString 内部的字节数组,客户端是谁就包含在requestDatagram中了
    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;
}

2.3 写一个带有界面的 Udp 客户端

在这里插入图片描述
Qt Creator 中是可以同时打开多个项目。此时,如果这俩项目中存在同名文件就非常容易混淆。

此时写的客户端,要能够主动给服务器发起请求
在这里插入图片描述

// 定义两个常量,来描述服务器的 地址 和 端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 PORT = 9090; 

端口号本质上是一个 2字节的 无符号 整数,quint16本质上就是一个unsigned short,虽然short通常都是2字节但是C++标准中没有明确规定这一点,只是说short不应该少于2个字节。

什么时候用引用?什么时候用赋值?

const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();

啥时候使用引用类型,啥时候使用值类型,需要平时写代码的时候,多去思考,多去注意的 !
大的原则,肯定是能用引用尽量用引用,但有的时候注意到,尤其是上面这种不同的值进行互相转换的时候,大概率是要用值类型的!

代码:

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

// 定义两个常量,来描述服务器的 地址 和 端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

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

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


void Widget::on_pushButton_clicked()
{
    // 1. 获取到输入框的内容
    const QString& text = ui->textEdit->toPlainText();
    // 2. 构造 UDP 的请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT); // 这里字符串的IP要转换为点分十进制的IP
    // 3. 发送请求数据
    socket->writeDatagram(requestDatagram);
    // 4. 把发送的请求也添加到列表框中
    ui->listWidget->addItem("客户端说:" + text);
    // 5. 把输入框的内容也清空一下
    ui->textEdit->setText("");
}

void Widget::processResponse()
{
    // 通过这个函数来处理收到的响应
    // 1. 读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
        // 啥时候使用引用类型,啥时候使用值类型,需要平时写代码的时候,多去思考,多去注意的
        // 大的原则,肯定是能用引用尽量用引用,但有的时候注意到,尤其是上面这种不同的值进行互相转换的时候,大概率是要用值类型的!
    // 2. 把响应数据显示到界面上
    ui->listWidget->addItem("服务器说:" + response);
}

在这里插入图片描述
在这里插入图片描述
如何启动多个客户端?
在这里插入图片描述
多启动几个可执行程序就好!
在这里插入图片描述
之前学Linux网络编程的时候,是使用云服务器部署服务器程序,其它的同学们也能连上。

  • 能否把现在的UDP服务器放到云服务器上呢?

大概率不行,取决于你的服务器是否安装了图形化界面,Qt程序需要依赖于图形化界面来运行的!Linux的云服务器,一般都是没有图形化界面的如果需要你需要手动额外安装。作为一个服务器,本身就是没有图形界面的(此处只是为了演示Qt网络编程的情况),也不会使用Qt来写服务器程序!

  • 能否使用先在的UDP客户端连接,Linux上写的UDP服务器呢?

这是完全OK的,这也是网络编程/协议的意义!
一般商业公司的项目,都是通过其它方式编写的服务器程序(大概率不会是Qt),但是使用Qt编写客户端!

3. TCP Socket

  • UDP 属于无连接,不可靠传输,面向数据报,全双工
  • TCP 有连接,可靠传输,面向字节流,全双工
    因此,TCP的代码,要比UDP稍微复杂一点点!

三次握手四次挥手(TCP建立链接或断开链接的时候完成的),操作系统系统内核负责完成的!
应用层的代码,只能告诉内核,我要发起一个连接(客户端),或者告诉内核,我要拿到一个已经建立好的连接(服务器)

3.1 核心 API 概述

核心类是:QTcpServerQTcpSocket

QTcpServer 用于监听客户端口,和获取客户端连接
在这里插入图片描述
QTcpSocket 用户客户端和服务器之间的数据交互
在这里插入图片描述

事件循环:简单理解,可以认为是Qt程序内部带有一个“生物钟”这样的东西!周期性的执行一些逻辑!

QByteArray ⽤于表⽰⼀个字节数组. 可以很⽅便的和 QString 进⾏相互转换. 例如:

  • 使⽤ QString 的构造函数即可把 QByteArray 转成 QString
  • 使⽤ QString 的 toUtf8 函数即可把 QString 转成 QByteArray
// 2. 通过信号槽,来处理客户端发来请求的情况
    // 2. 通过信号槽,来处理客户端发来请求的情况
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
        // a)读取出请求数据,此处readAll 返回的是QByteArray, 通过赋值转成 QString
        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);
    });

在Linux 网络编程,需要搞一个循环,循环的读取请求,循环处理…
在 Qt 中基于信号槽就不必循环了!
每次客户端发来请求,都能触发 readyRead 信号。
即使多个请求,槽函数也是可以顺利的执行到的!

  • 上述的代码其实是不够严谨,作为回显服务器是已经够了的!

  • 实际上使用TCP的过程中,TCP是面向字节流的,一个完整的请求,可能分成多段字节数组进行传输!

  • 虽然TCP已经帮我们处理了很多棘手的问题了,但是TCP本身不负责区分,从哪里到哪里是一个完整的应用层数据报(粘包问题)。

  • 更严谨的做法,应该是每次收到的数据都给放到一个大的字节数组缓冲区中,并且提前约定好应用层的协议的格式(分隔符?长度?其它办法?)

  • 再按照协议格式对缓冲区进行更细致的解析处理(当前不打算写这么复杂了)再按照协议格式对缓冲区数据进行更细致的解析处理

 QTcpSocket* clientSocket

每个客户端都有一个这样的对象,存在N个的,随着服务器的运行,客户端越来越多,如果不释放,此时累计的clientSocket也会越来越多!
QTcpServer QUdpSocket都是只有一份的(就算不释放,影响不大) 内存泄漏其实影响不大,但是如果是文件描述符泄漏呢?
现在的机器内存都很大,而文件描述符表的长度,则是操作系统的一个参数。Linux可以通过ulimits 命令来查看和调整!

// b) 手动释放 clientSocket
delete clientSocket;

一旦要是 delete 就意味着其它逻辑无法使用 clientSocket

务必要保证delete 是这个槽函数的最后一步,而且也要保证 delete 肯定能执行到,不会被return / 抛出异常 给跳过…

clientSocket->deleteLater();

直接使用delete是下策,使用deleteLater 更加合适 这个操作,不是立即销毁 clientSocket,
而是告诉Qt,下一轮事件循环中(槽函数都是在事件循环中执行的,进入到下一轮事件循环,意味着上一轮事件循环肯定结束了,也以为着当前的槽函数肯定是结束了),再进行上述销毁操作!

当然,上述做法都是权宜之计,相比之下,Java/Python/Go 等全自动化垃圾回收更好用一些!

socket->connectToHost("127.0.0.1", 9090);

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

在Linux写的TCP的回显服务器的时候,遇到了一个问题,多个客户端同时访问的时候,就只会有一个生效;后来引入了多线程,每个客户端安排一个单独的
线程,每个客户端安排一个单独的线程,问题才得到改善

在这里插入图片描述

在Linux中,之所以出现上述问题,和TCP,和多线程都没啥关系。从来没有说法,说TCP服务器必须使用多线程编写!
之前存在这个问题的本质原因,是写了一个双重循环,里层循环没有及时结束,导致外层循环不能快速的第二次调用到accept,导致第二个客户端无法处理了!
引入多线程,本质上就是把双重循环,化简成为两个独立的循环。

而在咱们Qt的服务器中,其实一个循环都没写,是通过Qt内置的信号槽来驱动的!
信号槽机制很好的化简了咱们的程序!但是基本没有正经的服务器用Qt来写!

3.2 代码:

服务端:

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

}

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

void Widget::processConnection()
{
    // 1. 通过tcpServer拿到一个socket对象,通过这个对象和客户端进行通信
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "]客户端上线!";
    ui->listWidget->addItem(log);

    // 2. 通过信号槽,来处理客户端发来请求的情况
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
        // a)读取出请求数据,此处readAll 返回的是QByteArray, 通过赋值转成 QString
        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, 直接使用delete是下策,使用deleteLater 更加合适
//        delete clientSocket;
        clientSocket->deleteLater();

    });
}

// 此处写的是回显服务器
QString Widget::process(const QString request)
{
    return request;
}

客户端:

 #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", 9090);
    // 调用者个函数,此时系统内核就会和对方的服务器之间进行三次握手了
    // 三次握手也是需要消耗一定的时间的
    // 4. 连接信号槽,处理响应
    connect(socket, &QTcpSocket::readyRead, this, [=](){
        // a) 读取出响应内容
        QString response = socket->readAll();
        // b) 把显示内容显示到界面上
        ui->listWidget->addItem("服务器说:" + response);
    });
    // 5. 等待连接建立的结果,确认是否连接成功
    bool ret = socket->waitForConnected();
    if (!ret){
        QMessageBox::critical(this, "连接服务器出错", 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("客户端说:" + text);
    // 4. 清空输入框的内容
    ui->lineEdit->setText("");
}

4. HTTP Client

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

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

HTTP 使用比 TCP/UDP 更多一些。
Qt中也提供了HTTP的客户端,HTTP协议本质上也就是基于TCP协议实现的,实现一个HTTP客户端/服务器,本质上就是基于TCP
socket 进行封装。 HTTP客户端:Qt只是提供了HTTP客户端,而没有提供HTTP服务器的库

4.1 核心API

关键类主要是三个 QNetworkAccessManager, QNetworkRequest, QNetworkReplyQNetworkAccessManager 提供了HTTP的核心操作。

  • QNetworkAccessManager 提供了HTTP的核心操作
    在这里插入图片描述

  • QNetworkRequest 表示一个HTTP请求(不含body)

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

在这里插入图片描述

QVariant 表示一个“类型可变”的值,类似于 C 语言中的void*,泛型编程,虽然在C++中还是挺常见的,但是使用门槛还是比较高,里面有一些坑!

  • 其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:
    在这里插入图片描述
    QNetworkReply 表示一个HTTP响应,这个类同时也是QIODevice的子类
    在这里插入图片描述
    此外,QNetworkReply 还有一个重要的信号finished 会在客户端收到完整的响应数据之后触发!

4.2 代码示例

给服务器发送一个GET请求

此处显示的响应结果,大概率是一个HTML,QPlainTextEdit 来进行表示,能够看到响应的原始模样!

QTextEidit(天然支持对HTML的解析),会对HTML进行解析渲染,最终显示的效果就不是原始的HTML。QTextEdit
背后还做了很多工作,当得到的HTML比较大的时候,也会造成卡顿!

QNetworkReply* response = manager->get(request);

get本身不是阻塞函数,get只是负责发出去请求,不负责等待响应回来
finished 信号

在这里插入图片描述
代码:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QNetworkReply>

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

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

    manager = new QNetworkAccessManager(this);
}

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


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

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

总结:

本文首先介绍了网络编程的基础知识,解释了网络编程中传输层的核心协议UDP和TCP,以及Qt框架如何通过提供相应的API简化了网络编程的过程。接着,文章详细讲解了UDP和TCP Socket编程,包括核心API的概述、如何编写带有界面的UDP回显服务器和客户端,以及如何实现TCP服务器和客户端的通信。此外,还介绍了Qt中的HTTP客户端编程,包括QNetworkAccessManagerQNetworkRequestQNetworkReply等关键类的作用和使用方式。

通过本文的学习,读者应该能够理解Qt网络编程的基本概念,掌握使用Qt进行UDP、TCP以及HTTP通信的方法,并能够根据实际需求编写网络应用程序。Qt的模块化设计和信号槽机制大大简化了网络编程的复杂性,使得开发者可以更加专注于业务逻辑的实现。最后,希望读者能够将本文的知识应用到实际项目中,提升网络编程的能力和效率。

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

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

相关文章

基于InP的通用光子集成技术(五)

Meint Smit et al 2014 Semicond. Sci. Technol. 29 083001 9.通用测试 9.1. 晶圆验证 在通用工艺中&#xff0c;必须对每批次晶片的工艺性能进行验证&#xff1b;客户将期待这样的验证。在MPW中&#xff0c;验证用户ASPIC工艺性能是不实际的&#xff1b;每个ASPIC都需要不同的…

Shopee巴西站点凭直播带货超越亚马逊,shopee巴西站热销类目有哪些?

巴西电商市场加速增长&#xff0c;被誉为跨境最后一个蓝海市场&#xff0c;吸引了众多卖家和电商平台的关注。在这一片潜力无限的热土上&#xff0c;有人隔岸观火等待时机&#xff1b;有人却果断迈步积极探索。东南亚及台湾地区的领航电商平台Shopee平台&#xff0c;在巴西强势…

【Python机器学习】利用AdaBoost元算法提高分类性能——在数据集上应用AdaBoost

在之前用过的马疝病数据集上应用AdaBoost。 在一个难数据集上的AdaBoost应用步骤&#xff1a; 1、收集数据&#xff1a;提供的文本文件 2、准备数据&#xff1a;确保类别标签是1河-1而不是1和0 3、分析数据&#xff1a;手工检查数据 4、训练算法&#xff1a;在数据上&#xff…

java-面向对象综合练习

1、文字格斗回合制游戏 需求: 格斗游戏&#xff0c;每个游戏角色的姓名&#xff0c;血量&#xff0c;都不相同&#xff0c;在选定人物的时候&#xff08;new对象的时候&#xff09;&#xff0c;这些信息就应该被确定下来。 举例&#xff1a; 程序运行之后结果为&#xff1a…

加密软件中的RSA和ECC的主要区别是什么

在加密软件中&#xff0c;RSA&#xff08;Rivest-Shamir-Adleman&#xff09;和ECC&#xff08;Elliptic Curve Cryptography&#xff0c;椭圆曲线密码学&#xff09;是两种广泛使用的非对称加密算法&#xff0c;它们之间存在多个关键区别。 1. 算法基础 RSA&#xff1a;基于大…

RPA+AI有什么应用?6大技术融合方向分析 | 实在RPA研究

随着数字化转型的加速&#xff0c;企业正寻求更高效、智能的方法来优化业务流程。机器人流程自动化&#xff08;RPA&#xff09;作为一种快速兴起的技术&#xff0c;已经证明了其在自动化重复性任务方面的能力。然而&#xff0c;当RPA与各种人工智能&#xff08;AI&#xff09;…

外部时钟传送带测量装置

外部时钟&传送带测量装置 1.外部时钟介绍2.循迹模块3.实操过程1.设置2.代码3.效果 链接: keysking-17 1.外部时钟介绍 将GPIO的外部电平接入即可对外部信号进行计数(PS&#xff1a;上面的内部时钟不是所谓的“高速内部时钟”&#xff0c;而是APB1的定时器分支)。当然,GPIO…

zabbix“专家坐诊”第250期问答

问题一 Q&#xff1a;乐维监控社区版监控交换机&#xff0c;能统计出端口的IP流量排名吗&#xff1f; A&#xff1a;社区版没有这个功能 &#xff0c;正式版&#xff0c;流量报表可以实现端口IP流量排行。 问题二 Q&#xff1a;我看了一下乐维有事件平台汇总的功能&#xff0…

浅谈逻辑控制器插件之jp@gc - Parameterized Controller

浅谈逻辑控制器插件之jpgc - Parameterized Controller jpgc - Parameterized Controller是JMeter的一个强大插件&#xff0c;由JMeter Plugins项目提供。此插件允许用户以更加灵活和动态的方式控制测试计划中的采样器执行逻辑。它通过引入参数化概念&#xff0c;使得单个控制…

【学术会议征稿】第四届电气工程与计算机技术国际学术会议(ICEECT2024)

第四届电气工程与计算机技术国际学术会议&#xff08;ICEECT2024&#xff09; 2024 4th International Conference on Electrical Engineering and Computer Technology 第四届电气工程与计算机技术国际学术会议&#xff08;ICEECT2024&#xff09;将于9月27日-29日在哈尔滨举…

汇编语言入门基础(概述)

目录 概述: 1.1 汇编语言与汇编指令 1.2 汇编语言编写程序的工作过程 1.3 数据的表示 1.4 计算机中的存储单元 1.5 计算机中的总线 1.5.1 三种总线 1.5.2 x86CPU性能一览 1.6 内存的读写与地址空间 1.6.1 CPU对存储器的读写 1.6.2 内存地址空间 1.6.3 内存地址空间…

linux虚拟机设置固定ip

修改/etc/sysconfig/network-scripts目录下ifcfg-eth0文件&#xff0c;各虚拟机这个文件名不一致&#xff0c;ifcfg-XX格式 vim /etc/sysconfig/network-scripts/ifcfg-eth0BOOTPROTO设置为static&#xff0c;然后在最后添加固定IP地址和默认网关、DNS等配置&#xff0c;IP地址…

学习笔记 韩顺平 零基础30天学会Java(2024.8.5)

P460 八大Wrapper类 黄色的父类是number&#xff0c;黑色的是自己独立的 P461 装箱和拆箱 手动装箱示例&#xff1a; int n1 100; Interger interger new Interger(n1);//或者&#xff1a;Interger interger Interger.valueOf(n1); 手动拆箱示例&#xff1a; int i interge…

SSM项目学习:用xml配置文件或注解开发实现控制反转和依赖注入

什么是SSM SSMSpring(Spring Framework)Spring MVC mybatis Spring Framework系统架构 Spring Framework学习线路 IoC(Inversion of Control)和DI(Dependency Injection) 他们解决的问题&#xff1a;代码耦合度高的问题&#xff0c;需要类自己new对象&#xff0c;修改部分代…

洗牌算法 和 杨辉三角

目录 一、洗牌算法 1.1 Poker 1.2 PokerBox 1.3 Test 二、杨辉三角 一、洗牌算法 【业务需求】&#xff1a; 第一步&#xff1a;生成52张牌&#xff0c;没有大小王&#xff0c;一共四种花色({"♠", "♥", "♣", "♦")&#xff0c…

爬虫中使用多进程、多线程的混合方式遇到的数据丢失问题

项目场景&#xff1a; 网络爬虫项目&#xff0c;主要实现多进程、多线程方式快速缓存网页资源到MongoDB&#xff0c;并解析网页数据&#xff0c;将信息写入到csv文件中。 问题描述 在单独使用多线程的过程中&#xff0c;是没有问题的&#xff0c;比如这个爬虫示例是爬取豆瓣电…

成都云飞浩容文化传媒有限公司共绘电商服务新蓝图

在这个日新月异的电商时代&#xff0c;每一家企业都渴望在浩瀚的互联网海洋中脱颖而出&#xff0c;实现品牌的飞跃式增长。而在这场没有硝烟的战争中&#xff0c;专业的电商服务成为了企业破局的关键。成都云飞浩容文化传媒有限公司&#xff0c;作为电商服务领域的佼佼者&#…

(ABC三题完整内容)2024年华数杯大学生数学建模竞赛解题思路完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

【mars3d】加载超图s3m模型说明

建议替换Cesium库&#xff0c;换成 超图版本Cesium mars3d mars3d-supermap &#xff0c;需要引入的资源为&#xff1a; "mars3d": ["Cesium-supermap/Widgets/widgets.css", //超图版本Cesium "Cesium-supermap/Cesium.js","mars3d/plu…

vue使用响应式API和页面组件ref相同名称问题

最近在使用vue3vite学习前端的东西。在学习form表单时发现&#xff0c;<el-form>里面的<el-input>绑定数据时&#xff0c;页面输入框在键盘输入之后没有反应&#xff0c;每次只能输入一各字母。 <template><div class"login-container"><…