用简单异步
可以轻松改造同步网络库从而获得大幅性能提升,用它改造异步回调网络库可以让我们以同步方式写代码,让代码更简洁,可读性更好,还能避免回调地狱
的问题.
本文通过两个
例子分别来介绍如何用简单异步
改造基于asio
的同步网络库和异步回调网络库.
示例
依赖了独立版的asio
(commitid:f70f65ae54351c209c3a24704624144bfe8e70a3),它是headeronly
的直接包含头文件即可.
以一个同步的echo server/client
为例,先来看看echoserver
:
空 开始服务器(异网::io环境&io环境,正 短 端口){ 传控::受者 a(io环境,传控::端点(传控::v4(),端口)); 对(;;){ 动[错误,套接字]=接受(a); 会话(标::移动(套接字)); }}
echoserver
启动时先监听端口,然后同步accept
,当有新连接到来时在会话
中处理网络读写事件.
元<型名 异网缓冲>标::双<异网::错误码,大小型>读些(异网::ip::传控::套接字&套接字, 异网缓冲&&缓冲){ 异网::错误码 错误; 大小型 长度=套接字.读些(标::前向<异网缓冲>(缓冲),错误); 中 标::造双(错误,长度);}空 会话(传控::套接字 套接字){ 对(;;){ 常 大小型 最大长度=1024; 符 数据[最大长度]; 动[错误,长度]=读些(套接字,数据,最大长度); 写(套接字,数据,长度); }}
在循环
中先同步读数据
,再把读到的数据
发送到对端.
同步的echo client
:
空 开始(异网::io环境&io环境,标::串 主机,标::串 端口){ 动[ec,套接字]=连接(io环境,主机,端口); 常 整 最大长度=1024; 符 写缓冲[最大长度]={"你好 简单异步"}; 符 读缓冲[最大长度]; 常 整 数=10000; 对(整 i=0;i<数;++i){ 写(套接字,写缓冲,最大长度); 动[错误,回复长度]=读些(套接字,读缓冲,最大长度); //处理数据. }}
同步的echoclient
也是类似的过程,先连接,再循环发送数据和读数据.整个逻辑都是同步的,代码简单易懂.
当然,因为整个过程都是同步等待的,所以无法并发的处理网IO
事件,性能是比较差的.如果用简单异步
来改造这个同步的网络库则可以大幅提升网络库的性能(参考benchmark),而且需要修改的代码很少,这就是简单异步
的威力.
简单异步::协程::懒<空>会话(传控::套接字 套接字){ 对(;;){ 常 大小型 最大长度=1024; 符 数据[最大长度]; 动[错误,长度]= 协待 异步读些(套接字,异网::缓冲(数据,最大长度)); 协待 异步写(套接字,异网::缓冲(数据,长度)); } 标::错误码 ec; 套接字.关闭(异网::ip::传控::套接字::都关闭,ec); 套接字.关闭(ec); 标::输出<<"完成回声消息,总:"<<消息索引-1<<".\n";}简单异步::协程::懒<空>开始服务器(异网::io环境&io环境, 正 短 端口, 简单异步::执行器*E){ 传控::受者 a(io环境,传控::端点(传控::v4(),端口)); 对(;;){ 动[错误,套接字]=协待 异步接受(a); 会话(标::移动(套接字)).通过(E).开始([](动&&){}); }}
可以看到用简单异步
改造的start_server
和会话
相比,返回类型变成了Lazy
,同步接口变成了co_await async_xxx
,原有的同步逻辑没有任何变化,这个微小的改动即可让我们把同步网络库改成异步协程网络库从而让性能得到巨大的提升.
更多的代码细节请看demo_example
中echoserver/client
的例子.
对于一些已有的异步回调网络库,也可以用简单异步
来消除回调,从而让我们可以用同步方式去使用异步接口,让代码变得更简洁易懂.
以asio
异步httpserver
为例:
空 连接::干读(){ 动 本(从本共享()); 套接字_.异步读些(异网::缓冲(缓冲_), [本,本](标::错误码 ec,标::大小型 传输字节) { 如(!ec) { 请求解析器::结果类型 结果; 标::绑定(结果,标::忽略)=请求解析器_.解析(请求_,缓冲_.数据(),缓冲_.数据()+传输字节); 如(结果==请求解析器::好) { 请求处理器_.请求处理(请求_,回复_); 干写(); } 异 如(结果==请求解析器::坏) { 回复_=回复::回复股票(回复::坏请求); 干写(); } 异 { 干读(); } } 异 如(ec!=异网::错误::中止操作) { 连接管_.停止(从本共享()); } });}空 连接::干写(){ 动 本(从本共享()); 异网::异步写(套接字_,回复_.到缓冲(), [本,本](标::错误码 ec,标::大小型) { 如(!ec) { 异网::错误码 忽略误码; 套接字_.关闭(异网::ip::传控::套接字::都关闭, 忽略误码); } 如(ec!=异网::错误::中止操作) { 连接管_.停止(从本共享()); } });}
可以看到基于回调
的异步网络库的代码,比同步
网络库复杂很多,如在异步读的回调中如果没有读到完整数据需要递归调用do_read
,如果读到完整数据之后才能在回调中调用异步写接口.同时,还要注意将shared_from_this()
得到的std::shared_ptr
传入到异步接口的回调函数中以保证安全的回调.总之,异步回调代码的编写难度较大,可读性也较差,如果用简单异步
改造则可以较好的解决这些问题,性能也不会降低.
简单异步::协程::懒<空>干读(){ 动 本(从本共享()); 对(;;){ 动[错误,传输字节]= 协待 异步读些(套接字_,异网::缓冲(读缓冲_)); 如(错误){断;} 请求解析器::结果类型 结果; 标::绑定(结果,标::忽略)=解析器_.解析( 请求_,读缓冲_,读缓冲_+传输字节); 如(结果==请求解析器::好){ 请求处理(请求_,响应_); 协待 异步写(套接字_,响应_.到缓冲()); }异 如(结果==请求解析器::坏){ 响应_=构建响应(状态类型::坏请求); 协待 异步写(套接字_,响应_.到缓冲()); 断; } }}
用简单异步
改造之后的httpserver
完全消除了回调函数,完全可按同步
方式写异步代码
,简洁易懂.
测试结果,提高2倍速度!