boost::asio 在创建io_service时,可以指定线程数,如果没有指定,默认是一个线程,也就是io_service run的那个线程,如果没有任务运行,该线程会退出。
如果在创建的时候指定了线程数,那么io_service在执行的时候线程数就可以并发执行,如果你run的线程还是只有1个,那么io_service想多线程执行也没有条件,asio在内部是没有创建多线程的,所以这个时候需要你来手动创建多个线程执行io_service的run操作,这样才可以提高效率。顺便说下,worker辅助类可以保证ioservice一直run下去,直到调用stop,才会退出,这个是个很不错的用法。另外客户端异步模式是使用的select模型,会创建一些线程来实现异步的功能,所以在使用异步connect的时候,会asio会创建一定的线程数目。
所以大家在使用asio的时候,想提高效率,那么一定要注意合适的线程数。还有别忘记使用worker类了。
---------------------------------------------------------------------------------------------------------------------------------------------------------
自己开发的基于boost::asio的网络引擎
今 天找到了贵论坛,发现坛主的很多想法和本人不谋而合,本人近1年主要精力都致力于开发一个大型多人在线游戏的基本架构和相关的技术模组。而我欣喜的发现我 与坛主的研究方向正好相反:我是先从服务器端开始研究入手的,目前服务器端告一段落,正准备开始客户端的研发,在寻找客户端引擎的时候碰巧找到了这里。
我看到坛主的这个板块,了解到Orz正需要一些服务器方面的资料,在此我先奉上个人的服务器端的一些成果,希望能有所帮助。
(一)自己开发的一个基于boost::asio的网络引擎
首先这个网络引擎是基于boost::asio的一个封装,网络部分功能非常底层,API只有基本的listen、connect、send、kick等 (均为异步,目前只实现了TCP协议),而其他方面提供的是基于mysql的db接口和log接口,还有一个自己开发的对象池,用于使用FreeList 的概念来事先分配内存,降低运行时期内存的分配时间;
另外就是开发了一个多线程下的数据结构,一个线程安全的map,这个map可以让无限个线程同时读和写(包括添加元素、删除元素、修改元素)而无需任何因为互斥锁定带来的线程等待等开销。即是说1000个线程和1个线程操作这个map的效率是相同的。
发布形式:win32(64位未测试,但是开发考虑了相关的定制,例如指针和long在64位下从4字节提高到8字节,引擎底层做了数据类型的 typedef)下 dll+lib+include;linux(Redhat、CentOS5.x,gcc3.4以上,需要安装boost1.37和 mysql5.0)so+include;source code,yes,of course!
网络部分的基本结构是这样的:
#1 io部分设计。一个线程池负责处理io,这个实际上就是一组boost::asio::io_service::run,每个 boost::asio::io_service下有一组私有线程,负责处理异步io事件,这里,boost::asio::io_service得数量 和其下私有线程的数量是可以根据配置文件自由设置的,如果你了解boost::asio,那么一定知道它推荐一个cpu对应一个 boost::asio::io_service对象(或者一个boost::asio::io_service,但是每个 boost::asio::io_service下的私有线程对应每个cpu),这样在多处理器(或者多核处理器)下效率可以达到最高;
#2 complete handler设计。另一个线程池负责处理封装好的complete handler,即对应io事件的用户定义的逻辑处理,例如io recv事件,对应一个用户实现邦定的(使用boost::bind和boost::function)handler来处理当接受到socket消息的 时候调用对应的handler(函数、仿函数对象、成员函数等)。#1和#2中的线程池之间使用一组线程安全的队列来传递消息(传递使用直接的值拷贝,不 使用动态内存,因为动态内存的申请和释放太消耗时间,即便使用内存池也一样。1k以下的值拷贝的时间损耗都远远小于对应的动态内存申请的时间;另外使用值 拷贝也有线程安全的考虑);
#3 封包的设计。head+body,head中有固定4字节的body长度信息(int32)和4字节的封包类型信息(int32),如果愿意,可以修改代 码进行扩展(packet部分是独立于引擎的模块,也是一组dll,lib,include或者so,include),接受和发送由于是tcp,所以按 照head中的body长度来控制一个封包的完整性。
#4 多线程模型。boss-worker模型,主要用于广播消息、查找、和db、log的实现上;生产者、消费者模型,主要用于#1和#2 的两个线程池之间的事件传递(io线程池产生completehandler,用户的线程池负责处理、消费)
#5 session的设计。一个session就是一个成功连接进来的客户端socket代理,出于线程安全和效率的考虑,session的存储容器不使用任 何stl和boost的容器,而是使用——C的数组(当然不是静态数组,而是:new char[n]这样的),来实现。而且是二维数组,这样配合对象池(指与先分配好一组“空”的session),我们无需任何互斥变量就能够毫无阻碍的访 问和修改数组中的每个元素(session),并发性能达到最大。至于二维数组的设计,第二维的值对应io线程池和用户线程池中的线程数量,即一个线程唯 一邦定一组session,这是为了分配session id时候效率和安全的考虑。(例如id 0~9对应一组session,10~19是第二组,而每组id和session都唯一绑定一个线程)
#6 线程之间数据传递的设计。线程安全的queue,使用了boost::thread::locks中的mutex、shared_mutex、 condition_variable_any等概念,当queue空闲时,使用条件变量等待特定的事件(例如新的元素push进来,或者程序退出等);
#7 异步下session的唯一性。由于整体是异步的,所以不可避免的会出现当一个session的某个处理还未结束的时候,这个session已经消失了, 甚至换了一个新的session(指id的分配),那么这个时候如果没有任何防范处理,之前的那个未完成的处理很可能会作用到这个新的session上, 就不可避免的出现一些错误和未知情况,我们如何防范呢?使用valid_code,设计一个64位的无符号整型变量,给每个session按照事先给定的 session总量(这个引擎使用预先分配内存方法,所以开始前必须手动指定session的最大数量——通过配置文件),分配一个唯一的数据段(例如 1~10000000,10000001~2000000等),每次这个session发现有新连接进来的socket使用了自己,则将自己的 valid_code +1,当加到最大值的时候(例如刚才举例的10000000等),自动变为最小值(例如刚才的1等)。每个session的valid_code在引擎的 初始化阶段是随机生成的(在事先指定好的数据段中随机)。再加上每个session的数据段时唯一的,所以不会产生重复的valid_code,这样鉴别 某个时间段内唯一的session就成了可能;
#8 agent基类的设计。这个其实就是封装了boost::thread类,即“带私有线程的类”,主要用于作为一些相对独立的工作,例如log记录、db访问处理、定期ping某个ip等,引擎中的logger和db接口都是继承自它;
#9 db接口设计。一个数据库对应一个database对象,每个database对象拥有一个自己私有的线程池,其中线程的数量可以根据配置文件自由设定(例如和mysql的连接数量等,处理query的线程数量等)
#10 一些零碎。BIG_ENDIAN问题,封包内部自动进行了处理,无须用户单独设置;跨平台以及不同编译器的预编译设置,以及不同cpu的针对处理(x86,powerpc等)
目前的不足之处:
#1 并未开发完全。udp没有实现封装,当然boost::asio完全支持。logger目前只支持printf功能,对于写file和传递到专门的log服务器方面只留下了接口;封包加密以及安全方面是一个空白,目前的版本并未添加。
#2 面向底层,并不提供高层功能,所以很多开发都需要自己进行;(对,这并不是一个“网游引擎”)
#3 性能方面并未经过大量测试,由于本人工作较忙,这些都是业余时间搞得,所以只是初步测了一下连接并发部分:使用某个不知道配置的笔记本测得3秒并发连接1500。再多的并发连接并没有尝试过。
PS 由于本人工作较忙,故只能提供源代码(只提供windows版,便于查看,linux可以直接使用源代码编译,gcc3.4以上boost1.37即可),文档方面一直没有时间整理,这篇文章都是中午抽空写的(零零散散修改到现在),所以暂时就写这么多把。
PS2 提供的源代码是vs2005sln,只包含source code、配置和工程文件。
PS3 我看到贵论坛在研究RakNet,据我的一个前同事说,他认为RakNet并不好,网络底层用的是select,而且不是异步,代码质量不高,建议我不要 使用它的网络层。我感觉RakNet的一些高层功能还是可以参考的,例如安全加密、大厅分流等,至于网络底层,我建议还是用boost::asio,跨平 台、性能和扩展性都很优秀,而且被C++标准委员会所支持,很被看好。
---------------------------------------------------------------------------------------------------------------------------------------------------------
HTTP Server性能测试报告
今天仔细看了asio的性能测试项目:Linux Performance Improvements,自己也动手实践了一下,不过测试的不是asio本身不同实现机制的性能(这个比较麻烦,需要下载多个asio的实现版本),只是简单测试了一下asio example中四个不同io_service模型的HTTP Server的性能,看看谁是牛b。
测试环境
linux服务器,CPU有4个processor,详细配置为:
Linux 2.6.9-67.ELsmp #1 SMP Wed Nov 7 13:58:04 EST 2007 i686 i686 i386 GNU/Linux Intel(R) Xeon(R) CPU E5430 @2.66GHz
HTTP Server io_service模型
server 1:a simple single-threaded server. 单线程,单io_service
server 2:io_service-per-CPU design. 多线程,多io_service,每个线程处理一个io_service,采用轮询方式选择io_service
server 3:a single io_service and a thread pool. 多线程,单io_service,所有线程都运行在同一个io_service上
server 4:a single-threaded HTTP server implemented using stackless coroutines
测试方法
分别将server运行在1、2、3、4个CPUs(即processors)
taskset -c 1 ./server 127.0.0.1 55555 /home/zhongying
taskset -c 1,2 ./server 127.0.0.1 55555 /home/zhongying
taskset -c 1,2,3 ./server 127.0.0.1 55555 /home/zhongying
taskset -c 0,1,2,3 ./server 127.0.0.1 55555 /home/zhongying
在同一台服务器上运行ab进行测试,并发100连接,请求4K的数据
taskset -c 0 ab -c 100 -n 100000 'http://127.0.0.1:55555/test.txt'
测试结果

从测试结果可以看多线程的server2和server3的性能差别不大,server2略微胜出,个人觉得servers3使用strand机制来防止多个线程同时执行一个连接的handler会产生一些开销,可能会稍微影响性能。
浙公网安备 33010602011771号