使用ASIO的协程实现高并发服务器

在 C++ 网络编程领域,Asio 库提供了两种主要的异步编程范式:传统的回调模式和基于协程的现代模式,传统的回调模式大家都很清楚,这里不多做介绍,本文主要介绍基于协程的模式,Asio 协程基于 C++20 标准协程,提供了更简洁的异步编程模型。

ASIO协程和回调对比

下面先列举一下ASIO使用回调和使用协程的区别,等看完区别后再讲讲ASIO使用协程的核心关键函数有哪些

让我用一个更生动的定时器示例来展示协程如何优雅地解决回调地狱问题。我们将实现一个简单但常见需求:

每隔3秒打印一次消息,共打印5次。

要实现上面这个功能,用传统的回调实现是很麻烦的,下面是用回调实现的代码(为了更清晰,这里不用lambda表达式):

#include <boost/asio.hpp>
#include <iostream>namespace asio = boost::asio;// 前向声明
void timer_callback1(asio::steady_timer* timer, int count);
void timer_callback2(asio::steady_timer* timer, int count);
void timer_callback3(asio::steady_timer* timer, int count);
void timer_callback4(asio::steady_timer* timer, int count);
void timer_callback5(asio::steady_timer* timer, int count);
void final_callback(asio::steady_timer* timer, int count);// 处理第一次定时器到期
void timer_callback1(asio::steady_timer* timer, int count) {std::cout << "回调模式: 第1次打印 - 3秒已过\n";timer->expires_after(std::chrono::seconds(3));timer->async_wait(std::bind(timer_callback2, timer, count + 1));
}// 处理第二次定时器到期
void timer_callback2(asio::steady_timer* timer, int count) {std::cout << "回调模式: 第2次打印 - 3秒已过\n";timer->expires_after(std::chrono::seconds(3));timer->async_wait(std::bind(timer_callback3, timer, count + 1));
}// 处理第三次定时器到期
void timer_callback3(asio::steady_timer* timer, int count) {std::cout << "回调模式: 第3次打印 - 3秒已过\n";timer->expires_after(std::chrono::seconds(3));timer->async_wait(std::bind(timer_callback4, timer, count + 1));
}// 处理第四次定时器到期
void timer_callback4(asio::steady_timer* timer, int count) {std::cout << "回调模式: 第4次打印 - 3秒已过\n";timer->expires_after(std::chrono::seconds(3));timer->async_wait(std::bind(timer_callback5, timer, count + 1));
}// 处理第五次定时器到期
void timer_callback5(asio::steady_timer* timer, int count) {std::cout << "回调模式: 第5次打印 - 3秒已过\n";timer->expires_after(std::chrono::seconds(3));timer->async_wait(std::bind(final_callback, timer, count + 1));
}// 最终回调
void final_callback(asio::steady_timer* timer, int count) {std::cout << "回调模式: 完成" << count << "次打印\n";delete timer; // 清理资源
}// 启动定时器序列
void start_timer_sequence(asio::io_context& io) {// 在堆上创建定时器(需要在整个序列中保持存活)asio::steady_timer* timer = new asio::steady_timer(io);// 设置第一次等待timer->expires_after(std::chrono::seconds(3));timer->async_wait(std::bind(timer_callback1, timer, 0));
}int main() {asio::io_context io;// 启动回调地狱start_timer_sequence(io);io.run();return 0;
}

正如上面的例子,每个异步操作需要独立的处理函数,5次操作需要6个函数(5个回调+1个启动函数),逻辑上深层嵌套:start → callback1 → callback2 → ... → final,回调函数形成链式调用,如果业务很复杂,你自己都不知道哪一个回调调用了哪一个,这是非常不友好的。c++11后有了lambda表达式这个情况有所好转,但lambda嵌套lambda看的还是很费劲

上面代码用协程来实现就比较简单了:

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>namespace asio = boost::asio;// 协程版 - 单一函数顺序执行
asio::awaitable<void> sequential_operations() {asio::steady_timer timer(co_await asio::this_coro::executor);// 操作1std::cout << "协程: 启动操作1\n";timer.expires_after(std::chrono::seconds(3));co_await timer.async_wait(asio::use_awaitable);std::cout << "协程: 操作1完成 - 3秒已过\n";// 操作2std::cout << "协程: 启动操作2\n";timer.expires_after(std::chrono::seconds(3));co_await timer.async_wait(asio::use_awaitable);std::cout << "协程: 操作2完成 - 3秒已过\n";// 操作3std::cout << "协程: 启动操作3\n";timer.expires_after(std::chrono::seconds(3));co_await timer.async_wait(asio::use_awaitable);std::cout << "协程: 操作3完成 - 3秒已过\n";// 操作4std::cout << "协程: 启动操作4\n";timer.expires_after(std::chrono::seconds(3));co_await timer.async_wait(asio::use_awaitable);std::cout << "协程: 操作4完成 - 3秒已过\n";// 操作5std::cout << "协程: 启动操作5\n";timer.expires_after(std::chrono::seconds(3));co_await timer.async_wait(asio::use_awaitable);std::cout << "协程: 操作5完成 - 3秒已过\n";std::cout << "协程: 所有操作完成\n";co_return;
}int main() {asio::io_context io;asio::co_spawn(io, sequential_operations(), asio::detached);io.run();return 0;
}

使用协程可以让整个序列在一个函数中实现,执行流程一目了然,从上到下顺序执行,无嵌套,无跳转,调整操作顺序只需调整代码顺序,另外,错误处理上,一个 try/catch 块覆盖所有操作

协程与传统回调对比

特性协程回调
代码结构顺序执行,类似同步代码嵌套回调,容易形成"回调地狱"
可读性高,逻辑清晰低,跳转复杂
错误处理使用 try/catch错误码参数
局部变量保持状态需要手动保持状态
控制流标准控制结构手动状态管理
资源管理RAII 自然适用需要额外注意生命周期

Asio 协程通过 C++20 标准协程提供了更简洁、更易读的异步编程模型,同时保持了高性能特性。掌握 co_awaitco_spawn 等关键机制,可以显著提高网络应用的开发效率和可维护性。

ASIO协程的核心概念和关键字

ASIO协程的关键概念和关键字如下:

1. co_await

  • 作用:挂起当前协程,等待异步操作完成

  • 用法:以asio的读取数据的例子举例,异步读取等到读取到数据后继续执行数据处理,传统的回调是这样实现的:

// 读取完成处理函数
void handle_read(boost::system::error_code ec, size_t n, std::shared_ptr<std::array<char, 1024>> buffer) {if (!ec) {std::cout << "读取到 " << n << " 字节数据\n";} else {std::cerr << "读取错误: " << ec.message() << "\n";}
}// 启动读取操作
void start_read(tcp::socket& socket) {// 使用 shared_ptr 管理缓冲区auto buffer = std::make_shared<std::array<char, 1024>>();socket.async_read_some(asio::buffer(*buffer),std::bind(handle_read, std::placeholders::_1, std::placeholders::_2, buffer));
}

使用协程是这样实现的

asio::awaitable<void> read_data(tcp::socket& socket) {char data[1024];// 使用 co_await 等待异步读取size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);std::cout << "读取到 " << n << " 字节数据\n";
}

协程直接"等待"操作完成,代码线性执行,从这个例子也看到协程的另外一个好处,就是可以使用局部变量来管理缓冲区,因为从语法上看,协程它是在一个函数里执行,缓冲区生命周期不会释放,而回调你只能用成员变量或者堆分配空间了,当然,你用lambda表达式也可以实现类型效果

2. co_return和asio::awaitable

  • 作用co_return类似return结束协程执行并返回值,这个返回值实际是asio::awaitable
//这里T是result对应类型
asio::awaitable<T> calculate_value() {co_return result; // 对于有返回值的协程
}asio::awaitable<void>  do_something() {co_return;        // 对于 void 协程
}

下面举例一个具体的例子

//calculate_value函数将延时1秒后返回42
asio::awaitable<int> calculate_value() {asio::steady_timer timer(co_await asio::this_coro::executor);timer.expires_after(std::chrono::seconds(1));co_await timer.async_wait(asio::use_awaitable);// 使用 co_return 返回值co_return 42;
}

协程直接返回结果,类似同步函数,co_return搭配asio::awaitable使用

3. asio::use_awaitable

  • 作用:将异步操作转换为可等待对象,这个作用是告诉asio的函数这是一个协程函数,是为了和异步函数能进行重载区分用的,这个标志能让asio的协程函数和异步回调函数都一样,仅仅是传入的参数不同
//协程版本
asio::awaitable<void> wait_for_timer() {
asio::steady_timer timer(co_await asio::this_coro::executor);// 使用 use_awaitable 使异步操作可等待
timer.expires_after(std::chrono::seconds(1));
co_await timer.async_wait(asio::use_awaitable);std::cout << "定时器完成\n";
}//回调版本
void wait_for_timer(asio::io_context& io) {auto timer = std::make_shared<asio::steady_timer>(io, std::chrono::seconds(1));timer->async_wait([](const asio::error_code& ec) {if (!ec) {std::cout << "定时器完成\n";} else {std::cerr << "定时器错误: " << ec.message() << "\n";}});
}

4. 启动协程 (co_spawn)

  • 作用co_spawn的作用是启动一个协程
asio::co_spawn(executor,           // 执行上下文 (io_context/strand)coroutine_function, // 协程函数completion_handler   // 完成回调
);

completion_handler是完成处理程序选项有两种:

  • asio::detached: 不关心协程结束(最常用)
asio::co_spawn(executor, session(std::move(socket)), asio::detached);
  • 自定义处理:
asio::co_spawn(executor, session(), [](std::exception_ptr e, int result) {if (e) std::rethrow_exception(e);std::cout << "Result: " << result << "\n";});

这里用ASIO回调方式写一个最简单的tcp服务器是这样的:

// 会话状态管理类
class Session : public std::enable_shared_from_this<Session> {
public:Session(tcp::socket socket) : socket_(std::move(socket)) {}void start() {start_read();}void start_read() {auto self = shared_from_this();auto buffer = std::make_shared<std::array<char, 1024>>();socket_.async_read_some(asio::buffer(*buffer),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2, buffer));}void handle_read(boost::system::error_code ec, size_t n, std::shared_ptr<std::array<char, 1024>> buffer) {if (!ec) {// 处理数据...start_read(); // 继续读取}}private:tcp::socket socket_;
};int main() {asio::io_context io;tcp::socket socket(io);// 创建会话并启动auto session = std::make_shared<Session>(std::move(socket));session->start();io.run();
}

用协程可以变为这样:

int main() {asio::io_context io;tcp::socket socket(io);// 使用 co_spawn 启动协程asio::co_spawn(io, session(std::move(socket)), asio::detached);io.run();
}// 协程函数
asio::awaitable<void> session(tcp::socket socket) {// ...协程逻辑...co_return;
}

asio::co_spawn启用一个协程进入session函数,然后等待其完成。

上面例子中asio::detached说明启动这个协程后就不管他的返回了,如果你要获取返回,可以传入一个回调(协程最终还是不能完全避免回调),例如:

// 完成处理函数
void handle_completion(std::exception_ptr e, int result) {if (e) {try {std::rethrow_exception(e);} catch (const std::exception& ex) {std::cerr << "计算错误: " << ex.what() << "\n";}} else {std::cout << "计算结果: " << result << "\n";}
}// 可能失败的协程
asio::awaitable<int> compute_value() {// ...可能抛出异常的计算...co_return 42;
}int main() {asio::io_context io;// 启动协程并指定完成处理函数asio::co_spawn(io, compute_value(), handle_completion);io.run();
}

ASIO协程的其它操作

1. 切换执行上下文

asio::dispatch:协程执行上下文切换,它允许开发者精确控制协程在哪个线程或执行器上运行

// 切换到指定线程的 io_context
co_await asio::dispatch(target_io_context, asio::use_awaitable);

当协程执行到co_await asio::dispatch(...)时:

  • 挂起当前协程:暂停当前执行流程
  • 调度到目标执行器:将协程续体(continuation)提交到目标执行器的队列
  • 在目标上下文恢复:当目标执行器调度该任务时,协程在目标线程恢复执行

例如:网络请求后,有些复杂计算切换到别的线程进行操作:

asio::awaitable<void> process_request() {// 在网络线程池处理请求Request req = co_await receive_request();// 切换到计算线程池处理CPU密集型任务co_await asio::dispatch(compute_pool, asio::use_awaitable);Result res = co_await heavy_computation(req);// 切回网络线程池发送响应co_await asio::dispatch(network_pool, asio::use_awaitable);co_await send_response(res);
}

还有就是线程和GUI的切换,GUI线程一般不会和网络请求在一个线程中

asio::awaitable<void> update_ui() {Data data = co_await fetch_data();// 切换到GUI线程更新界面co_await asio::dispatch(gui_executor, asio::use_awaitable);ui_label.set_text(data.message);ui_chart.update(data.values);
}

2. 协程取消

asio::cancellation_signal cancel_signal;// 启动可取消协程
asio::co_spawn(executor, [](asio::cancellation_signal sig) -> asio::awaitable<void> {asio::steady_timer timer(co_await asio::this_coro::executor);timer.expires_after(10s);// 绑定取消信号co_await timer.async_wait(asio::bind_cancellation_slot(sig.slot(), asio::use_awaitable));}(cancel_signal),asio::detached
);// 在需要时取消
cancel_signal.emit(asio::cancellation_type::all);

协程操作符

协程操作符主要是||&&,允许多个协程同时运行,并等待所有(&&)或者某个(||)协程完成。

1.||操作符

下面举个网络编程中最常见的场景之一"带超时的异步读取"来展示协程的操作符,网络编程经常有一个需求,就是你给对方写一个数据,要n秒内等待对方回复,如果对方n秒内不回复,你要重发,最多重发m次,这个逻辑梳理为:

  • 向对方发送请求数据
  • 等待回复,设置超时时间(如3秒)
  • 如果超时未收到回复,重发请求
  • 最多重发M次(如3次)

用协程这样实现的(注意,这里协程的||操作符要include asio/experimental/awaitable_operators.hpp):

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>
#include <system_error>
#include <chrono>namespace asio = boost::asio;
using asio::ip::tcp;
using namespace asio::experimental::awaitable_operators;
namespace chrono = std::chrono;// 带超时重发的可靠请求
asio::awaitable<bool> reliable_request(tcp::socket& socket, const std::string& request,int max_retries = 3,chrono::seconds timeout = 3s) 
{int attempt = 0;while (attempt <= max_retries) {attempt++;std::cout << "尝试 #" << attempt << "/" << max_retries << "\n";try {// 发送请求co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable);// 准备接收响应std::string response;response.resize(1024);// 设置定时器asio::steady_timer timer(co_await asio::this_coro::executor);timer.expires_after(timeout);// 同时等待读取和超时,这里演示了||操作符,这里面有两个协程函数auto result = co_await (socket.async_read_some(asio::buffer(response), asio::use_awaitable) ||timer.async_wait(asio::use_awaitable));//只要上面有一个返回结果就会往下执行// 处理结果if (result.index() == 0) {  // 收到响应size_t bytes_read = std::get<0>(result).first;response.resize(bytes_read);std::cout << "收到响应: " << response << "\n";co_return true;}else {  // 超时std::cout << "请求超时, ";if (attempt < max_retries) {std::cout << "准备重试...\n";} else {std::cout << "已达最大重试次数\n";}}}catch (const std::exception& e) {std::cerr << "错误: " << e.what() << "\n";co_return false;}}co_return false;  // 所有尝试失败
}// 示例使用
asio::awaitable<void> client_session(tcp::socket socket) {try {// 构造请求数据std::string request = "QUERY_DATA";// 发送可靠请求(最多重试3次,超时3秒)bool success = co_await reliable_request(socket, request, 3, 3s);if (success) {std::cout << "请求成功完成\n";} else {std::cout << "请求失败\n";}}catch (const std::exception& e) {std::cerr << "会话错误: " << e.what() << "\n";}
}int main() {asio::io_context io;// 创建并连接socket(示例)tcp::socket socket(io);// 实际中应连接服务器: socket.connect(endpoint);// 启动客户端会话asio::co_spawn(io, client_session(std::move(socket)), asio::detached);io.run();return 0;
}

上面这个协程实现还是比较清晰的,如果用回调实现,则非常麻烦:

#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <functional>namespace asio = boost::asio;
using asio::ip::tcp;
namespace chrono = std::chrono;// 前向声明
class ReliableRequest;
void handle_write(boost::system::error_code ec, size_t, std::shared_ptr<ReliableRequest> request);
void handle_read(boost::system::error_code ec, size_t bytes_read,std::shared_ptr<ReliableRequest> request);
void handle_timeout(boost::system::error_code ec,std::shared_ptr<ReliableRequest> request);
void start_request(std::shared_ptr<ReliableRequest> request);// 请求状态管理类
class ReliableRequest : public std::enable_shared_from_this<ReliableRequest> {
public:ReliableRequest(tcp::socket& socket, std::string request, int max_retries, chrono::seconds timeout): socket_(std::move(socket)), request_(std::move(request)), max_retries_(max_retries), timeout_(timeout), timer_(socket_.get_executor()), attempt_(0){}void start() {attempt_ = 0;start_attempt();}private:friend void handle_write(boost::system::error_code, size_t, std::shared_ptr<ReliableRequest>);friend void handle_read(boost::system::error_code, size_t,std::shared_ptr<ReliableRequest>);friend void handle_timeout(boost::system::error_code,std::shared_ptr<ReliableRequest>);void start_attempt() {attempt_++;std::cout << "尝试 #" << attempt_ << "/" << max_retries_ << "\n";auto self = shared_from_this();// 启动异步写入asio::async_write(socket_, asio::buffer(request_),std::bind(handle_write, std::placeholders::_1, std::placeholders::_2, self));}void start_wait() {auto self = shared_from_this();response_buffer_ = std::make_shared<std::array<char, 1024>>();// 启动异步读取socket_.async_read_some(asio::buffer(*response_buffer_),std::bind(handle_read, std::placeholders::_1, std::placeholders::_2, self));// 启动超时定时器timer_.expires_after(timeout_);timer_.async_wait(std::bind(handle_timeout, std::placeholders::_1, self));}void handle_success(size_t bytes_read) {std::string response(response_buffer_->data(), bytes_read);std::cout << "收到响应: " << response << "\n";if (completion_) completion_(true);}void handle_failure() {std::cout << "请求失败\n";if (completion_) completion_(false);}void handle_retry() {if (attempt_ < max_retries_) {std::cout << "准备重试...\n";start_attempt();} else {std::cout << "已达最大重试次数\n";handle_failure();}}public:// 完成回调using CompletionHandler = std::function<void(bool)>;CompletionHandler completion_;tcp::socket socket_;std::string request_;int max_retries_;chrono::seconds timeout_;asio::steady_timer timer_;int attempt_;std::shared_ptr<std::array<char, 1024>> response_buffer_;
};// 写入完成处理
void handle_write(boost::system::error_code ec, size_t, std::shared_ptr<ReliableRequest> request) {if (ec) {std::cerr << "写入错误: " << ec.message() << "\n";request->handle_failure();return;}request->start_wait();
}// 读取完成处理
void handle_read(boost::system::error_code ec, size_t bytes_read,std::shared_ptr<ReliableRequest> request) {// 取消定时器request->timer_.cancel();if (ec == asio::error::operation_aborted) {return; // 超时已处理}if (ec) {std::cerr << "读取错误: " << ec.message() << "\n";request->handle_retry();return;}request->handle_success(bytes_read);
}// 超时处理
void handle_timeout(boost::system::error_code ec,std::shared_ptr<ReliableRequest> request) {if (ec == asio::error::operation_aborted) {return; // 读取已完成}if (ec) {std::cerr << "定时器错误: " << ec.message() << "\n";request->handle_retry();return;}// 取消读取操作request->socket_.cancel();std::cout << "请求超时, ";request->handle_retry();
}// 启动可靠请求
void start_reliable_request(tcp::socket socket, std::string request,ReliableRequest::CompletionHandler completion,int max_retries = 3, chrono::seconds timeout = 3s) {auto request_obj = std::make_shared<ReliableRequest>(std::move(socket), std::move(request), max_retries, timeout);request_obj->completion_ = completion;request_obj->start();
}// 示例使用
void handle_request_complete(bool success) {if (success) {std::cout << "请求成功完成\n";} else {std::cout << "请求失败\n";}
}int main() {asio::io_context io;// 创建socket(示例)tcp::socket socket(io);// 实际中应连接服务器// 启动可靠请求start_reliable_request(std::move(socket), "QUERY_DATA", handle_request_complete,3, chrono::seconds(3));io.run();return 0;
}

2. &&操作符

&&操作符允许你同时启动多个异步操作,等待所有操作完成,以元组形式获取所有结果

auto [result1, result2] = co_await (async_op1(use_awaitable) &&async_op2(use_awaitable)
);

例如要多个接口结果执行完成后才返回,那么久可以用&&操作符

asio::awaitable<SearchResults> search_products(string_view query) {// 并行搜索多个分类auto [electronics, clothing, books] = co_await (product_service.search("electronics", query) &&product_service.search("clothing", query) &&product_service.search("books", query));...//合并到resultsco_return results;
}

总结

Asio协程方便了C++高并发服务器的开发,在保持异步高性能的同时,提供同步代码的简洁性,同步执行流简化调试和性能分析,随着C++20标准的广泛采用,协程已成为构建下一代高性能服务器的首选范式。

但要注意的是,协程并不会提高程序运行的效率,从原理上讲,协程有可能还没有回调高效,但这个性能并不会有太大的差距

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

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

相关文章

OpenCV——轮廓检测

轮廓检测 一、轮廓检测二、轮廓的层级三、轮廓的特征3.1、轮廓面积3.2、轮廓周长3.3、边界矩形3.4、最小外接圆3.5、近似轮廓3.6、凸包 一、轮廓检测 轮廓可以简单的描述为具有相同颜色或灰度的连续点连在一起的一条曲线&#xff0c;轮廓通畅会显示出图像中物体的形状。关于轮…

高等概率论题解-心得笔记【15】

文章目录 拓扑参考文献 拓扑 参考文献 《测度论基础与高等概率论》

Windows 10关闭自动更新功能

Windows 10关闭自动更新功能&#xff0c;大家是不是经常用下面的几个步骤&#xff1a; 1、禁用Windows Update服务&#xff1b; 2、在组策略里关闭Win10自动更新相关服务&#xff1b; 3、禁用任务计划里边的Win10自动更新&#xff1b; 4、在注册表中关闭Win10自动更新&…

[Meetily后端框架] 配置指南 | 后端API网关 | API文档体系

链接: https://github.com/Zackriya-Solutions/meeting-minutes docs&#xff1a;会议纪要管理系统 本项目是一个专门用于**处理会议记录**的后端系统。 系统接收会议文本内容&#xff0c;利用先进的AI模型自动识别关键信息&#xff0c;包括行动项、决策内容以及截止期限。 处…

Flink2.0 配置 historyserver

Flink2.0 配置 historyserver 主要是去修改config.yaml配置文件 主要修改的点有两个 网上很多文档都是写的只配置一个 都是坑啊 historyserver :历史服务器 运行 Flink job 的集群一旦停止(例如yarn模式&#xff0c;程序一旦停止&#xff0c;集群也就关闭了)&#xff0c;只能去…

LLM的训练过程

一般而言&#xff0c;训练一个完整的 LLM 需要经过图1中的三个阶段——Pretrain、SFT 和 RLHF。 1.预训练 Pretrain&#xff0c;即预训练&#xff0c;是训练 LLM 最核心也是工程量最大的第一步。LLM 的预训练和传统预训练模型非常类似&#xff0c;同样是使用海量无监督文本对随…

用 AI + Canvas 生成图形、动画与图表

摘要 随着人工智能&#xff08;AI&#xff09;技术与 Web 可视化的结合&#xff0c;前端开发者可以通过自然语言生成复杂的图表、动画和交互式画布&#xff0c;极大地提升了开发效率和用户体验。本文作为《AI 前端&#xff1a;构建智能化 Web 应用的未来》专栏的第七篇&#…

SQL Server for Linux 如何实现高可用架构

关键词&#xff1a;SQL Server for Linux、高可用、读写分离、动态扩容、Always On、可用性组 &#x1f4cb; 文章目录 前言&#xff1a;Linux上的SQL Server不再是梦高可用架构设计 Always On 可用性组故障转移集群实例 读写分离架构 可用性组读写分离应用层读写分离 动态扩…

【51单片机流水灯控制4种造型,按下1,2,3,4时,数码管对应显示键号,同时流水灯对应四种造型】2022-6-1

缘由流水灯控制4种造型&#xff0c;按下1,2,3,4时&#xff0c;数码管对应显示键号&#xff0c;同时流水灯对应四种造型-编程语言-CSDN问答 #include "REG52.h" unsigned char code smgduan[]{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5…

设计模式 - 工厂方法

工厂方法是一种设计模式&#xff0c;对工厂制造方法进行接口规范化&#xff0c;允许子类工厂决定具体知道哪类产品的实例&#xff0c;最终降低系统耦合&#xff0c;使系统的可维护性、可扩展性等得到提升。 一、工厂的多元化与专业化 要实例化对象&#xff0c;就得用到关键词“…

数据应该如何组织,才能让Excel“读懂”?

前言&#xff1a;如果你希望Excel能“读懂”你的数据&#xff0c;就得学会让排序、筛选、数据透视表、函数等这些功能为我们服务。 假设你在和一个非常聪明但有点“死板”的机器人&#xff08;Excel&#xff09;对话&#xff0c;你必须用它能理解的语言来组织信息。 “一维表”…

js防止重复提交的3种解决方案

防止 javascript 重复点击和提交的关键方法有三种&#xff1a;1. 禁用按钮法&#xff0c;点击后立即禁用按钮并更改文本提示&#xff0c;请求完成后恢复&#xff1b;2. 节流函数&#xff08;throttle&#xff09;&#xff0c;限制函数在设定时间间隔内仅执行一次&#xff0c;适…

【信创-k8s】银河麒麟V10国防版+鲲鹏/飞腾(arm64架构)在线/离线部署k8s1.30+kubesphere

银河麒麟作为国家核高基专项的重要成果&#xff0c;国防版凭借其卓越的安全性和可靠性&#xff0c;已成为军工领域的首选操作系统。之前我们在适配麒麟V4国防版的过程中已发现诸多安全性要求&#xff0c;而麒麟V10国防版在安全防护等级上又达到了更高的级别。 本文将主要演示离…

解锁单周期MIPS硬布线:Logisim实战全攻略

目录 一、引言二、MIPS 架构与单周期设计原理2.1 MIPS 架构概述2.2 单周期设计原理剖析 三、Logisim 工具基础3.1 Logisim 简介3.2 基本操作与组件认识 四、单周期 MIPS 硬布线设计步骤4.1 了解 MIPS 指令集4.2 搭建数据通路4.3 设计硬布线控制器4.4 在 Logisim 中创建电路 五、…

7.4.2B+树

B树&#xff1a; (1)每个分支节点最多有m个子树(孩子节点)。 阶&#xff1a;即看当前的B树是几阶B树&#xff0c;就看每个分支节点最多有几个子树&#xff0c;还是看最下一层有几个分叉就是几阶&#xff1f;&#xff1f;&#xff1f; 叶子节点&#xff1a;最下边的一层叫叶子…

MFC获取本机所有IP、局域网所有IP、本机和局域网可连接IP

获取本机所有IP地址 // 获取本机所有IP地址 int CMachine::GetLocalIPs(std::vector<CString>& vIPValue) {//返回IP数量&#xff0c; -1表示获取失败vIPValue.clear();int IpNum 0;//1.初始化wsa WSADATA wsaData;int ret WSAStartup(MAKEWORD(2, 2), &wsaD…

【C语言】贪吃蛇小游戏

1. 所需知识 C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API... 2. Win32 API介绍 2.1 Win32 API windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外&#xff0c;它同时也是一个很大的服务中心&#xff0c;调用这个服务中心的各种…

PostgreSQL 容器化分布式技术方案

&#x1f4cb; 目录 引言&#xff1a;为什么选择容器化PostgreSQLPostgreSQL容器化基础分布式架构设计高可用实现方案读写分离架构动态扩缩容策略生产环境实践总结与展望 引言&#xff1a;为什么选择容器化PostgreSQL 在数字化转型的浪潮中&#xff0c;数据库作为企业的"…

NV025NV033美光固态闪存NV038NV040

美光固态闪存技术突破与市场布局深度解析 一、技术突破&#xff1a;232层NAND闪存与高密度存储的革新 美光NV系列固态闪存的核心竞争力源于其232层NAND闪存技术&#xff0c;这一技术通过垂直堆叠工艺&#xff0c;将存储单元层层叠加&#xff0c;宛如在指甲盖面积内构建超过20…

Matplotlib 绘图库从入门到精通:Python 数据可视化全解析

引言 在数据科学的世界里&#xff0c;"一图胜千言" 这句话有着深刻的含义。数据可视化不仅是数据分析师展示成果的重要手段&#xff0c;更是数据科学家探索数据、发现规律的强大工具。Matplotlib 作为 Python 生态系统中最著名的数据可视化库&#xff0c;为我们提供…