c++ 网络库asio的优势
如果说答案是性能,那么肯定有人会满不在乎。觉得性能不够的话,只要加机器就可以了。然而更少的机器,意味着更低的能耗,更少的硬件投入,更少的人力资源投入去维护机器。总而言之,更低的成本。
肯定会有人说,C++的开发速度太慢了。然而这并不是绝对的。C++也可以做到非常快速的开发。有句俗语“脚本一时爽,重构火葬场”说的正是脚本语言开发的项目进入维护阶段后无穷的灾难。而C++经过了几十年的发展,拥有庞大的工具链.不管是动态分析还是静态分析都有大量的工具,能极大的帮助程序员减少错误.c++得益于精良的设计,严格的检查,越是大型的工程,越是能降低开发成本。
但这并不意味着C++就不适合小型项目了。小型的项目,也可以快速开发。因为C++11开始,已经感觉像是全新的语言了,可以完全以脚本的形式去使用,获得接近甚至超越脚本语言的开发速度,同时得益于编译优化,获得不俗的运行时性能。C++正是鱼和熊掌得兼的语言。
为什么要用asio这个库?
事实上如果使用C++开发服务端程序,你有多得数不清的选择。什么ACE啦,libuv啦,libevent啦,libev啦,甚至可以直接使用epoll/iocp这样的系统API。为什么要用asio呢?
那些年我们用过的网络库
在计算机史前文明时代,曾经有个世界难题,叫“c10kproblem”.这个是继y2kproblem后的又一个重大攻关项目.全世界的文艺青年都想拿下解决这个问题的荣誉,正可谓八仙过海,各显神通.
那一年,NPTL还没有研究出来.还不能创建成千上万个线程
那一年,windows还在蓝屏中挣扎,无暇顾及网络.
然而,曙光还是有的.异步的出现带给了人以希望.古老的UNIX早就想到了,提供了select()系统调用供人驱使.然而问题还是有的,select只能支持1024个文件描述符,windows上的select更是劣质到只能使用64个.就算通过修改定义强迫接受一万个文件描述符,也没有解决实际的问题.select实在是太慢了.
在这种背景下,IBM老大哥带领着MS老弟先搞了IOCP.然而开源的人有开源的做法,在NIH综合症的影响下,BSD的人敢为天下所不齿,发明了Kqueue.同样在NIH综合症影响下,Linux的一群M*的猴子捣鼓出了epoll.
分裂,让人头疼.
然而,他们都声称自己的新接口对select有质的提升,是破解c10k问题的不二法宝.你用也得用,不用也得用.为了让自己编写的网络程序能跨平台,程序员开始了对3大各自为阵的法宝的膜拜学习.
除了需要应对多套互不兼容的API,异步本身也需要更高级的抽象,把程序员从编写异步代码的地狱模式里拯救出来.于是程序员们急需一个上天入地无所不能的法宝的法宝,把这3家法宝给统御起来.
率先站出来悳瑟的是ACE.
悳瑟的ACE
恰乱世刚过,天下待定,C++委员会的老人们却韬光养晦,不问世事.所谓乱世出英雄,英雄出少年,欧文大学出了名秀才.凭借其洋洋洒洒的一片雄文《Pattern-OrientedSoftwareArchitecture》中举去了首府学城,并为ACE奠定了无可撼动的地位.
ACE的名字,也许灵感来自AdaptiveClubbedRod,这也是当年一位英雄少年的宝贝.既是宝贝,必需如意.即是后来的葫芦娃都怕了“如意宝贝”.
ACE如意在什么地方呢?如意其一,支持IOCP/kqueu/epoll/select/you_name_it各种接口,号曰没有不能跨的平台.如意其二,支持多种模型。这些模型都在《Pattern-OrientedSoftwareArchitecture》有过详细叙述.ACE本身就是这篇论文的实践,因为他知道,纸上得来终觉浅绝知此事要躬行。如意其三,接口和模式排列组合下,多少种,竟可不修改代码而适应。
然而ACE毕竟嫩了点,没过几年就失势了.现在除了一些老程序员还在用,新生代的程序员已经不再使用ACE了.为什么呢?陈硕在他的博客里说,ACE过于复杂,甚至比它试图封装的对象更复杂,程序员是指望用你的如意宝贝去驾驭另外那三家宝贝的,结果你比他们还难。ACE犯了早期C++库都会犯的一个错误,过度设计,过度java化。所谓java化,就是以对象代替接口,以虚函数代替回调,以继承代替组合。以虚类代替模板。对象间关系错综复杂,牵一发而动全身。除了作者,已经无人能参与ACE的开发了。
与此同时,C语言的回归却在背后悄然进行。C语言的复辟,带来了几个更为糟糕的替代品,libevent和libev,以及乘着nodejs的盛行东风而来的libuv。
原始的libevent
C语言有着顽强的生命力,当然,这并不是因为C语言有多好,在后续的章节了我们还会深入的探讨C++相对C的改进。C语言的顽强和人天生的懒惰和偏见是有一定关系的。这种惰性表现为随遇而安,表现为固执己见。非要拼命的否定C++,固守C,对C的缺点视而不见,诋毁C++相对C的改进。固守的结果就是简陋原始的libevent.然而因为保守党巨大的人数优势,libevent应其群众基础良好而获得了空前的广泛使用。
libevent就如名字所言,是一个异步事件框架。从OS那里获得事件,然后派发。派发机制就是“回调函数”。异步异步,归根结底就是处理从操作系统获得的事件。iocp也好,epoll也罢,都只是用来获取事件的接口。libevent去掉了ACE华而不实的包装,保留了异步事件,极大的简化了模型。不得不说软件工程是个糟糕的发明,从来都把简单问题复杂化。libevent把简单问题简单化,让异步网络编程反朴归真,应该来说,本是一个好库。
然而libevent因为设计缺陷,例如使用全局变量,定时器无法处理时间跳变,诸如此类的设计缺陷导致了libev的出现。libev就是为了克服libevent的缺陷而诞生的。然而,libev就一定好了吗?
禁锢的libev
libev带着对libevent的怨气出世了。吸收了libevent的所有缺点,虽然承诺过改进。然而libev如何改进的了呢?libev已经够原始了,向下改进还不如让人直接使用系统的api,向上改进,一是会导致和libevent的重叠,二是很快就碰到了C语言强加的禁锢。
C语言因其语法简陋简洁而著称。然而,缺乏必要的抽象能力,导致C语言编写异步程序,就如同安迪拿着小锤子琢开肖生克监狱的墙壁一样。能,但是要耗费巨大的精力和时间。编写异步程序,最需要的2个抽象能力,其一为协程,其二是函数对象,包括匿名函数对象,也就是lambda。C统统没有。函数对象是实现闭包比不可少的,如果没有函数对象,就只能通过携带void指针的形式迂回完整,繁琐不说,还特别容易出错。程序里也到处充满了类型强转。到处是临时定义的类型,就为了传给void使用。
尽管C有那么多缺点,然而libev还未来得及被C的缺点拖累,因为他不支持IOCP.于是libuv就出来给libev擦屁股了。支持了iocp后的libuv就真的只有C本身的缺点了吗?
混乱的libuv
libuv可以说是C语言的异步库所能达到的最高高度了。完完全全的触碰到了C语言的自身瓶颈,好在libuv只是nodejs的底层库,上层软件转移到javascript语言而逃避了C的禁锢。
真的是这样的吗?libuv自身还有什么缺点呢?
开源社区avplayer的大拿jackarain曾经说过,一个网络库好不好,就看他有没有正确的处理TCP关闭,readwrite实现的ui不对。libuv很遗憾的是,不合格。libuv的uv_write没有返回值,允许空回调。也就是忽略write错误。网络出错的情况下,libuv的用户只能稀里糊涂的知道出错了,至于错在哪?数据到底有没有发出去了?一概不知道。把数据交给uv_write后,就是一笔糊涂账了,大概TCP不可靠的说法就是从这里传出来的吧。
ASIO腾空出世
在地球最大的岛上,另一位少年开始拜读ACE的大作。那时候,没有libuv没有libev更没有libevent.有的只是ACE.然而这个南方小国的少年没有跟风陷入ACE崇拜,他以敏锐的目光察觉到了ACE的弊病。ACE哪里做的不好?又哪里是值得借鉴的?
少年在给c++委员会写的一篇上书中说,Proactor模型乃最优模型。而Proactor模型,乃ACE提出的6个模型之一。根据IT界赢者通吃律,一个优秀的网络库,只需要支持Proactor模型即可。支持其他次优模型都是徒劳的。ACE试图全盘通吃,犯了大忌。
少年在一次开发者大会的演讲上,再次透露,网络库不宜做成框架,而是要像系统的API那样,作为一个乐高积木。ACE做成了一个框架,同样不妥。
你说了那么多ACE不好,有本事你弄个好的啊?批评者向来都是这么理直气壮。正如ACE的作者实践了“纸上得来终觉浅绝知此事要躬行”一样,这位勇敢的少年也拿出了ASIO,“实践出真知”,他如是说。
那还是SARS病毒肆虐的年代,几乎没有人注意到,今后颠覆C++的网络世界的ASIO悄然出世了。而他的父亲,还只是悉尼的学子。ASIO并没有显赫的家庭背景,然而英雄不问出处,它注定将有不平凡的一生。
俗语有云,三岁看老。在asio才三岁的时候,它父亲就将asio引荐给了c++委员会的老人们。上一次他们这么做的时候,他们接纳了STL。ASIO最终被内定,然后放入Boost锻炼,经过Boost十余的锻炼,ASIO终于在2017年进入了c++标准。
前摄者?为什么是演员
在给c++老人会的引荐信里,asio爸爸仔细阐述了asio的设计抉择回答了围绕asio的设计提出的很多问题。为什么Proactor会是最佳模型?
- 跨平台许多操作系统都有异步API,即便是没有异步API的Linux,通过epoll也能模拟Proactor模式。
- 支持回调函数组合将一系列异步操作进行组合,封装成对外的一个异步调用。这个只有Proactor能做到,Reactor做不到。意味着如果asio使用Reactor模式,就对不起他“库”之名。
- 相比Reactor可以实现Zero-copy
- 和线程解耦长时间执行的过程总是有系统异步完成,应用程序无需为此开启线程
Proactor也并非全无缺点,缺点就是内存占用比Reactor大。Proactor需要先分配内存而后处理IO,而Reactor是先等待IO而后分配内存。相对的Proactor却获得了Zero-copy好处。因为内存已经分配好了,因此操作系统可以将接受到的网络数据直接从网络接口拷贝到应用程序内存,而无需经过内核中转。Proactor模式需要一个loop,这个loopasio将其封装为io_service.他不仅是asio的核心,更是一切基于asio设计的程序的核心。
宇宙级异步核心
io_service脱胎于IO但不仅用于IO.ChristopherKohlhoff在给委员会的另一份编号N3747的信上上说它是宇宙级异步模型UniversalAsynchronousModel。在宇宙级异步模型里,一个异步操作由三部分构成
- 发起按照asio的编码规范,所有的发起操作都使用async_前缀,使用async_动词的形式作为函数名。
- 执行异步过程在发起的时候被executor执行(系统可以是支持AIO的内核,不支持AIO的系统则是aiso用户层模拟)
- 完成并回调在发起async_*操作的时候,总是携带一个回调的闭包。asio使用闭包作为异步事件完成的处理回调,没而不是C式的回调函数。asio的宇宙异步模型里,回调总是在执行io_service::run的线程里执行。asio绝不会在内部线程里调用回调。
在回调里发起新的异步操作,一轮套一轮。整个程序就围绕着io_service::run运转起来了。io_service不仅仅能用于异步IO,还可以用来投递任意闭包。实现作为线程池的功能。这一通用型异步模型彻底击败微软PPL提案,致使微软转而研究协程。然而微软在协程上同样面临asio的绞杀。
闭包和协程
宇宙级asio使用闭包作为回调,而C库只能使用函数+void*,ACE虽然使用的C++语言,却不知道闭包为何物,使用的是虚函数作为回调。需要大量的从ACE的对象继承。以闭包为回调,asio更是支持了一种叫“无栈协程”的强悍武器。asio的无栈协程,仅仅通过库的形式,不论是在性能上,还是易用性上,还是简洁性上,甚至是B格上,都超过了微软易于修改语言而得的await提案。
微软,乃至ACE,并不是不知道闭包,而是在c++里实现闭包的宇宙级executor——也就是io_service,需要对模板技术的精通。asio“把困难留给自己,把方便带给大家”,以地球人无法理解的方式硬是在c++98上实现了宇宙级异步核心。当然,如果c++11早点出现,如果c++17早点出现,实现asio的宇宙模型会更加的简单——其实这也是c++的理念,增加语言特性,只是为了让语言用起来更简单。
缓冲区
有了闭包的支持,内存管理也变得轻轻松松起来。ASIO本身并不管理内存,所有的IO操作,只提交对用户管理的内存的引用,称Buffers。asio::buffers引用了用户提交的内存,保持整个IO期间,这块内存的有效性是用户的责任。然而这并不难!因为回调是一个闭包。通过闭包持有内存,只要asio还未回调,闭包就在,闭包在,内存在。asio在调用完回调后才删除相应的闭包。因此资源管理的责任可以丢给闭包,而闭包可以通过智能指针精确的控制内存。不是GC,胜于GC千百倍!益于c++的RAII机制,再无内存泄漏之忧!
以上就是c++网络库asio的优势的详细内容,更多关于c++网络库asio的资料请关注毛票票其它相关文章!