qt中的网络请求QNetworkAccessManager——两种方法同步与异步

目录

一、qt中的网络请求

1.网络的一些基础知识

网络的GET和POST方式

 网络中的鉴权

2.qt实现网络请求方式get和post

GET 请求

3.qt网络请求中添加鉴权信息

1. 生成鉴权字符串

2. 设置鉴权头部到 QNetworkRequest

3. 简单的网络处理响应和错误

二、同步阻塞网络请求

三、异步非阻塞网络请求

四、两种方式结合使用

五、实战进阶

1.当有多个结构体和多个解析函数的时候,有没有更加优化的方式实现上面的功能

2.拼接两个请求的结果

3.有多个请求,并且需要顺序执行完成,各自处理结果

1.先定义一个队列的结构体

2.处理队列的函数,有处理完成和处理下一个两个函数

 3.增加一个过渡传递的处理函数

4.结合之前的同步与异步的处理,在主函数调用


一、qt中的网络请求

1.网络的一些基础知识

网络的GET和POST方式

网络请求中的GET和POST方式存在显著的区别,以下从多个方面进行详细解释:

GETPOST
请求数据大小限制由于请求参数以查询字符串的形式附加在URL上,因此传输数据量受到URL长度的限制。通常,这个长度限制在2048个字符左右,但实际限制可能因浏览器和服务器设置而异请求参数保存在请求体中,因此理论上不受数据大小限制。但在实际应用中,服务器可能会设置POST请求的数据大小上限,这个上限通常远大于GET请求的限制,例如默认为8M。
安全性由于请求参数暴露在URL中,可能会被保存在日志、浏览器历史记录等地方,因此在涉及敏感数据传输时安全性较低。请求参数保存在请求体中,相对于GET请求,POST请求传输的数据更加安全。然而,需要注意的是,即使使用POST请求,也不能完全保证数据的安全性,因为抓包软件仍然可能捕获到请求的内容。如果需要更高的安全性,可以对数据进行加密处理。
缓存与记录如果请求的是静态资源,可能会被缓存;如果是数据,则通常不会被缓存。此外,由于GET请求的参数会暴露在URL中,因此可能会被保存在浏览器历史记录或服务器日志中。请求的数据不会被缓存,也不会保留在浏览器的历史记录中。这增加了数据传输的安全性,但也意味着如果需要重新获取数据,必须重新发送POST请求。
传参方式参数通过URL进行传递,以“key=value&key=value”的形式出现。参数放在请求体中进行传递,可以通过表单或其他方式进行提交。
TCP数据包通常只产生一个TCP数据包,浏览器会一次性将HTTP头部和数据发送出去。可能产生两个TCP数据包。首先,浏览器发送包含HTTP头部的数据包,服务器响应100 Continue后,浏览器再发送包含实际数据的数据包。然而,需要注意的是,在发送POST请求时,如果客户端没有设置Expect头,服务器可能不会发送100 Continue响应。
应用场景常用于从指定的资源请求数据,如搜索、排序和筛选等操作。由于安全性较低,不建议用于提交敏感信息。通常用于向指定的资源提交数据,如表单提交、文件上传、发布文章等操作。由于安全性较高,适合用于提交敏感信息。
 网络中的鉴权

在网络请求中,鉴权(Authentication)是一个过程,用于验证请求者(通常是客户端)的身份和权限,以确保其有权访问特定的资源或执行特定的操作。鉴权是网络安全性的一部分,旨在防止未经授权的访问和数据泄露。

鉴权是网络请求中确保安全性和访问控制的关键环节。通过验证请求者的身份和权限,可以防止未经授权的访问和数据泄露,保护系统的安全性和数据的完整性。

以下是鉴权在网络请求中的一些关键点和常见方法:

  1. 身份验证:验证请求者是否是其所声称的用户。这通常通过用户名和密码、令牌(如JWT、OAuth令牌)、公钥/私钥对、生物识别或其他身份验证机制来完成。
  2. 授权:确定经过身份验证的请求者是否有权访问请求的资源或执行请求的操作。这通常基于用户的角色、权限或策略来决定。
  3. 令牌:在许多现代系统中,鉴权过程使用令牌(Token)作为验证请求者身份和权限的凭据。令牌通常是一串包含用户身份信息和授权信息的字符串,可以在客户端和服务器之间安全地传递。常见的令牌类型包括JWT(JSON Web Tokens)和OAuth令牌。
  4. 会话管理:在传统的基于会话的鉴权系统中,服务器会为用户创建一个会话(Session),并在会话中存储用户的身份验证和授权信息。服务器为每个会话分配一个唯一的会话标识符(如会话ID),并将其发送给客户端。客户端在后续请求中携带会话ID,以便服务器能够识别用户并检索其会话信息。
  5. API密钥:对于API访问,鉴权通常通过API密钥来实现。API密钥是一个唯一的字符串,用于标识和验证调用API的客户端。客户端在API请求中包含API密钥,服务器验证该密钥的有效性,并据此决定是否授权访问。
  6. OAuth和OpenID Connect:OAuth和OpenID Connect是用于第三方应用程序鉴权和授权的开放标准。它们允许用户授权第三方应用程序访问其受保护的资源,而无需共享其用户名和密码。OAuth主要关注授权,而OpenID Connect在OAuth的基础上增加了身份验证功能。
  7. 挑战/响应机制:在某些情况下,服务器可能会向客户端发送一个挑战(Challenge),要求客户端提供证明其身份和权限的信息。客户端响应挑战并提供必要的信息,然后服务器验证这些信息并据此决定是否授权访问。
  8. HTTPS:鉴权过程通常通过HTTPS(HTTP Secure)协议进行,以确保在客户端和服务器之间传输的数据(包括身份验证和授权信息)得到加密保护,防止被恶意第三方截获和篡改。

2.qt实现网络请求方式get和post

GET 请求

  1. 创建 QNetworkAccessManager:首先,你需要一个 QNetworkAccessManager 对象来发送网络请求。

  2. 设置请求 URL:使用 QUrl 对象设置请求的 URL。

  3. 创建 QNetworkRequest:创建一个 QNetworkRequest 对象,并设置其 URL。

  4. 添加鉴权信息:使用 QNetworkRequest::setRawHeader() 方法添加鉴权头。例如,如果你使用的是 Basic Authentication(基本认证),你可能需要生成一个 Base64 编码的字符串,该字符串包含用户名和密码,并将其作为 Authorization 头的值。

QNetworkAccessManager manager;  
QUrl url("http://example.com/api/resource");  
QNetworkRequest request(url);  // 假设你已经有了一个 Base64 编码的鉴权字符串  
QByteArray authHeaderData = "Basic " + yourBase64EncodedAuthString;  
request.setRawHeader(QByteArray("Authorization"), authHeaderData);  // 发送 GET 请求  
manager.get(request);

POST 请求

  1. 创建 QNetworkAccessManager:同样,你需要一个 QNetworkAccessManager 对象。

  2. 设置请求 URL:使用 QUrl 对象设置请求的 URL。

  3. 创建 QNetworkRequest:与 GET 请求相同,你需要创建一个 QNetworkRequest 对象并设置其 URL。

  4. 添加鉴权信息:与 GET 请求相同,使用 QNetworkRequest::setRawHeader() 方法添加鉴权头。

  5. 创建并发送 POST 数据:使用 QNetworkReply 对象的 post() 方法发送 POST 数据。你需要一个 QNetworkRequest 对象和一个包含 POST 数据的 QByteArray 或 QHttpMultiPart 对象。

QNetworkAccessManager manager;  
QUrl url("http://example.com/api/resource");  
QNetworkRequest request(url);  // 添加鉴权信息(与 GET 请求相同)  
QByteArray authHeaderData = "Basic " + yourBase64EncodedAuthString;  
request.setRawHeader(QByteArray("Authorization"), authHeaderData);  // 准备 POST 数据(这里只是一个简单的示例)  
QByteArray postData = "key1=value1&key2=value2";  // 发送 POST 请求  
QNetworkReply *reply = manager.post(request, postData);

3.qt网络请求中添加鉴权信息

在 Qt 中进行网络请求时,无论是 GET 还是 POST 方式,添加鉴权信息通常意味着在请求头(HTTP headers)中包含一些特定的字段,如 Authorization,来验证客户端的身份。以下是如何在 Qt 的 QNetworkRequest 和 QNetworkAccessManager 中为 GET 和 POST 请求添加鉴权信息的步骤:

1. 生成鉴权字符串

首先,你需要生成一个鉴权字符串。这通常取决于你使用的鉴权机制。对于 Basic Authentication(基本认证),你需要将用户名和密码以特定的格式拼接起来,并使用 Base64 编码。

例如,对于 Basic Authentication:

QNetworkAccessManager manager;  
QUrl url("http://example.com/api/resource");  
QNetworkRequest request(url);  // 设置鉴权头部  
request.setRawHeader(QByteArray("Authorization"), "Basic " + authHeaderData);  // 发送 GET 请求  
QNetworkReply *reply = manager.get(request);  // ... 处理 reply 和错误情况 ...

2. 设置鉴权头部到 QNetworkRequest

然后,你可以将这个 Base64 编码的字符串设置为 Authorization 头的值,并添加到 QNetworkRequest 对象中。

对于 GET 请求:

QNetworkAccessManager manager;  
QUrl url("http://example.com/api/resource");  
QNetworkRequest request(url);  // 设置鉴权头部  
request.setRawHeader(QByteArray("Authorization"), "Basic " + authHeaderData);  // 发送 GET 请求  
QNetworkReply *reply = manager.get(request);  // ... 处理 reply 和错误情况 ...

对于 POST 请求:

QNetworkAccessManager manager;  
QUrl url("http://example.com/api/resource");  
QNetworkRequest request(url);  // 设置鉴权头部  
request.setRawHeader(QByteArray("Authorization"), "Basic " + authHeaderData);  // 准备 POST 数据  
QByteArray postData = "key1=value1&key2=value2";  // 发送 POST 请求  
QNetworkReply *reply = manager.post(request, postData);  // ... 处理 reply 和错误情况 ...
在上面的代码中,"Basic "是 Basic Authentication 机制的标准前缀,后面跟着的是 Base64 编码的用户名和密码字符串。
3. 简单的网络处理响应和错误

不要忘记连接 QNetworkAccessManager 和 QNetworkReply 的信号来处理响应和错误。例如,你可以连接 QNetworkReply::finished() 信号来处理响应完成的情况,以及 QNetworkReply::errorOccurred() 信号来处理网络错误。

connect(reply, &QNetworkReply::finished, this, [reply]() {  if (reply->error() == QNetworkReply::NoError) {  // 处理响应数据  QByteArray responseData = reply->readAll();  // ...  } else {  // 处理错误  qDebug() << "Error:" << reply->errorString();  }  reply->deleteLater(); // 清理 QNetworkReply 对象  
});
确保在不再需要 QNetworkReply对象时调用其 deleteLater()方法,以避免内存泄漏。到此,网络请求的简单方法已经写完了,但是聪明的小伙伴已经发现了,由于网络请求是有延迟的,也就是要等到网络接收finish信号,然后再执行槽函数,而我们知道,如果这个时候有多个请求呢,多个请求用同一个网络对象,这个时候前一个的url地址还没有finish,下一个请求的url地址已经改变,这个时候,第一个的请求就不会收到结束的数据了,此时就需要干预了,本人总结了两种方法来解决这个问题

二、同步阻塞网络请求

主要的方法就是利用QEventLoop在接收网络返回内容结束之前,一直阻塞等待,这样再调用一个函数接口的时候,可以同步返回结果。

定义结构体 A

struct A {int data; // 示例数据// 其他字段
};

主类定义:

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QEventLoop>
#include <QDebug>class NetworkManager : public QObject {Q_OBJECTpublic:NetworkManager(QObject* parent = nullptr) : QObject(parent) {manager = new QNetworkAccessManager(this);}A getA() {QEventLoop loop;A result;// 发起网络请求QNetworkRequest request(QUrl("http://www.example.com"));QNetworkReply* reply = manager->get(request);// 连接槽函数解析数据connect(reply, &QNetworkReply::finished, [&]() {if (reply->error() == QNetworkReply::NoError) {QByteArray response = reply->readAll();result = parseResponse(response);} else {qWarning() << "Network error:" << reply->errorString();}reply->deleteLater();loop.quit();});// 阻塞等待网络请求完成loop.exec();return result;}private:QNetworkAccessManager* manager;A parseResponse(const QByteArray& response) {A parsedData;// 假设解析为整数数据parsedData.data = QString(response).toInt();return parsedData;}
};

主函数:

#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);NetworkManager networkManager;A data = networkManager.getA();qDebug() << "Received data:" << data.data;return a.exec();
}

当然同步的方法是有劣势的,比如阻塞,这个时候是会卡住程序的,因为要等待网络接收完成,对于简单的请求或者并不多的请求数量来说是没什么问题的,但是请求多,需要时常更新请求等等问题的时候,就要使用另外一种方式了,那就是异步请求

三、异步非阻塞网络请求

 定义结构体 A

struct A {int data; // 示例数据// 其他字段
};

主类定义:

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>
#include <functional>class NetworkManager : public QObject {Q_OBJECTpublic:using Callback = std::function<void(const A&)>;NetworkManager(QObject* parent = nullptr) : QObject(parent) {manager = new QNetworkAccessManager(this);}void getA(const Callback& callback) {// 发起网络请求QNetworkRequest request(QUrl("http://www.example.com"));QNetworkReply* reply = manager->get(request);// 连接槽函数解析数据connect(reply, &QNetworkReply::finished, this, [this, reply, callback]() {A result;if (reply->error() == QNetworkReply::NoError) {QByteArray response = reply->readAll();result = parseResponse(response);} else {qWarning() << "Network error:" << reply->errorString();}reply->deleteLater();callback(result);});}private:QNetworkAccessManager* manager;A parseResponse(const QByteArray& response) {A parsedData;// 假设解析为整数数据parsedData.data = QString(response).toInt();return parsedData;}
};

主函数:

#include <QCoreApplication>void handleResult(const A& data) {qDebug() << "Received data:" << data.data;QCoreApplication::quit();
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);NetworkManager networkManager;networkManager.getA(handleResult);return a.exec();
}

四、两种方式结合使用

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>
#include <functional>
#include <QEventLoop>class NetworkManager : public QObject {Q_OBJECTpublic:using Callback = std::function<void(const A&)>;NetworkManager(QObject* parent = nullptr) : QObject(parent) {manager = new QNetworkAccessManager(this);}void getAAsync(const Callback& callback) {// 发起网络请求QNetworkRequest request(QUrl("http://www.example.com"));QNetworkReply* reply = manager->get(request);// 连接槽函数解析数据connect(reply, &QNetworkReply::finished, this, [this, reply, callback]() {A result;if (reply->error() == QNetworkReply::NoError) {QByteArray response = reply->readAll();result = parseResponse(response);} else {qWarning() << "Network error:" << reply->errorString();}reply->deleteLater();callback(result);});}A getASync() {QEventLoop loop;A result;getAAsync([&](const A& data) {result = data;loop.quit();});loop.exec(); // 阻塞,直到数据准备好return result;}private:QNetworkAccessManager* manager;A parseResponse(const QByteArray& response) {A parsedData;// 假设解析为整数数据parsedData.data = QString(response).toInt();return parsedData;}
};

五、实战进阶

1.当有多个结构体和多个解析函数的时候,有没有更加优化的方式实现上面的功能

可以通过泛型和策略模式来优化代码结构。你可以创建一个通用的异步请求处理类,该类可以接受不同的解析函数和回调函数,以处理不同类型的结构体。

定义不同的结构体:

struct A {int data;// 其他字段
};struct B {QString text;// 其他字段
};

定义解析函数:

A parseA(const QByteArray& response) {A parsedData;// 假设解析为整数数据parsedData.data = QString(response).toInt();return parsedData;
}B parseB(const QByteArray& response) {B parsedData;// 假设解析为字符串数据parsedData.text = QString(response);return parsedData;
}

通用网络管理器类:

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>
#include <functional>
#include <QEventLoop>class NetworkManager : public QObject {Q_OBJECTpublic:using ErrorCallback = std::function<void(const QString&)>;NetworkManager(QObject* parent = nullptr) : QObject(parent) {manager = new QNetworkAccessManager(this);}template <typename T>void getAsync(const QUrl& url, std::function<T(const QByteArray&)> parser, std::function<void(const T&)> callback, ErrorCallback errorCallback = nullptr) {QNetworkRequest request(url);QNetworkReply* reply = manager->get(request);connect(reply, &QNetworkReply::finished, this, [this, reply, parser, callback, errorCallback]() {if (reply->error() == QNetworkReply::NoError) {QByteArray response = reply->readAll();T result = parser(response);callback(result);} else {if (errorCallback) {errorCallback(reply->errorString());} else {qWarning() << "Network error:" << reply->errorString();}}reply->deleteLater();});}template <typename T>T getSync(const QUrl& url, std::function<T(const QByteArray&)> parser, ErrorCallback errorCallback = nullptr) {QEventLoop loop;T result;getAsync<T>(url, parser, [&](const T& data) {result = data;loop.quit();}, [&](const QString& error) {if (errorCallback) {errorCallback(error);}loop.quit();});loop.exec();return result;}private:QNetworkAccessManager* manager;
};

主函数:

#include <QCoreApplication>void handleResultA(const A& data) {qDebug() << "Received data for A:" << data.data;
}void handleResultB(const B& data) {qDebug() << "Received data for B:" << data.text;
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);NetworkManager networkManager;// 异步请求示例networkManager.getAsync<A>(QUrl("http://www.example.com/api/a"), parseA, handleResultA);networkManager.getAsync<B>(QUrl("http://www.example.com/api/b"), parseB, handleResultB);// 同步请求示例A dataA = networkManager.getSync<A>(QUrl("http://www.example.com/api/a"), parseA);qDebug() << "Synchronously received data for A:" << dataA.data;B dataB = networkManager.getSync<B>(QUrl("http://www.example.com/api/b"), parseB);qDebug() << "Synchronously received data for B:" << dataB.text;return a.exec();
}

2.拼接两个请求的结果

void Widget::getCsync(std::function<void (const C &)> callback, ErrorCallback errorCallback)
{C result;int completedRequests = 0;const int totalRequests = 2;auto checkCompletion = [&]() {if (++completedRequests == totalRequests) {callback(result);}};getAsync<int>(QUrl("http://www.example.com/api/part1"), parsePart1,[&](const int& data) {result.dataA = data;checkCompletion();},errorCallback);getAsync<QString>(QUrl("http://www.example.com/api/part2"), parsePart2,[&](const QString& data) {result.textB = data;checkCompletion();},errorCallback);
}

3.有多个请求,并且需要顺序执行完成,各自处理结果

因为异步的时候,如果这时候有多个请求呢,就会遇到我说过的问题,前一个请求还没结束,下一个就开始了,这样前一个的槽函数就不会执行,这个时候就需要增加一些方法来让第一个请求结束之后再执行第二个,而且整体都要是异步的。

那么就来到我们最终版本,队列异步处理

1.先定义一个队列的结构体
// 定义一个通用的处理结构
struct RequestInfo {std::function<QUrl()> urlGenerator;std::function<void(const QByteArray&)> parser;std::function<void(const QByteArray&)> callback;
};
2.处理队列的函数,有处理完成和处理下一个两个函数
template <typename T>
void Widget::handleRequestCompleted(const QByteArray& response, std::function<T (const QByteArray&)> parse, std::function<void(const T&)> callback,  QQueue<RequestInfo>& requestQueue) {qDebug() << "Request completed.";// 处理数据,可以通过回调函数调用不同的处理函数T data = parse(response);callback(data);if (!requestQueue.isEmpty()) {processNextRequest(requestQueue);} else {qDebug() << "All requests completed.";}
}void Widget::processNextRequest(QQueue<RequestInfo>& requestQueue) {RequestInfo requestInfo = requestQueue.dequeue();QUrl url = requestInfo.urlGenerator();qDebug() << "Starting request for URL:";auto parserFunction = requestInfo.parser;  // 将解析函数保存在局部变量中auto callbackFunction = requestInfo.callback;  // 将回调函数保存在局部变量中getAsync<QByteArray>(url, [&](const QByteArray& response) { return this->parseALL(response); }, [callbackFunction, this](const QByteArray& response) {callbackFunction(response);  // 调用回调函数}, handleError);
}
 3.增加一个过渡传递的处理函数
//异步中多请求过渡的函数
QByteArray Widget::parseALL(const QByteArray& response)
{QJsonDocument doc = QJsonDocument::fromJson(response);// 检查是否解析成功if (doc.isNull()) {qDebug() << "解析 JSON 失败";} else {// 获取根对象QJsonObject rootObj = doc.object();// 检查是否存在 "code" 键if (rootObj.contains("code")) {// 获取 "code" 键对应的值int codeValue = rootObj["code"].toInt();// 打印值(如果不为 0)if (codeValue != 0 && codeValue != 200) {qDebug() << "parseALL" << QString::fromUtf8(response);}else{qDebug() << "访问成功code:" << codeValue;}}}return response;
}
4.结合之前的同步与异步的处理,在主函数调用
    // 将不同的请求加入队列requestQueue.enqueue({ [&]() { return this->generateUrl1(); },[&](const QByteArray& response) { return this->parseALL(response); },[&](const QByteArray& response) {handleRequestCompleted<A>(response,[this](const QByteArray& response) { return this->parseA(response); },[this](const A& data) { return this->handleAData(data); },requestQueue);}});requestQueue.enqueue({ [&]() { return this->generateUrl2(); },[&](const QByteArray& response) { return this->parseALL(response); }, [&](const QByteArray& response) {handleRequestCompleted<B>(response,[this](const QByteArray& response) { return this->parseB(response); },[this](const B& data) { return this->handleResultB(data); },requestQueue);}});requestQueue.enqueue({ [&]() { return this->generateUrl3(); },[&](const QByteArray& response) { return this->parseALL(response); }, [&](const QByteArray& response) {handleRequestCompleted<C>(response,[this](const QByteArray& response) { return this->parseC(response); },[this](const C& data) { return this->handleCData(data); },requestQueue);}});if (!requestQueue.isEmpty()) {processNextRequest(requestQueue);} else {qDebug() << "No URLs to process.";}

上方的队列,由于我将parse处理函数,generate生成url函数,handle回调处理函数都写成了类的私有函数,所以这里需要传函数指针,如果把这些函数写在类外,则不需要像我的写法这样麻烦。可能写的会有点乱,而且实际的代码已经改的面目全非,如果有问题可以留言评论。

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

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

相关文章

密码与网络安全(一):专栏导读

1.专栏目的 这个专栏的核心目的是提升博主自己的密码与网络安全知识&#xff0c;其次也想将相关的学习收获分享给感兴趣的小伙伴。博主自己的工作主要量子技术相关&#xff0c;身边的同事基本上也是物理专业出身&#xff0c;最近和单位密码领域同事聊天时他提到一个思路很好的启…

【Linux 网络】网络基础(三)(其他重要协议或技术:DNS、ICMP、NAT)

一、DNS&#xff08;Domain Name System&#xff09; DNS 是一整套从域名映射到 IP 的系统。 1、DNS 背景 TCP/IP 中使用 IP 地址和端口号来确定网络上的一台主机的一个程序&#xff0c;但是 IP 地址不方便记忆。于是人们发明了一种叫主机名的东西&#xff0c;是一个字符串&…

当没用git工具是怎么快速下载项目

https://github.com/lucasb-eyer/pydensecrf/archive/refs/heads/master.zip 是一个用于直接下载 GitHub 仓库中最新代码的链接。让我们详细解释一下这个 URL 的结构以及它的用途&#xff1a; ### URL 结构说明 1. **基本仓库 URL**&#xff1a; https://github.com/l…

学习笔记——网络参考模型——TCP/IP模型

二、TCP/IP模型 TCP/IP模型(TCP/IP协议栈)&#xff1a;很多个互联网协议的集合&#xff0c;其中以TCP和IP为主&#xff0c;将这些协议的集合称为TCP/IP协议栈。目前使用最多的协议模型。 因为OSI协议栈比较复杂&#xff0c;且TCP和IP两大协议在业界被广泛使用&#xff0c;所以…

JavaScript 动态网页实例 —— 窗口控制

除了打开和关闭窗口之外,还有很多其他控制窗口的方法。例如,可以使用 window.focus()方法使窗口获得焦点,也可以利用与其相对的window.blur 方法使窗口失去焦点。本节介绍移动窗口、改变窗口大小、窗口滚动、窗口超时操作、常用窗口事件、常用窗口扩展等窗口控制的方法和手段。…

Docker 部署 OCRmyPDF、提取PDF内容

一、镜像导入 # 拉取镜像 docker pull jbarlow83/ocrmypdf# 导出镜像 docker save -o /data/ocrmypdf/ocrmypdf.tar jbarlow83/ocrmypdf:latest # 导入镜像 docker load -i ocrmypdf.tar二、调取镜像 # 【调用镜像】&#xff08;以下2选1&#xff09;# 1-执行后删除容器【官方…

vue3 树节点如何通过子节点的parentid找到父节点数据

在Vue 3中&#xff0c;如果你有一个树形结构的数据&#xff0c;并且想要通过子节点的parentId找到其父节点数据&#xff0c;你可以使用递归组件或者在组件的方法中实现递归逻辑来遍历树形数据。 以下是一个简单的示例&#xff0c;展示如何在Vue 3组件中实现这个功能&#xff1…

[每周一更]-(第99期):MySQL的索引为什么用B+树?

文章目录 B树与B树的基本概念B树&#xff08;Balanced Tree&#xff09;B树&#xff08;B-Plus Tree&#xff09;对比 为什么MySQL选择B树1. **磁盘I/O效率**2. **更稳定的查询性能**3. **更高的空间利用率**4. **并发控制** 其他树结构的比较参考 索引是一种 数据结构&#x…

【启明智显分享】WIFI6开发板ZX6010:开源OpenWrt SDK,接受定制!

在数字化飞速发展的当下&#xff0c;网络速度和稳定性已成为各行各业不可或缺的关键因素。今天&#xff0c;我们为大家推荐一款基于IPQ6010的AX1800方案ZX6010 Wi-Fi6开发板&#xff0c;为您的网络世界注入强大动力。 一、超强硬件配置 ZX6010搭载IPQ6010四核ARM Cortex A53处…

LeeCode热题100(两数之和)

本文纯干货&#xff0c;看不懂来打我&#xff01; 自己先去看一下第一题的题目两数之和&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 简单来说就是让你在一个数组里面找两个数&#xff0c;这两个数的和必须满足等于目标值target才行。 我认为你要是没有思路的话&a…

营造科技展厅主题氛围,多媒体应用有哪些新策略?

长久以来&#xff0c;展厅作为线下向公众传递信息的窗口&#xff0c;其设计风格与内容主题紧密相连&#xff0c;展现出千姿百态的面貌。然而&#xff0c;随着数字多媒体技术的日新月异&#xff0c;展厅不再仅仅是传统的信息展示平台&#xff0c;而是成为了引领内容展示潮流的风…

modelscope只读盘无法启动模型问题

简介 将提前下载好的modelscope模型目录&#xff0c;映射到容器中作为只读模式时会报错。 原因分析 使用modelscope加载模型时会去修改ast_index和具体模型的一些隐藏文件。 解决方案 如果你使用的框架支持直接传model的绝对路径&#xff08;例如vllm&#xff09;&#xf…

数仓建模—需求管理

文章目录 数仓建模—需求管理需求管理的背景日常需求管理项目需求管理要明确用户明确指标明确业务规则明确取数来源明确用户与指标对应关系总结数仓建模—需求管理 今天我们简单介绍一下数仓的需求管理,其实需求管理是一个软技能,为什么说是软技能,因为即使你不会需求管理或…

DW怎么Python:探索Dreamweaver与Python的交织世界

DW怎么Python&#xff1a;探索Dreamweaver与Python的交织世界 在数字世界的广袤天地中&#xff0c;Dreamweaver&#xff08;简称DW&#xff09;与Python这两大工具各自闪耀着独特的光芒。DW以其强大的网页设计和开发能力著称&#xff0c;而Python则以其简洁、易读和强大的编程…

【Git】git合并分支指定内容到主分支

git合并分支指定内容到主分支 在现实开发中&#xff0c;往往需要合并分支内容&#xff0c;如下图&#xff1a; 我们平时在其他分支修改了部分代码&#xff0c;如何将分支部分代码合并到主分支上面呢&#xff1f; 合并步骤&#xff1a; 1、切换当前到主分支 git checkout m…

大型制造业集团IT信息化总体规划方案(65页PPT)

方案介绍&#xff1a; 本大型制造业集团IT信息化总体规划方案旨在通过构建先进、高效、稳定的IT信息化系统&#xff0c;支撑集团各业务领域的运营和管理需求&#xff0c;促进集团整体运营效率和竞争力的提升。通过实施本项目&#xff0c;集团将能够更好地应对市场变化和客户需…

初级前端开发岗

定位&#xff1a; 日常任务的辅助执行者&#xff0c;前端基础建设的参与者。 素质要求&#xff1a; 是否遵循部门敏捷流程、规范、P0制度&#xff1b;具备良好的沟通和协作能力;负责日常迭代任务的落地执行;拥有较强的执行力&#xff0c;能够灵活解决问题; 职责&#xff1a…

【Linux-Yocto】

Linux-Yocto ■ 1.1 安装 Git 与配置 Git 用户信息■ 1.2 获取 Yocto 项目■ 1.3 开始构建 Yocto 文件系统■ 1.4 构建 SDK 工具■■■ ■ 1.1 安装 Git 与配置 Git 用户信息 sudo apt-get install git git config --global user.name "username" // 配置 Git 用户名…

python绘制piper三线图

piper三线图 Piper三线图是一种常用于水化学分析的图表&#xff0c;它能够帮助我们理解和比较水样的化学成分。该图表由三个部分组成&#xff1a;两个三角形和一个菱形。两个三角形分别用于显示阳离子和阴离子的相对比例&#xff0c;而菱形部分则综合显示了这些离子比例在水样…

十四天学会Vue——Vue 组件化编程(理论+实战)(第四天)

二、 Vue组件化编程 2.1 组件化模式与传统方式编写应用做对比&#xff1a; 传统方式编写应用 依赖关系混乱&#xff0c;不好维护&#xff1a;例如&#xff1a;比如需要引入js1&#xff0c;js2&#xff0c;js3&#xff0c;但是js3需要用到js1、2的方法&#xff0c;所以js1、2…