首页 > 其他分享 >Boost Asio协程实现服务器

Boost Asio协程实现服务器

时间:2023-08-14 19:26:01浏览次数:43  
标签:Asio asio 协程 boost std msg co Boost

参考:https://llfc.club/category?catid=225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2RHA2vfllSmYXf4xcJqzzVtLrJt

简介

之前介绍了asio服务器并发编程的几种模型,包括单线程,多线程IOServicePool,多线程IOThreadPool等,今天带着大家利用asio协程实现并发服务器。利用协程实现并发程序有两个好处
1   将回调函数改写为顺序调用,提高开发效率。
2   协程调度比线程调度更轻量化,因为协程是运行在用户空间的,线程切换需要在用户空间和内核空间切换。

协程案例

asio官网提供了一个协程并发编程的案例,我们列举一下

#include<boost/asio/co_spawn.hpp>
#include<boost/asio/detached.hpp>
#include<boost/asio/io_context.hpp>
#include<boost/asio/ip/tcp.hpp>
#include<boost/asio/signal_set.hpp>
#include<boost/asio/write.hpp>
#include<cstdio>
using boost::asio::ip::tcp;
using boost::asio::awaitable;
using boost::asio::co_spawn;
using boost::asio::detached;
using boost::asio::use_awaitable;
namespace this_coro = boost::asio::this_coro;
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
# define use_awaitable \
boost::asio::use_awaitable_t(__FILE__, __LINE__, __PRETTY_FUNCTION__)
#endif
awaitable<void> echo(tcp::socket socket)
{
try
{
char data[1024];
for(;;)
{
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), use_awaitable);
co_await async_write(socket, boost::asio::buffer(data, n), use_awaitable);
}
}
catch(std::exception& e)
{
std::printf("echo Exception: %s\n", e.what());
}
}
awaitable<void> listener()
{
auto executor = co_await this_coro::executor;
tcp::acceptor acceptor(executor,{ tcp::v4(),10086});
for(;;)
{
tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
co_spawn(executor, echo(std::move(socket)), detached);
}
}
int main()
{
try
{
boost::asio::io_context io_context(1);
boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);
signals.async_wait([&](auto,auto){ io_context.stop();});
co_spawn(io_context, listener(), detached);
io_context.run();
}
catch(std::exception& e)
{
std::printf("Exception: %s\n", e.what());
}
}

 

1   我们用awaitable<void>声明了一个函数,那么这个函数就变为可等待的函数了,比如listener被添加awaitable<void>之后,就可以被协程调用和等待了。
2   co_spawn表示启动一个协程,参数分别为调度器,执行的函数,以及启动方式, 比如我们启动了一个协程,deatched表示将协程对象分离出来,这种启动方式可以启动多个协程,他们都是独立的,如何调度取决于调度器,在用户的感知上更像是线程调度的模式,类似于并发运行,其实底层都是串行的。

  1. co_spawn(io_context, listener(), detached);

我们启动了一个协程,执行listener中的逻辑,listener内部co_await 等待 acceptor接收连接,如果没有连接到来则挂起协程。执行之后的io_context.run()逻辑。所以协程实际上是在一个线程中串行调度的,只是感知上像是并发而已。
3   当acceptor接收到连接后,继续调用co_spawn启动一个协程,用来执行echo逻辑。echo逻辑里也是通过co_wait的方式接收和发送数据的,如果对端不发数据,执行echo的协程就会挂起,另一个协程启动,继续接收新的连接。当没有连接到来,接收新连接的协程挂起,如果所有协程都挂起,则等待新的就绪事件(对端发数据,或者新连接)到来唤醒。

改进服务器

我们可以利用协程改进服务器编码流程,用一个iocontext管理绑定acceptor用来接收新的连接,再用一个iocontext或以IOServicePool的方式管理连接的收发操作,在每个连接的接收数据时改为启动一个协程,通过顺序的方式读取收到的数据

voidCSession::Start(){
auto shared_this = shared_from_this();
//开启接收协程
co_spawn(_io_context,[=]()->awaitable<void>{
try{
for(;!_b_close;){
_recv_head_node->Clear();
std::size_t n = co_await boost::asio::async_read(_socket,
boost::asio::buffer(_recv_head_node->_data, HEAD_TOTAL_LEN),
use_awaitable);
if(n ==0){
std::cout <<"receive peer closed"<< endl;
Close();
_server->ClearSession(_uuid);
co_return;
}
//获取头部MSGID数据
short msg_id =0;
memcpy(&msg_id, _recv_head_node->_data, HEAD_ID_LEN);
//网络字节序转化为本地字节序
msg_id = boost::asio::detail::socket_ops::network_to_host_short(msg_id);
std::cout <<"msg_id is "<< msg_id << endl;
//id非法
if(msg_id > MAX_LENGTH){
std::cout <<"invalid msg_id is "<< msg_id << endl;
_server->ClearSession(_uuid);
co_return;
}
short msg_len =0;
memcpy(&msg_len, _recv_head_node->_data + HEAD_ID_LEN, HEAD_DATA_LEN);
//网络字节序转化为本地字节序
msg_len = boost::asio::detail::socket_ops::network_to_host_short(msg_len);
std::cout <<"msg_len is "<< msg_len << endl;
//长度非法
if(msg_len > MAX_LENGTH){
std::cout <<"invalid data length is "<< msg_len << endl;
_server->ClearSession(_uuid);
co_return;
}
_recv_msg_node = make_shared<RecvNode>(msg_len, msg_id);
//读出包体
n = co_await boost::asio::async_read(_socket,
boost::asio::buffer(_recv_msg_node->_data, _recv_msg_node->_total_len), use_awaitable);
if(n ==0){
std::cout <<"receive peer closed"<< endl;
Close();
_server->ClearSession(_uuid);
co_return;
}
_recv_msg_node->_data[_recv_msg_node->_total_len]='\0';
cout <<"receive data is "<< _recv_msg_node->_data << endl;
//投递给逻辑线程
LogicSystem::GetInstance().PostMsgToQue(make_shared<LogicNode>(shared_from_this(), _recv_msg_node));
}
}
catch(std::exception& e){
std::cout <<"exception is "<< e.what()<< endl;
Close();
_server->ClearSession(_uuid);
}
}, detached);
}

 

其余的逻辑和之前大体相同,测试了一下在一个iocontext负责接收新连接,一个iocontext负责接收数据和发送数据的情况下,客户端创建100个连接,收发500次,总用时为55s
https://cdn.llfc.club/20230616141235.png

简介

之前介绍了asio服务器并发编程的几种模型,包括单线程,多线程IOServicePool,多线程IOThreadPool等,今天带着大家利用asio协程实现并发服务器。利用协程实现并发程序有两个好处
1   将回调函数改写为顺序调用,提高开发效率。
2   协程调度比线程调度更轻量化,因为协程是运行在用户空间的,线程切换需要在用户空间和内核空间切换。

协程案例

asio官网提供了一个协程并发编程的案例,我们列举一下

#include<boost/asio/co_spawn.hpp>
#include<boost/asio/detached.hpp>
#include<boost/asio/io_context.hpp>
#include<boost/asio/ip/tcp.hpp>
#include<boost/asio/signal_set.hpp>
#include<boost/asio/write.hpp>
#include<cstdio>
using boost::asio::ip::tcp;
using boost::asio::awaitable;
using boost::asio::co_spawn;
using boost::asio::detached;
using boost::asio::use_awaitable;
namespace this_coro = boost::asio::this_coro;
#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
# define use_awaitable \
boost::asio::use_awaitable_t(__FILE__, __LINE__, __PRETTY_FUNCTION__)
#endif
awaitable<void> echo(tcp::socket socket)
{
try
{
char data[1024];
for(;;)
{
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), use_awaitable);
co_await async_write(socket, boost::asio::buffer(data, n), use_awaitable);
}
}
catch(std::exception& e)
{
std::printf("echo Exception: %s\n", e.what());
}
}
awaitable<void> listener()
{
auto executor = co_await this_coro::executor;
tcp::acceptor acceptor(executor,{ tcp::v4(),10086});
for(;;)
{
tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
co_spawn(executor, echo(std::move(socket)), detached);
}
}
int main()
{
try
{
boost::asio::io_context io_context(1);
boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);
signals.async_wait([&](auto,auto){ io_context.stop();});
co_spawn(io_context, listener(), detached);
io_context.run();
}
catch(std::exception& e)
{
std::printf("Exception: %s\n", e.what());
}
}

1   我们用awaitable<void>声明了一个函数,那么这个函数就变为可等待的函数了,比如listener被添加awaitable<void>之后,就可以被协程调用和等待了。
2   co_spawn表示启动一个协程,参数分别为调度器,执行的函数,以及启动方式, 比如我们启动了一个协程,deatched表示将协程对象分离出来,这种启动方式可以启动多个协程,他们都是独立的,如何调度取决于调度器,在用户的感知上更像是线程调度的模式,类似于并发运行,其实底层都是串行的。

  1. co_spawn(io_context, listener(), detached);

我们启动了一个协程,执行listener中的逻辑,listener内部co_await 等待 acceptor接收连接,如果没有连接到来则挂起协程。执行之后的io_context.run()逻辑。所以协程实际上是在一个线程中串行调度的,只是感知上像是并发而已。
3   当acceptor接收到连接后,继续调用co_spawn启动一个协程,用来执行echo逻辑。echo逻辑里也是通过co_wait的方式接收和发送数据的,如果对端不发数据,执行echo的协程就会挂起,另一个协程启动,继续接收新的连接。当没有连接到来,接收新连接的协程挂起,如果所有协程都挂起,则等待新的就绪事件(对端发数据,或者新连接)到来唤醒。

改进服务器

我们可以利用协程改进服务器编码流程,用一个iocontext管理绑定acceptor用来接收新的连接,再用一个iocontext或以IOServicePool的方式管理连接的收发操作,在每个连接的接收数据时改为启动一个协程,通过顺序的方式读取收到的数据

  1. voidCSession::Start(){
    auto shared_this = shared_from_this();
    //开启接收协程
    co_spawn(_io_context,[=]()->awaitable<void>{
    try{
    for(;!_b_close;){
    _recv_head_node->Clear();
    std::size_t n = co_await boost::asio::async_read(_socket,
    boost::asio::buffer(_recv_head_node->_data, HEAD_TOTAL_LEN),
    use_awaitable);
    if(n ==0){
    std::cout <<"receive peer closed"<< endl;
    Close();
    _server->ClearSession(_uuid);
    co_return;
    }
    //获取头部MSGID数据
    short msg_id =0;
    memcpy(&msg_id, _recv_head_node->_data, HEAD_ID_LEN);
    //网络字节序转化为本地字节序
    msg_id = boost::asio::detail::socket_ops::network_to_host_short(msg_id);
    std::cout <<"msg_id is "<< msg_id << endl;
    //id非法
    if(msg_id > MAX_LENGTH){
    std::cout <<"invalid msg_id is "<< msg_id << endl;
    _server->ClearSession(_uuid);
    co_return;
    }
    short msg_len =0;
    memcpy(&msg_len, _recv_head_node->_data + HEAD_ID_LEN, HEAD_DATA_LEN);
    //网络字节序转化为本地字节序
    msg_len = boost::asio::detail::socket_ops::network_to_host_short(msg_len);
    std::cout <<"msg_len is "<< msg_len << endl;
    //长度非法
    if(msg_len > MAX_LENGTH){
    std::cout <<"invalid data length is "<< msg_len << endl;
    _server->ClearSession(_uuid);
    co_return;
    }
    _recv_msg_node = make_shared<RecvNode>(msg_len, msg_id);
    //读出包体
    n = co_await boost::asio::async_read(_socket,
    boost::asio::buffer(_recv_msg_node->_data, _recv_msg_node->_total_len), use_awaitable);
    if(n ==0){
    std::cout <<"receive peer closed"<< endl;
    Close();
    _server->ClearSession(_uuid);
    co_return;
    }
    _recv_msg_node->_data[_recv_msg_node->_total_len]='\0';
    cout <<"receive data is "<< _recv_msg_node->_data << endl;
    //投递给逻辑线程
    LogicSystem::GetInstance().PostMsgToQue(make_shared<LogicNode>(shared_from_this(), _recv_msg_node));
    }
    }
    catch(std::exception& e){
    std::cout <<"exception is "<< e.what()<< endl;
    Close();
    _server->ClearSession(_uuid);
    }
    }, detached);
    }

     

其余的逻辑和之前大体相同,测试了一下在一个iocontext负责接收新连接,一个iocontext负责接收数据和发送数据的情况下,客户端创建100个连接,收发500次,总用时为55s
https://cdn.llfc.club/20230616141235.png

标签:Asio,asio,协程,boost,std,msg,co,Boost
From: https://www.cnblogs.com/bwbfight/p/17629511.html

相关文章

  • Kotlin: 什么是协程(coroutine)?
    在Kotlin中,协程(Coroutine)是一个轻量级的线程管理工具,它可以用更简单、更可读的方式来处理并发和异步任务。协程使得编写异步代码的方式几乎与同步代码一样简单。1.基础概念协程与线程的差异:线程是操作系统级别的,其调度由操作系统管理。相反,协程是应用级别的,其调度由程序或库管理。......
  • golang之协程+chan通道
     [管道]分为有缓冲和无缓冲两种无缓冲: 1)接受者与发送者必然存在于两个协程,否则会造成互相等待死锁的情况顺序执行多协程:varch1=make(chanint)varstopFlag=make(chanbool)//保证两个协程顺序执行gofunc(){fmt.Println("g1")......
  • could not find boost (missing iostreams) (found version xxxx)
    具体报错信息如上图,通过终端指定-DBOOST_LIBRARYDIR是无效的,需要在cmakelis中修改。注意这里报错溯源是cmakelistline29,所以修改如下set(CMAKE_INCLUDE_PATH${CMAKE_INCLUDE_PATH}"/home/rzhang/del/include")###新增set(CMAKE_LIBRARY_PATH${CMAKE_LIBRARY_PATH}"/h......
  • Python黑魔法 --- 协程分解与封装
    Python黑魔法---异步IO(asyncio)协程pythonasyncio网络模型有很多中,为了实现高并发也有很多方案,多线程,多进程。无论多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态。使用协程可以实现高效的并发任务。Python的在3.4中引入了......
  • boost
    1.AlgorithmGraphTheBGLgraphinterfaceandgraphcomponentsaregeneric,inthesamesenseastheStandardTemplateLibrary(STL).GeometryTheBoost.Geometrylibraryprovidesgeometricalgorithms,primitivesandspatialindex.PolygonVoronoidiagram......
  • C++ Boost库简介
    1、boost是一个功能强大、构造精良、跨平台、代码开源、完全免费的c++程序库。1)功能强大:共包含160余个库/组件,涵盖字符串与文本处理、容器、迭代器、算法、图像处理、模板元编程、并发编程等多个领域。2)构造精良: 由c++标准委员会成员发起倡议并建立boost社区,C+......
  • C++ Boost库介绍
    Boost库是C++的一个开源类库,包含了大量实用工具和组件,可以大大简化C++编程过程中的繁琐操作。以下是Boost库常见的运用场景:1.多线程编程:Boost.Thread模块提供了丰富的线程相关功能,如锁、条件变量、线程池等,使得多线程编程更加容易。2.正则表达式处理:Boost.Regex模块提供了对正......
  • C++ 字符串拼接技巧(stringstream、字符串迭代器、字符串的加法运算符、std::accumulat
    在C++中,经常需要将多个字符串拼接成一个大字符串。这个过程很容易出错,但有一些技巧可以帮助我们轻松地实现这个目标。本文将介绍一些C++中join字符串的技巧。一、使用stringstreamstringstream是一个流。使用它可以将多个字符串连接起来,然后将它们转换为一个字符串。可......
  • 进程 线程 协程的区别
    进程进程是操作系统,进行资源分配和调度的基本单位,多个进程之间相互独立,进程的特点是稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制线程线程是cpu进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的,能独立运......
  • 分享之python 协程
    线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。协程的适用场景:当程序中存在大......