网络编程(21)——通过beast库快速实现http服务器
目录
二十一、day21
1. 头文件和作用域重命名
2. reponse时调用的一些函数
3. http_connection
a. 构造函数
b. start()
c. process_request()
d. create_response()
e. create_post_response()
f. write_response()
4. Server
5. 主函数
6. 测试
1)测试get
2)测试post
二十一、day21
上一节学习了一个http服务器是如何实现的,以及每一步函数的实现过程。但上面大部分函数其实以及被封装好了,我们只需要调用就行了,今天跟着恋恋风辰大佬学习如何通过beast网络库快速搭建http服务器。
其实官网以及给出了如何通过beast快速搭建服务器的示例,可以参考boost官网“
Chapter 1. Boost.Beastwww.boost.org/doc/libs/1_86_0/libs/beast/doc/html/index.html
视频参考:
【C++ 网络编程(22) beast网络库实现http服务器】
https://www.bilibili.com/video/BV1Ck4y1T7na?vd_source=cb95e3058c2624d2641da6f4eeb7e3a1www.bilibili.com/video/BV1Ck4y1T7na?vd_source=cb95e3058c2624d2641da6f4eeb7e3a1
1. 头文件和作用域重命名
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <ctime>
#include <cstdlib>
#include <memory>
#include <string>
#include <json/json.h>
#include <json/value.h>
#include <json/reader.h>
#include <iostream>// 因为beast、http、net是作用域,所以可以直接用namespace重命名
// 但是tcp是一个类,所以只能通过using重命名
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;
2. reponse时调用的一些函数
namespace my_program_state {std::size_t request_count() { // 统计请求的次数static std::size_t count = 0;return count++;}std::time_t now() { // 当前的时间return std::time(0);}
}
只封装两个函数,一个返回请求次数,一个返回当前的时间戳
3. http_connection
封装一个http_connection类用于处理 HTTP 请求和响应。负责接收客户端请求,解析请求内容,生成适当的响应,并将响应发送回客户端。同时,该类还管理连接的超时处理,确保长时间未响应的连接能够及时关闭
class http_connection : public std::enable_shared_from_this<http_connection> {
private:tcp::socket _socket;beast::flat_buffer _buffer{ 8192 }; // 缓存区_bufferhttp::request<http::dynamic_body> _request;// 请求头http::response<http::dynamic_body> _response; // 回应net::steady_timer _deadline{ // 定时器_socket.get_executor(),std::chrono::seconds(60)};void read_request() {}void check_deadline() {}void process_request() {}void create_response() {}void write_response() {}void create_post_response() {}public:http_connection(tcp::socket&& socket) : _socket(std::move(socket)) {}void start() {read_request(); // 读请求check_deadline(); // 判断超时}};
http_connection类继承std::enable_shared_from_this<T>模板类,便于使用shared_from_this()函数实现伪闭包。http_connection类成员变量的解释如下:
- _buffer:定义一个缓存区,缓存区大小不超过8k
- _request:构造一个请求头,类型为http::request<http::dynamic_body>
- dynamic_body:允许发送各种类型的请求
- string_body:只允许发文本类型的请求
- _response:响应,同样也是dynamic_body类型
- _deadline:定时器,用于异步操作时的超时控制或延时任务,使用的是steady_timer定时器对象,即使系统时间被改变,定时器仍能够按照预期的时间触发
- get_executor():获取于socket相关的执行器,也就是说,定时器和socket共享相同的IO线程资源
- std::chrono::seconds(60):定时器在60s后被触发
成员函数的实现如下:
a. 构造函数
http_connection(tcp::socket socket) : _socket(socket) {}
该构造函数会报错,因为boost::asio::basic_stream_socket 类不支持复制构造。因此,在构造函数中尝试通过复制传递 socket 时,会发生编译错误,socket 只能通过移动语义来传递。
正确的构造函数如下:
http_connection(tcp::socket&& socket) : _socket(std::move(socket)) {}
为什么http_connection(tcp::socket socket)中按值传递不会调用复制构造函数,而是在初始化成员变量_socket(socket)时才调用复制构造函数?
http_connection(tcp::socket socket) 中的参数 socket 是按值传递的,这意味着它是从调用者那里“拷贝”进来的。由于 tcp::socket 禁止复制,但支持移动,所以编译器会自动尝试使用移动构造函数来初始化这个按值传递的参数。如果调用者传入的是一个临时对象(例如 std::move(socket) 或新创建的对象),那么这个按值传递的 socket 实际上是“移动”进来的,而不是复制进来的。
为什么仅在 _socket(socket) 这里出错?
在构造函数的初始化列表中,_socket(socket) 试图复制socket 参数,因为没有显式地使用 std::move。按值传递的参数 socket 本身是一个左值,要想移动它,必须显式地使用 std::move,将其转换为右值引用。这就是为什么必须在初始化列表中使用 std::move(socket)。
总结:
- 按值传递的 tcp::socket 是通过移动语义构造的,不会有问题。
- 但在初始化成员变量时,如果不显式使用 std::move,编译器会尝试复制 socket 参数,这是不允许的。因此,必须使用 std::move 使其变成右值,从而调用移动构造函数来初始化成员变量
除此之外,还有另外一种方式可以调用外界的socket,就是通过引用:
1)引用
引用绑定到已有的 socket 对象,并且始终指向这个对象,没有复制或移动。保证了在 http_connection 类中操作的 socket 与外部的 socket 是同一个对象,因此可以共享状态。
但是,由于 _socket 引用的是外部传入的对象,所以在 http_connection 对象的生命周期内,这个 socket 对象必须始终存在。如果 socket 对象被销毁了,引用就变成悬空引用,可能导致未定义行为。
tcp::socket& _socket;
http_connection(tcp::socket& socket) : _socket(socket)
2)移动构造函数
但如果我们像http_connection独立的管理connection,而不需要接收外界传递进来的socket,那么如何做?
tcp::socket _socket;
http_connection(tcp::socket socket) : _socket(std::move(socket))
或者这样写
tcp::socket _socket;
http_connection(tcp::socket&& socket) : _socket(std::move(socket))
其实 tcp::socket&& 和 tcp::socket 完全不一样,一个是按值传递,一个是右值引用,但因为socket中禁用了复制构造函数,允许移动,所以编译器会自动尝试使用移动构造函数来初始化这个按值传递的参数,所以这里都是相同的。但在其他时候,不能这样用,这两种传递完全不同。
特性 | 引用 (tcp::socket&) | 移动语义 (tcp::socket&&) |
---|---|---|
对象所有权 | 由外部对象管理 | 由 http_connection 管理 |
效率 | 无复制或移动,效率高 | 通过移动语义高效转移对象所有权 |
生命周期依赖性 | 依赖于外部对象的生命周期 | 不依赖外部对象生命周期,自行管理 |
适用场景 | 适合共享已有对象并保证生命周期的一致性 | 适合 http_connection 需要完全控制 socket 对象 |
b. start()
void start() {read_request(); // 读请求check_deadline(); // 判断超时}
http_connection类就是负责服务器与客户端的联系,如果server接收到一个新的客户端连接,那么就创建一个http_connection对象,并调用start函数开始读客户端的请求。
其中,read_request()函数的实现如下
void read_request() {auto self = shared_from_this(); // 伪闭包http::async_read(_socket, _buffer, _request,[self](beast::error_code ec, std::size_t bytes_transferred) {boost::ignore_unused(bytes_transferred); // 没用到bytes_transferred参数,编译器会警告,这里直接忽略掉if (!ec) {self->process_request();}});}
调用异步读async_read函数从socket读取HTTP请求,数据存储至_buffer,解析后的数据存储至_request
- _socket: 表示与客户端的通信套接字,负责接收数据
- _buffer: 用于临时存储接收到的原始数据(未解析的字节流)
- _request: HTTP 请求对象,解析后的 HTTP 请求会存储在这个对象中
读取成功后调用lambda函数,因为async_read的回调函数中必须有error_code 和 bytes_transferred参数,但后者我们并没有在lambda函数体中使用到,为了避免编译器警告,通过boost::ignore_unused函数忽略该参数。当HTTP请求被成功读取后,调用process_request函数处理请求头。
void check_deadline() {auto self = shared_from_this(); // 伪闭包_deadline.async_wait([self](boost::system::error_code ec) {if (!ec) {self->_socket.close(ec);}});}
check_deadline() 通常用于实现超时处理。比如在一个网络服务中,客户端可能需要在一定时间内完成请求或响应。如果超过了这个时间(如60秒),定时器会触发,然后关闭客户端的连接。这样可以避免占用资源过久,确保服务器的稳定运行。
之前学的tcp是一个长连接,但今天学习的http是一个短连接,如果处理请求时间太长,我们应该主动将其连接断掉。
通过调用异步等待async_wait函数,用于等待定时器的超时事件发生。这个函数不会阻塞当前线程,而是让程序继续执行其他任务,当定时器时间到时,执行给定的回调函数。该回调函数用于关闭套接字,断开客户端与服务器的通信连接。
该函数还有另外一种写法,你觉得对吗?
void check_deadline() {auto self = shared_from_this(); // 伪闭包_deadline.async_wait([this](boost::system::error_code ec) {if (!ec) {this->_socket.close(ec);}});}
其实这个函数有风险存在,因为当调用check_deadline()时,需要过60s后判断超时后才会执行lambda函数,但是比如在58s在执行这个lambda函数时,http_connection因为某种原因(网络断开,引用计数减为0)被回收,那么lambda就会出错(this->_socket.close(ec);中this指向的空间发生了改变,那么这个close的执行是无效的,系统会崩溃)。所以必须将self传进来,让lambda捕获该参数,使其引用计数加一,实现伪闭包。
auto self = shared_from_this(); // 伪闭包_deadline.async_wait([sel,thisf](boost::system::error_code ec) {if (!ec) {this->_socket.close(ec);}});
还有,如果将self显式放在捕获列表中,那么无论是否在 lambda 体内使用,引用计数都会增加,因为捕获行为本身就会创建一个 shared_ptr 的拷贝。
注意【self】和【=】的区别,后者虽然也会捕获self,但如果在函数体没有使用到的话,编译器会优化,不让其创建实例。
c. process_request()
该函数用于处理不同的HTTP请求类型(GET或POST),并根据请求的具体方法生成相应的响应数据,返回给客户端。
void process_request() {_response.version(_request.version());_response.keep_alive(false); // true是长连接,false是短连接switch (_request.method()) {case http::verb::get:_response.result(http::status::ok);_response.set(http::field::server, "Beast");create_response();break;case http::verb::post:_response.result(http::status::ok);_response.set(http::field::server, "Beast");create_post_response();break;default:_response.result(http::status::bad_request);_response.set(http::field::content_type, "text/plain");beast::ostream(_response.body()) << "Invalid request-method"<< std::string(_request.method_string()) << "'";break;}write_response();}
首先,将响应对象_response的HTTP版本设置为与请求对象_request相同的版本。HTTP 请求和响应都包含一个版本号,通常是 HTTP/1.1 或 HTTP/1.0,该操作确保响应与请求的协议版本匹配。
然后设置为短连接,keep_alive 用于决定是否启用长连接。在 HTTP/1.1 中,长连接允许客户端在同一个 TCP 连接上发送多个请求。
- false: 表示使用短连接,即在发送完响应后,服务器将关闭连接。
- true: 表示启用长连接,连接不会在响应后立即关闭,客户端可以继续发送更多请求。
最后,判断请求方法是GET还是POST。
如果请求方法是GET,那么
- 设置响应状态为
http::status::ok
(200 OK),表示请求成功 - 设置响应头字段
server
,其值为"Beast"
,表明服务器使用了Beast
库 - 调用
create_response()
来生成 GET 请求的响应数据(内容在create_response()
中处理)
如果请求方法是POST,那么
- 设置响应状态为
http::status::ok
,表示请求成功 - 同样设置
server
字段 - 调用
create_post_response()
,处理 POST 请求的特定逻辑
如果是未被明确处理的 HTTP 方法(如 PUT、DELETE 等),默认行为是
- 设置响应状态为
http::status::bad_request
,表示这是一个无效请求 - 设置响应头字段
content-type
为"text/plain"
,告知客户端响应的内容类型是纯文本 - 使用
beast::ostream(_response.body())
向响应_response的正文中写入一条错误消息,说明这个请求使用了无效的 HTTP 方法,并附上具体的请求方法字符串
d. create_response()
该函数生成GET请求的响应数据。
void create_response() {if (_request.target() == "/count") {_response.set(http::field::content_type, "text/html");beast::ostream(_response.body())<< "<html>\n"<< "<head><title>Request count</title></head>\n"<< "<body>\n"<< "<h1>Request count</h1>\n"<< "<p>There have been "<< my_program_state::request_count()<< " requests so far.</p>\n"<< "</body>\n"<< "</html>\n";}else if (_request.target() == "/time") {_response.set(http::field::content_type, "text/html");beast::ostream(_response.body())<< "<html>\n"<< "<head><title>Current time</title></head>\n"<< "<body>\n"<< "<h1>Current time</h1>\n"<< "<p>The current time is "<< my_program_state::now()<< " seconds since the epoch.</p>\n"<< "</body>\n"<< "</html>\n";}else {_response.result(http::status::not_found);_response.set(http::field::content_type, "text/plain");beast::ostream(_response.body()) << "File not found\r\n";}}
1)判断请求的URL目标路径是否为‘/count’,表示客户端请求获取请求计数
- 在这种情况下,响应将设置内容类型为 text/html,表示返回的是一个 HTML 页面
- 然后通过 beast::ostream(_response.body()) 将 HTML 数据写入到响应的正文中,内容包括页面的标题“Request count”和请求计数
- my_program_state::request_count() 返回当前的请求计数,该值会被插入到响应的 HTML 中,显示在页面上
生成的HTML如下:
<html>
<head><title>Request count</title></head>
<body>
<h1>Request count</h1>
<p>There have been [请求计数] requests so far.</p>
</body>
</html>
2)判断请求的URL目标路径是否为‘/time’,表示客户端请求当前的系统时间
- 响应同样设置内容类型为 text/html
- 生成一个 HTML 页面,显示当前时间。时间通过 my_program_state::now() 来获取,返回自 Unix 纪元以来的秒数
生成的HTML如下:
<html>
<head><title>Current time</title></head>
<body>
<h1>Current time</h1>
<p>The current time is [当前秒数] seconds since the epoch.</p>
</body>
</html>
3)判断请求的URL目标路径既不是/count,也不是/time,则认为该资源不存在
- 设置响应状态为 http::status::not_found,即 HTTP 404 Not Found
- 设置内容类型为 text/plain,表示返回的是纯文本
- 然后向响应正文写入一条简单的错误消息:File not found\r\n
e. create_post_response()
该函数处理客户端发送的 POST 请求,主要处理 /email 路径的请求,并解析接收到的 JSON 数据。函数根据收到的 POST 数据生成不同的响应。
void create_post_response() {if (_request.target() == "/email") {// 读取并打印收到的requestauto& body = this->_request.body();auto body_str = boost::beast::buffers_to_string(body.data());std::cout << "receive body is " << body_str << std::endl;// 构造返回的responsethis->_response.set(http::field::content_type, "text/json");Json::Value root; // 发送的根Json::Reader reader;Json::Value src_root; // 原始的根// 解析数据bool parse_success = reader.parse(body_str, src_root);if (!parse_success) { // 解析失败std::cout << "Failed to parse JSON data!" << std::endl;root["error"] = 1001;std::string jsonstr = root.toStyledString();beast::ostream(this->_response.body()) << jsonstr; // 将数据写入bodyreturn;}// 解析成功auto email = src_root["email"].asString(); // 将收到的email转为stringstd::cout << "email is " << email << std::endl;root["error"] = 0;root["email"] = src_root["email"];root["msg"] = "recevie email post success";std::string jsonstr = root.toStyledString(); // 序列化root数据beast::ostream(this->_response.body()) << jsonstr; // 将数据写入body}else {_response.result(http::status::not_found);_response.set(http::field::content_type, "text/plain");beast::ostream(_response.body()) << "File not found\r\n";}}
1)如果客户端发送的POST请求的目标路径是‘/email’:
- 读取并打印请求的 body: 从 _request.body() 中获取请求体数据(通常是客户端通过 POST 方式发送的内容),并将其转换为字符串格式,然后打印
- 设置响应的内容类型:服务器将返回 JSON 格式的响应,因此设置响应的 Content-Type 为 text/json
- 解析 JSON 数据:使用 Json::Reader 对请求体中的字符串进行 JSON 解析,解析 body_str 中的 JSON 数据,并将其存储在 src_root 中。如果解析失败,parse_success 将为 false。
- 如果解析失败,打印错误消息并构造带有错误码的 JSON 响应,比如
{"error": 1001
}
-
- 如果解析成功,从 src_root 中提取 email 字段,并打印它
- 构造返回的 JSON 响应,包含 email 和成功消息
- 最后,将 root 数据序列化为字符串并写入响应体中
返回的 JSON 响应内容类似于:
{"error": 0,"email": "example@example.com","msg": "receive email post success"
}
2)如果客户端的 POST 请求目标路径不是 /email,服务器返回 HTTP 404 Not Found 错误
f. write_response()
当系统解析完请求后并执行过GET/POST的相应操作生成响应后,执行write_response函数,将生成的 HTTP 响应异步写回客户端,并在发送完成后执行一些清理操作。
void write_response() {auto self = shared_from_this(); // 伪闭包_response.content_length(_response.body().size()); // 响应的长度http::async_write(_socket, _response, [self](beast::error_code ec, std::size_t) {// 只关闭服务器发送端,客户端收到服务器的响应后,也关闭客户端的发送端self->_socket.shutdown(tcp::socket::shutdown_send, ec);self->_deadline.cancel();});}
首先,设置响应的 Content-Length,即响应正文的长度,表示服务器将要发送的内容大小,并将该长度赋值到 HTTP 头部的 Content-Length 字段。
然后,调用异步写函数将响应_response发送到客户端,发送完成后关闭连接:
- 关闭 TCP 连接的发送端,这意味着服务器已经完成了发送操作,但连接仍然可以用于接收数据
- tcp::socket::shutdown_send 表示只关闭发送端,而保留接收端未关闭。这是为了遵循 TCP 的四次挥手协议:客户端在接收到服务器的响应后,应关闭自己的发送端,并最终关闭整个连接
- 在调用 shutdown() 时遇到错误,错误信息会存储在 ec 中,但不做额外处理
最后,取消定时器,表示当前操作已经完成,不再需要超时检测了,这样可以防止超时处理逻辑错误地关闭仍然活跃的连接。
4. Server
为了方便,server直接写为一个函数,不封装为类
void http_server(tcp::acceptor& acceptor, tcp::socket& socket) {acceptor.async_accept(socket, [&](boost::system::error_code ec) {if (!ec) {// 创建一个http_connection新实例,并调用start函数std::make_shared<http_connection>(std::move(socket))->start();}http_server(acceptor, socket);});
}
调用async_accept函数异步等待客户端的连接,如果有新的连接,那么创建一个http_connection新实例,并调用start函数,读取客户端发送的请求头并执行相应操作。
5. 主函数
int main()
{try {auto const address = net::ip::make_address("127.0.0.1");unsigned short port = static_cast<unsigned short>(8080);net::io_context ioc{ 1 };tcp::acceptor acceptor( ioc,{address,port} );tcp::socket socket(ioc);http_server(acceptor, socket);ioc.run();}catch (std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;return EXIT_FAILURE;}return 0;
}
6. 测试
首先,从PostMan官网下载postman:
Download Postman | Get Started for Freewww.postman.com/downloads/
1)测试get
然后启动服务器并打开浏览器的新页面,输入‘127.0.0.1:8080/count’,测试get。
服务器成功返回数据,并展示位HTML页面,当刷新页面后,count也会相应的增加
测试time,输入‘127.0.0.1:8080/time’,页面会显示当前的时间戳
2)测试post
打开postman,按照图片进行如下操作
首先,创建一个新的请求,并设置位post,然后输入‘127.0.0.1:8080/email’,在raw中输入数据并点击发送
{"email":"123456@edu.com"
}
服务器返回相应数据
同时,服务器这里也打印处POST的相应数据
同理,也可以用postman做get测试,我这里就不再演示了
相关文章:

网络编程(21)——通过beast库快速实现http服务器
目录 二十一、day21 1. 头文件和作用域重命名 2. reponse时调用的一些函数 3. http_connection a. 构造函数 b. start() c. process_request() d. create_response() e. create_post_response() f. write_response() 4. Server 5. 主函数 6. 测试 1)测…...

Logback
Logback 简介 SpringBoot 内置日志框架 用来自定义控制台日志输出样式、生成日志文件 使用 由于是内置所以不需要引入,稍加配置就可以直接使用。 内置源头查看 配置application.yml # 日志配置 logging:level:com.ruoyi: logging.levelorg.springframework: war…...

Sub - Adjacent Transformer — 对AT的有趣改进
出处:IJCAI 2024 未开源,链接貌似是:jackyue1994/Sub-Adjacent-Transformer (github.com) 贡献:1. 提出:基于 “次邻域” 及 “注意力贡献” 的注意力学习机制,以增强异常、正常的区分;2. 首次…...

『Mysql集群』Mysql高可用集群之主从复制 (一)
Mysql主从复制模式 主从复制有一主一从、主主复制、一主多从、多主一从等多种模式. 我们可以根据它们的优缺点选择适合自身企业情况的主从复制模式进行搭建 . 一主一从 主主复制 (互为主从模式): 实现Mysql多活部署 一主多从: 提高整个集群的读能力 多主一从: 提高整个集群的…...

PHP获取图片属性(size, width, 和 height)的函数
在PHP中,要获取图片的尺寸(宽度和高度),你可以使用 getimagesize() 函数。这个函数不仅返回图片的宽度和高度,还返回图片的类型和MIME类型等信息。 以下是 getimagesize() 函数的基本用法: <?php /…...

MySQL启动失败解决方案
目录 引言 一、查看/启动mysql服务的两种方式 方法一: 方法二: 二、修改mysql服务启动路径的地址 三、"my.ini"文件的使用 设置my.ini文件的路径 给出一个使用my.ini文件的小例子 引言 造成启动闪退\失败的原因我仅仅以个人查询的一下博…...

Spring Boot中使用MyBatis-Plus和MyBatis拦截器来实现对带有特定注解的字段进行AES加密。
1. 添加依赖 首先,在pom.xml文件中添加必要的依赖项: xml 深色版本 <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifac…...

Python GUI 编程:tkinter 初学者入门指南——框架、标签框架
在本文中,将介绍 tkinter Frame 框架小部件、 LabelFrame 标签框架小部件的使用方法。 Frame 框架 Frame 框架在窗体上建立一个矩形区域,作为一个容器,用于组织分组排列其他小部件。 要创建框架,请使用以下构造函数。 frame …...

Mac 远程 Windows 等桌面操作系统工具 Microsoft Remote Desktop for Mac 下载安装详细使用教程
最近需要在 Mac 上远程连接控制我的 windows 电脑系统,经过一番尝试对于 win 来说还是微软自家推出的 Microsoft Remote Desktop for Mac 最最好用,没有之一 简介 Microsoft Remote Desktop是一款由微软公司开发的远程桌面连接工具,可以让用…...

初级网络工程师之从入门到入狱(四)
本文是我在学习过程中记录学习的点点滴滴,目的是为了学完之后巩固一下顺便也和大家分享一下,日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、Wlan应用实战1.1、拓扑图详解1.2、LSW11.3、AC11.4、抓包1.5、Tunnel隧道模式解析1.6、AP、…...

MinIO配置与使用
在数字化时代,数据存储与管理变得尤为重要,尤其是对于非结构化数据如日志文件的处理。MinIO,作为一个高性能、可扩展的分布式对象存储系统,以其对Amazon S3的全面兼容性和轻量级设计,成为了众多企业和开发者存储大量数…...

【漏洞复现】SpringBlade menu/list SQL注入漏洞
》》》产品描述《《《 致远互联智能协同是一个信息窗口与工作界面,进行所有信息的分类组合和聚合推送呈现。通过面向角色化、业务化、多终端的多维信息空间设计,为不同组织提供协同门户,打破组织内信息壁垒,构建统一协同沟通的平台。 》》》漏洞描述《《《 致远互联 FE协作办公…...

物联网智能项目(含案例说明)
物联网(Internet of Things,简称IoT)智能项目是指利用物联网技术将各种物理设备、传感器、软件、网络等连接起来,实现设备之间的互联互通,并通过数据采集、传输、处理和分析,实现智能化管理和控制的项目。以…...

【YOLOv8改进】 YOLOv8 更换骨干网络之GhostNetV3步骤详解
这里yolov8源码版本是 ultralytics-8.2.54 GhostNetV3 源码下载 https://codeload.github.com/huawei-noah/Efficient-AI-Backbones 将ghostnetv3.py文件复制一份到源码./ultralytics-8.2.54/ultralytics/nn/modules路径下 我根据mobilenetv4的教程,修改了ghostne…...

成绩查询小程序,家长查分超方便~
这都马上2025年了,我不相信还有老师不知道怎么发成绩,如果你不知道,那么这篇文章不要错过,推荐给大家我用了7年的发成绩工具 易查分,新版本更新之后,发成绩只需要一分钟的时间即可生成一个成绩查询系统。 …...

鸿蒙开发(NEXT/API 12)【上传下载文件】远场通信场景
场景介绍 本协议栈框架支持将文件上传到服务器或者从服务器下载文件。 开发步骤 导包。 import { rcp } from kit.RemoteCommunicationKit; import {fileIo} from kit.CoreFileKit;下载文件。 let SESSION_CONFIG: rcp.SessionConfiguration {// 此处请根据业务设置合适的…...

快速理解AUTOSAR CP的软件架构层次以及各层的作用
在 AUTOSAR CP 的架构中,软件分为 应用层 (App)、运行时环境 (RTE) 和 基础软件层 (BSW) 三个主要层级。下面是每一层的主要功能与简单的代码示例来展示它们之间的关系。 1. 概述 应用层 (App):包含应用程序代码,主要实现业务逻辑。应用层通…...

【Unity】Unity中接入Admob聚合广告平台,可通过中介接入 AppLovin,Unity Ads,Meta等渠道的广告
一、下载Google Admob的SDK插件 到Google Admob官网中,切换到Unity平台 进来之后是这样,注意后面有Unity标识,然后点击下载,跳转到github中,下载最新的Admob插件sdk,导入到Unity中 二、阅读官方文档&…...

PythonExcel批量pingIP地址
问题: 作为一个电气工程师(PLC),当设备掉线的时候,需要用ping工具来检查网线物理层是否可靠连接,当项目体量过大时,就不能一个手动输入命令了。 解决方案一: 使用CMD命令 for /L %…...

软媒市场新蓝海:软文媒体自助发布与自助发稿的崛起
在信息时代的浪潮中,软媒市场以其独特的魅力和无限的潜力,成为了企业营销的新宠。随着互联网的飞速发展,软文媒体自助发布平台应运而生,为企业提供了更加高效、便捷的营销方式。而自助发稿功能的加入,更是让软媒市场的蓝海变得更加广阔。 软媒市场的独特价值 软媒市场之所以能…...

【笔记】Day2.5.1查询运费模板列表(未完
(一)代码编写 1.阅读需求,确保理解其中的每一个要素: 获取全部运费模板:这意味着我需要从数据库中查询所有运费模板数据。按创建时间倒序排序:这意味着查询结果需要根据模板的创建时间进行排序࿰…...
阿基米德螺旋线等距取点
曲线公式 极坐标形式: 笛卡尔坐标形式: 弧长公式 对极坐标形式积分可得弧长为: 将上式转换为一元二次方程: 解此一元二次方程可得: 等距取点 弧长L等距递增,代入公式,再利用笛卡尔坐标公式即…...

2024年全球增强现实(AR)市场分析报告
一、增强现实统计数据(2024) 市场价值:2024年,全球AR市场价值超过320亿美元,并预计到2027年将突破500亿美元。用户基础:目前约有14亿活跃的AR用户设备,这一数字预计将在2024年增长至17.3亿。消费者认知:大约四分之三的44岁以下成年人对AR有所了解。购物体验:基于AR的购物…...

探索 NetworkX:Python中的网络分析利器
文章目录 **探索 NetworkX:Python中的网络分析利器**一、背景介绍二、NetworkX是什么?三、如何安装NetworkX?四、NetworkX的五个简单函数五、NetworkX的三个应用场景六、常见问题及解决方案七、总结 探索 NetworkX:Python中的网络…...

Python知识点:基于Python技术,如何使用AirSim进行无人机模拟
开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! 如何使用Python和AirSim进行无人机模拟 无人机技术的发展为许多行业带来了革命性…...

《中国林业产业》是什么级别的期刊?是正规期刊吗?能评职称吗?
问题解答 问:《中国林业产业》是不是核心期刊? 答:不是,是知网收录的正规学术期刊。 问:《中国林业产业》级别? 答:国家级。主管单位:国家林业和草原局 …...

私域流量下的白酒新传奇:半年破五千万的营销策略揭秘
在当今的数字化浪潮中,某白酒品牌独树一帜,摒弃了实体店和传统电商的常规路径,仅凭其精心构建的私域流量生态,在短短六个月内创造了超过五千万元的销售额奇迹。这一非凡成就背后,蕴含着一套独特的营销策略。 重塑营销&…...

Tomcat 配置:方便运行 Java Web 项目
目录 一、作用 二、安装 三、配置环境 四、启动 五、访问 一、作用 是一个轻量级的web服务器,可使用Tomcat运行Java Web项目。 二、安装 1. 基于JDK(安装Tomcat之前,先安装JDK,并配置环境变量JAVA_HOME) 2. apache-tom…...

Spring Boot知识管理:机器学习与AI集成
5系统详细实现 5.1 管理员模块的实现 5.1.1 用户管理 知识管理系统的管理员可以对用户新增,修改,删除,查询操作。具体界面的展示如图5.1所示。 图5.1 用户管理管理界面 5.1.2 文章分类 管理员登录可以在文章分类新增,修改&#…...

Superset SQL模板使用
使用背景 有时想让表的时间索引生效,而不是在最外层配置报表时,再套多一层时间范围。这时可以使用SQL模板 参考官方文档 https://superset.apache.org/docs/configuration/sql-templating/#:~:textSQL%20Lab%20and%20Explore%20supports%20Jinja 我…...