【C++boost::asio网络编程】使用asio协程搭建异步服务器的笔记
协程和线程关系以及线程和进程的关系简单区分
资源消耗与开销
- 进程与线程:
- 进程 是操作系统分配资源的基本单位,每个进程拥有自己的内存空间、文件描述符等资源。
- 线程 是进程中的执行单元,同一个进程中的多个线程共享进程的资源(如内存空间、文件描述符等)。线程的创建和上下文切换相对于进程来说,开销较小,但仍然需要操作系统的支持。
- 协程与线程:
- 协程 是一种轻量级的用户级执行单元,它与线程相比,具有更小的资源开销。多个协程通常运行在一个线程中,共享线程的资源(如 CPU 时间、内存等)。
- 线程 是操作系统调度的基本单元,协程是在用户级别进行调度的,它并不依赖操作系统进行上下文切换。因此,协程的切换和调度开销比线程小得多。
调度与上下文切换
- 进程与线程:
- 进程 和 线程 的调度通常由操作系统管理,线程的上下文切换涉及到保存和恢复 CPU 寄存器、内存映射等状态。
- 线程切换 的开销比进程切换小,但仍然需要操作系统的干预。
- 协程与线程:
- 协程 的调度通常是由程序自身管理的,协程的上下文切换不需要操作系统的参与,通常只是通过保存和恢复少量的状态(例如局部变量、程序计数器等)来完成。因此,协程的上下文切换比线程切换的开销更小。
并发性
- 进程与线程:
- 进程 之间是完全独立的,一个进程的崩溃不会直接影响到其他进程。
- 线程 之间是并发的,在同一进程中可以并行执行多个线程(在多核 CPU 上,多个线程可以真正地并行运行),但是它们会共享进程的资源。
- 协程与线程:
- 协程 是轻量级的并发单元,它们运行在单个线程中。多个协程可以在同一个线程中并发执行,但它们并不会并行执行(即不会在多个 CPU 核心上同时运行)。协程通过协作式调度来实现并发,通常在 I/O 操作或者其他需要等待的地方挂起,给其他协程执行的机会。
独立性
- 进程与线程:
- 进程 是独立的,通常不会直接共享内存等资源,进程间的通信需要通过特定的机制(如管道、消息队列、共享内存等)。
- 线程 共享进程的内存空间,可以直接访问共享数据,因此线程之间的通信较为方便,但也需要注意同步问题。
- 协程与线程:
- 协程 不独立,它们共享同一个线程的资源。多个协程之间的切换不会涉及到线程的上下文切换,只是在协程之间的切换。协程之间通常通过共享内存来通信,但也需要小心避免并发问题。
main函数
int main()
{
try
{
boost::asio::io_context ioc;
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto) {
ioc.stop();
});
boost::asio::co_spawn(ioc, listener, boost::asio::detached);
ioc.run();
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
boost::asio::co_spawn(ioc, listener, boost::asio::detached);
这段代码的主要功能是启动一个新的协程来运行listener函数(这个函数用来监听来自客户端的连接),
listener函数
boost::asio::awaitable<void> listener()
{
auto executor = co_await boost::asio::this_coro::executor;
boost::asio::ip::tcp::acceptor acceptor(executor, { boost::asio::ip::tcp::v4(),8888 });
while (true)
{
boost::asio::ip::tcp::socket socket = co_await acceptor.async_accept(boost::asio::use_awaitable);
boost::asio::co_spawn(executor, echo(std::move(socket)), boost::asio::detached);
}
}
boost::asio::awaitable<void> listener()
首先来介绍一下这个返回值为什么要这么写:boost::asio::awaitable<T>
是一种协程返回类型,表示该函数返回的是一个可以挂起的协程
什么叫做返回一个可以挂起的协程?
这个意思就是说该函数返回的对象能在某个点暂停执行,等待某个操作(例如异步IO,网络请求等)完成后再继续执行,这种特性是现代协程模型中的核心概念。在C++20中,协程通过co_await
,co_return
等关键字来实现,例如co_await
用于挂起当前协程并等待一个异步操作完成。
而挂起指的是协程在执行过程中暂停下来,等待某些条件或操作完成,这时,协程将不会占用CPU,其他任务可以继续执行,直到挂起的操作完成之后,该协程才会恢复执行
auto executor = co_await boost::asio::this_coro::executor;
这段代码就是用来获取当前协程所在的"执行上下文"。在Boost::asio中,executor是一个对象,主要负责调度和执行任务,它是io_context或io_service的一部分,确保异步任务能够执行。
co_await是一个协程操作符,它用户挂起当前协程,直到等待的对象完成.通过co_wait,协程不会立即返回,而是将控制权交还给调用者,直到获取到执行器对象为止。
boost::asio::ip::tcp::socket socket = co_await acceptor.async_accept(boost::asio::use_awaitable);
async_accept
中的参数boost::asio::use_awaitable
的主要作用是使得async_accept能够与C++20的协程机制兼容,在这段代码中,它使得async_accept
返回的是一个C++20 协程机制交互的“协程兼容对象“,能够在异步操作完成时继续执行
boost::asio::co_spawn(executor, echo(std::move(socket)), boost::asio::detached);
这行代码的意思是启动一个协程。第一个参数是一个执行器对象,用户控制协程的上下文。第二个参数是socket对象,表示创建的协程通过这个对象为客户端进行服务,第三个参数的作用是表示不需要关心启动的这个协程完成的情况。
echo函数
boost::asio::awaitable<void> echo(boost::asio::ip::tcp::socket socket)
{
try
{
char data[1024];
while (true)
{
size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
std::cout << "写回去" << std::endl;
co_await boost::asio::async_write(socket, boost::asio::buffer(data, n), boost::asio::use_awaitable);
}
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}
}
echo函数的作用就是接收客户端发过来的信息然后转发回去,实现一个简单的echo server服务器。