﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-oldworm-随笔分类-游戏开发</title><link>http://www.cppblog.com/oldworm/category/15069.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 03 Oct 2010 07:53:40 GMT</lastBuildDate><pubDate>Sun, 03 Oct 2010 07:53:40 GMT</pubDate><ttl>60</ttl><item><title>我的网络模块设计第二版</title><link>http://www.cppblog.com/oldworm/archive/2010/10/03/128474.html</link><dc:creator>袁斌</dc:creator><author>袁斌</author><pubDate>Sun, 03 Oct 2010 06:25:00 GMT</pubDate><guid>http://www.cppblog.com/oldworm/archive/2010/10/03/128474.html</guid><wfw:comment>http://www.cppblog.com/oldworm/comments/128474.html</wfw:comment><comments>http://www.cppblog.com/oldworm/archive/2010/10/03/128474.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/oldworm/comments/commentRss/128474.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/oldworm/services/trackbacks/128474.html</trackback:ping><description><![CDATA[<p>07年我写了一篇文章叫《我的网络模块设计》，姑且叫那个为第一版吧，由于持续对网络模块进行改进，所以现在的实现和当时有很大改变，加上上层应用越来越多，又经过了几年时间考验，现在的实现方式比之前的更灵活更有效率，也因为最近看了一些人做网络程序多年竟毫无建树，一直要用别人写的网络模块，所以有感而写此文，为了使得此文不受上一篇《我的网络模块设计》的影响，我决定写之前不看原来的文章，所以此文跟原文那篇文章可能没有太多相似性。<br>&nbsp;一个基本的网络模块，无非就是管理N个连接，快速处理每个连接的收发数据、消息等，所谓好的网路模块，无非就是稳定、高效、灵活，下面分几部分来写：<br>&nbsp;一、 连接管理<br>&nbsp;之所以首先写连接管理，是因为连接管理是核心，也是最难的地方，我写第一个网络库之前，搜索过很多当时可以找到的例子工程，当时几乎找不到可稳定运行的工程，当然更找不到好的，于是摸索前进，期间对连接管理使用了各种方法，从最早一个cs（临界区CriticalSection，我简称cs），recv send都用这个cs，到后来send用一个cs，recv用一个cs，用多个的时候还出过错，最后使用一个cs+一个原子值ref管理一个连接，每个连接send的时候用cs，recv的时候用ref，如果该连接的消息要跨线程异步执行，也使用ref，如此较简单的解决了连接管理的问题。<br>&nbsp;同样使用生存期管理方法，也有人用智能指针，虽然原理和我直接操纵生存期一样，但实现方法毕竟不同，不过我为了让实现依赖少一些没有引入智能指针。<br>&nbsp;当然我后来也发现很多人不是用这种方法，如有些人就id来管理连接，每个连接分个id，其他操作全部用id，每次对连接的调用先翻译一下，如果id找得到映射目标就调用，否则就说明该连接不存在了，这种方法简单只是不直接，多了个查找过程，另外查找的时候可能还需要全局锁（这依赖于连接数据组织）。<br>&nbsp;也有人使用一个线程管理连接，其他所有与该连接有关的生存期问题全部到该线程处理，这样也是可行的，只是需要做一个较好的包装，如果包装好上层调用方便，如果包装不好，可能上层调用就有一些约束。<br>&nbsp;虽然各种方法都有人使用，但我一直选择直接的生存期管理方法，其实内部实现的时候还是有很多优化措施的，减少了大量addref、release的调用，进一步提高了效率。<br>&nbsp;二、 线程组<br>&nbsp;我最初做网络库的时候还不是很清楚上层如何使用这个库，后来在上面做了几个应用之后慢慢有了更多想法，最近的网络库是设计了这么几组线程：io线程组、同步线程组、异步线程组、时钟线程组、log线程组，每组线程都可开可关，就算io线程组也是可关的，这只是为了整个库更灵活适用性更广泛，如只用同步线程组或异步线程组仅将这个线程组当一个消息队列使用。<br>&nbsp;Io线程组就是处理io收发的，listen recv send 以及解密解压缩都是在这组线程，一般这组线程会开2个或2*cpu个。<br>&nbsp;同步线程组，一般这组线程开1个，用来处理logic。<br>&nbsp;异步线程组，这组线程根据需要开0个或n个，简单应用无db等慢速操作的应用不开，有很多db等慢速操作的可以开很多个。<br>&nbsp;时钟线程组，一般不开或开1个。<br>&nbsp;Log线程组，一般开1个，主要为了避免其他线程调用WriteLog的时候被磁盘io阻塞，所以弄了一个log线程。<br>&nbsp;其实还有一个主线程，我的每组线程（包括主线程）都支持事件和定时器，io线程、同步线程、异步线程组、时钟线程组、甚至log线程组都支持事件和定时器，到去年我还只是让每组线程都支持事件，今年为了更好的使用时钟我给每组线程设计了定时器，现在定时器线程组有点鸡肋的味道，一般是用不上专门的定时器线程组，不过我还没有将它删掉，主要在我的设计里面，它和同步异步线程组一样，都只是一组线程，如果必要的时候可以将它用作同步线程或者异步线程组，所以继续保留了它的存在。<br>&nbsp;这几组线程之间都是可互发消息的，所以一个逻辑要异步到别的线程执行是非常方便的，只要调用一下PostXXEvent(TlsInfo *ptls, DWORD dwEvent, WPARAM wParam, LPARAM lParam);我凭借这个设计使得这套网络库几乎可以适用上层各种应用，不管是非常简单的网络应用还是复杂的，一框打尽。对最简单的，一个io线程搞定，其他线程全关，对于复杂的io线程+同步+异步+log全开。<br>&nbsp;三、 内存池<br>&nbsp;内存池其实没有想象中的那么神秘，当然如果要让一个网络程序持续7*24小时稳定高效运行，内存池几乎必不可少的，内存池的作用首先是减少内存碎片，其次是为了提高速度，我想这两点很容易想明白的，关于内存池我之前写了系列文章，可参考我的博客：<br>&nbsp;<br>《内存池之引言》 <a href="http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx">http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx</a><br>&nbsp;《单线程内存池》 <a href="http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx">http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx</a><br>&nbsp;《多线程内存池》 <a href="http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx">http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx</a><br>&nbsp;《dlmalloc、nedmalloc》 <a href="http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx">http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx</a><br>&nbsp;《线程关联内存池》 <a href="http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx">http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx</a><br>&nbsp;《线程关联内存池再提速》 <a href="http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx">http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx</a><br>&nbsp;<br>四、 定时器<br>&nbsp;关于定时器，上面讲线程组的时候已经讲过，我现在的设计是每个线程（包括主线程）都支持定时器，调用方法都是一样的，回调函数形式也是一样的，由于定时器放到各组线程里面，所以减少了线程之间的切换，提高了效率。<br>&nbsp;关于定时器，可参考《定时器模块改造》 <a href="http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx">http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx</a><br>&nbsp;<br>五、 包格式<br>&nbsp;关于包格式可参考《常用cs程序自定义数据包描述》 <a href="http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx">http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx</a><br>&nbsp;<br>六、 Buffer<br>&nbsp;之前的文章其实我一直没有提过我的buffer，其实我的buffer设计是很灵活的，现在它和pool也是有些关联的，我的poolset其实底下就是按照各种不同大小的buffer预设的尺寸。Buffer我设计为循环式，不允许回绕，包含<br>&nbsp;Char *pbase 块基址<br>&nbsp;Char *pread 当前读指针<br>&nbsp;Char *pwrite 当前写指针<br>&nbsp;DWORD tag;<br>&nbsp;Buffer *next;<br>&nbsp;Capacity 总分配尺寸，上面分配的时候可能只是指定了19，但实际可能分配的是32个字节，所以内部用的时候要根据capacity来最大限度的利用缓冲区。<br>&nbsp;Buffer分配还利用了一个技巧，事实上分配的时候是一次分配一个需要的大缓冲，前面为Buffer自身的数据，后面为数据部分，pbase指向数据部分，这样处理减少了一次分配，我估计很多人都在用这个技巧。<br>&nbsp;Pwrite总是不会小于pread的，但pread可能和pbase不一样，仅当后面空余空间不够用的时候才可能会移动数据，否则数据不会移动。<br>&nbsp;WSARecv的时候我是这么处理的，如果首次获取了一个包的一部分，但buffer中还有足够的空间放下包的剩余部分，我不会再分配一个buffer去recv，而是直接用原buffer指定一个合适的偏移和size去WSARecv，这样可以最大限度的减少复制。<br>&nbsp;刚才还有朋友问到我recv的层次组织，我的网络库里面是这样组织的，OnRecv是个虚函数，最基础的IocpClient的OnRecv只处理数据而不解析格式，IocpClientMsg就会认识默认的一种包格式，这个类的OnRecv会将m_recvbuf中的数据组织为msg，并尽可能的一次返回更多个msg，回调OnMsg函数，由上层决定该消息在哪个线程处理，这样我认为是最灵活的，如果是个很小的server，可能直接就在io线程里面处理了，也可postevent到同步线程处理，亦可PostEvent到异步线程处理。<br>&nbsp;<br>七、 TLSINFO<br>&nbsp;TlsInfo顾名思义就是每个线程关联的一组数据，暂时我还没有看到别人这么设计，也许我设计得有些复杂了，在这个数据里面有一些常用的和该线程相关的数据，如该线程的分配基、步长，用这两个参数可让每个线程制造出唯一序列，还有常用pool的地址，如tm_pool *p1k; tm_pool *p2k;&#8230; 这样设计使得要分配的时候直接取tm_pool，最大限度的发挥了分配速度，还有一些常规参量long c; long d; DWORD a; DWORD b;&#8230; 这几个值可理解为栈内值，其实为了减少上层调用复杂度的，如我将一个连接的包从io线程PostEvent到同步线程处理，PostEvent首参数就是tlsinfo，PostEvent会根据tlsinfo里面的一个内部值决定是不是要调用addref，因为我有个地方预增了2，所以大多数情况下在io发到其他线程的时候是无需调用addref的，提高了效率，tlsinfo里的其他一些值上层应用可使用，用在逻辑处理等情况下。<br>&nbsp;<br>八、 性能分析<br>&nbsp;*nix下有很多知名的网络库，但在win下特别是使用iocp的库里面，一直就没有一个能作为基准的库，即使asio也因为出来太晚不为大多数人熟悉而不能成为基准库，libevent接iocp由于采用0 buffer模拟所以也没有发挥出足够的性能，对比spserver我比它快70%左右，我总在想要是微软能将他那个iocp的例子写得更好一点就好了，至少学的人有一个更高一点的基础，而不至于让<a href="http://www.codeproject.com/KB/IP/iocp_server_client.aspx">http://www.codeproject.com/KB/IP/iocp_server_client.aspx</a>这样的垃圾代码都能成为很多人的样板。<br>&nbsp;<br>九、 杂谈<br>&nbsp;为了写好一个win下稳定高效的网络库，我07年的时候几乎搜遍了那个时间段之前所有能找到的iocp例子，还包括通过朋友等途径看到的如snda等网络库，可惜真没找到好的，大多数例子是只要多线程发起几千个连接不断发送数据马上就死了，偶尔几个不死的（包括snda的）只要随机连接并断开就会产生句柄泄漏，关闭所有连接之后句柄并不关闭等，也就是说这些例子连基本的生存期管理都没搞定，能通过生存期管理并且不死的只有有限的几个，可惜性能又太差，杯具啊。<br>&nbsp;早年写网络库的时候也加入了sodme在google上建的那个群，当时群还是很热闹的，可惜大多数人都是摸索，所以很多问题只是讨论却从无定论，没有谁能说服别人，也没有人可轻易被说服，要是现在或许有一些很有经验的人，可惜那个群由于GFW现在虽能访问也不大活跃了。<br>&nbsp;最近看到有些写网络程序7年甚至更久的人还在用libevent、ace等感想很复杂，可悲的是那些人还没意识到用一个库和写一个库有多大的区别，可能那些人一辈子也认识不到写一个库比用一个库难多少，那些人以为这些库基本会用了，让他自己去写也基本是照这个模式，不会有什么突破，就无需自己动手了，悲哀啊。当然，要写一个稳定的网络库需要耗费很多时间，特别是要写一个能和知名库性能接近或更好的库，更是要费神费力，没点耐心和持久力是不可能做好的。在中文领域随便查什么稍有些名气的代码，总是能找到很多剖析类文章，可原创的东西总是很少，也不知道那些大侠怎么搞的，什么都能剖析可怎么总写不出什么像样的东西呢。<br>&nbsp;其实本来没有打算写这篇文章，可能是看了陈硕的muduo才使得我有了写出来的冲动，大概是受到他的开源鼓励吧。<br>&nbsp;谨以此文记录本人最近3年对网络模块的修改并简短总结。</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/oldworm/aggbug/128474.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/oldworm/" target="_blank">袁斌</a> 2010-10-03 14:25 <a href="http://www.cppblog.com/oldworm/archive/2010/10/03/128474.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我的网络模块设计第一版</title><link>http://www.cppblog.com/oldworm/archive/2010/10/03/128473.html</link><dc:creator>袁斌</dc:creator><author>袁斌</author><pubDate>Sun, 03 Oct 2010 06:25:00 GMT</pubDate><guid>http://www.cppblog.com/oldworm/archive/2010/10/03/128473.html</guid><wfw:comment>http://www.cppblog.com/oldworm/comments/128473.html</wfw:comment><comments>http://www.cppblog.com/oldworm/archive/2010/10/03/128473.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/oldworm/comments/commentRss/128473.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/oldworm/services/trackbacks/128473.html</trackback:ping><description><![CDATA[<p><span><strong><span>我的</span><span><span>IOCP</span></span></strong><strong><span>网络模块设计</span></strong></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>为了设计一个稳定易用高效的</span><span><span>iocp</span></span><span>网络模块，我前前后后花了好几个月的时间，也曾阅读过网上很多资料和代码，但是非常遗憾，能找到的资料一般都说得很含糊，很少有具体的，能找到的代码离真正能商用的网络模块差得太远，大多只是演示一下最基本的功能，而且大多是有很多问题的，主要问题如下：</span></span></p>
<p><span><span><span><span>1、</span>&nbsp;</span></span></span><span><span>很多代码没有处理一次仅发送成功部分数据的情况。</span></span></p>
<p><span><span><span><span>2、</span>&nbsp;</span></span></span><span><span>几乎没有找到能正确管理所有资源的代码。</span></span></p>
<p><span><span><span><span>3、</span>&nbsp;</span></span></span><span><span>大多没有采用用</span><span><span>pool</span></span><span>，有的甚至画蛇添足用什么</span><span><span>map</span></span><span>查找对应客户端，没有充分使用</span><span><span>perhandle, perio</span></span><span>。</span></span></p>
<p><span><span><span><span>4、</span>&nbsp;</span></span></span><span><span>接收发送数据大多拷贝太多次数。</span></span></p>
<p><span><span><span><span>5、</span>&nbsp;</span></span></span><span><span>接收管理大多很低效，没有充分发挥</span><span><span>iocp</span></span><span>能力。</span></span></p>
<p><span><span><span><span>6、</span>&nbsp;</span></span></span><span><span>几乎都没有涉及上层如何处理逻辑，也没有提供相应解决方案（如合并</span><span><span>io</span></span><span>线程处理或单独逻辑线程）。</span></span></p>
<p><span><span><span><span>7、</span>&nbsp;</span></span></span><span><span>大多没有分离流数据和包数据。</span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span>问题还有很多，就不一一列出来了，有一定设计经验的人应该有同感。要真正解决这些问题也不是那么容易的，特别是在</span><span><span>win</span></span><span>下用</span><span><span>iocp</span></span><span>的时候资源释放是个麻烦的问题，我在资源管理上花了很多时间，起初也犯了很多错误，后来在减少同步对象上又花了不少时间（起初</span><span><span>client</span></span><span>用了两个同步对象，后来减少为</span><span><span>1</span></span><span>个）。下面我就我所设计的网络模块的各个部分进行简单的讲解</span></span></p>
<p><strong><span><span>一、内存管理。</span></span></strong></p>
<p><span><span>内存管理是采用池模式，设计了一个基础池类，可以管理某固定大小的池</span></span></p>
<p><span><span>class CBufferPool</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>void *newobj();</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>void delobj(void *pbuf);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;</span></span></span></p>
<p><span><span>}; </span></span></p>
<p><span><span>在基础池类上提供了一个模板的对象池</span></span></p>
<p><span><span>template &lt;class T&gt;</span></span></p>
<p><span><span>class CObjPool : public CBufferPool</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>public:</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>T *newobj()</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>void *p = CBufferPool::newobj();</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>T *pt = new(p) T;</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return pt;</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>void delobj(T* pt)</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>pt-&gt;~T();</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CBufferPool::delobj(pt);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span>};</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>在基础池的基础上定义了一个简单的通用池</span></span></p>
<p><span><span>class CMemoryPool</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>private:</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CBufferPool bp[N];</span></span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span>};</span></span></p>
<p><span><span>通用池是由</span><span><span>N</span></span><span>个不同大小的基础池组成的，分配的时候圆整到合适的相近基础池并由基础池分配。</span></span></p>
<p><span><span>最后还提供了一个内存分配适配器类，从该类派生的类都支持内存池分配。</span></span></p>
<p><span><span>class t_alloc</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>public:</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>static void *operator new(size_t size)</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return CMemoryPool::instance().newobj(size);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>static void operator delete(void *p, size_t size)</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CMemoryPool::instance().delobj(p, size);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span>};</span></span></p>
<p><span><span>根据测试</span><span><span>CMempool</span></span><span>分配速度比</span><span><span>CObjpool&lt;&gt;</span></span><span>稍微慢一点点，所以我在用的时候就直接用</span><span><span>t_alloc</span></span><span>类派生，而不是用对象池，这是个风格问题，也许有很多人喜欢用更高效一点的</span><span><span>objpool</span></span><span>方式，但这个并不大碍。</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>在网络模块中</span><span><span>OVERLAPPED</span></span><span>派生类就要用池进行分配，还有</span><span><span>CIocpClient</span></span><span>也要用池分配，再就是</span><span><span>CBlockBuffer</span></span><span>也是从池分配的。</span></span></p>
<p><span><span>如下定义：</span></span></p>
<p><span><span>struct IOCP_ACCEPTDATA : public IOCP_RECVDATA, public t_alloc</span></span></p>
<p><span><span>class CIocpClient : public t_alloc</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>二、数据缓冲区。</span></span></strong></p>
<p><span><span>数据缓冲区</span><span><span>CBlockBuffer</span></span><span>为环形，大小不固定，随便分配多少，主要有以下几个元素：</span></span></p>
<p><span><span><span>Char *pbase;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>环形首部</span></span></p>
<p><span><span><span>Char *pread;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>当前读指针</span></span></p>
<p><span><span><span>Char *pwrite;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>当前写指针</span></span></p>
<p><span><span><span>Int nCapacity;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>缓冲区大小</span></span></p>
<p><span><span><span>Long nRef;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>关联计数器</span></span></p>
<p><span><span>用这种形式管理缓冲区有很多好处，发送数据的时候如果只发送了部分数据只要修改</span><span><span>pread</span></span><span>指针即可，不用移动数据，接收数据并处理的时候如果只处理了部分数据也只要修改</span><span><span>pread</span></span><span>指针即可，有新数据到达后直接写到</span><span><span>pwrite</span></span><span>并修改</span><span><span>pwrite</span></span><span>指针，不用多次拷贝数据。</span><span><span>nRef</span></span><span>关联计数还可处理一个包发给</span><span><span>N</span></span><span>个人的问题，如果要给</span><span><span>N</span></span><span>个人发送相同的包，只要分配一个缓冲区，并设置</span><span><span>nRef</span></span><span>为</span><span><span>N</span></span><span>就可以不用复制</span><span><span>N</span></span><span>份。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>三、收发缓冲区管理</span></span></strong></p>
<p><strong><span><span>发送缓冲区</span></span></strong></p>
<p><span><span>我把</span><span><span>CIocpClient</span></span><span>的发送数据设计为一个</span><span><span>CBlockBuffer </span></span><span>的队列，如果队列内有多个则</span><span><span>WSASend</span></span><span>的时候一次发送多个，如果只有一个则仅发送一个，</span><span><span>CIocpClient</span></span><span>发送函数提供了两个，分别是：</span></span></p>
<p><span><span>Bool SendData(char *pdata, int len);</span></span></p>
<p><span><span>Bool SendData(CBlockBuffer *pbuffer);</span></span></p>
<p><span><span>第一个函数会检测发送链的最后一个数据块能否容纳发送数据，如果能复制到最后一个块，如果不能则分配一个</span><span><span>CBlockBuffer</span></span><span>挂到发送链最后面，当然这个里面要处理同步。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>接收缓冲区</span></span></strong></p>
<p><span><span>接收管理是比较简单的，只有一个</span><span><span>CBlockBuffer</span></span><span>，</span><span><span>WSARecv</span></span><span>的时候直接指向</span><span><span>CBlockBuffer-&gt;pwrite</span></span><span>，所以如果块大小合适的话基本上是不用拼包的，如果一次没有收到一个完整的数据包，并且块还有足够空间容纳剩余空间，那么再提交一个</span><span><span>WSARecv</span></span><span>让起始缓冲指向</span><span><span>CBlockBuffer-&gt;pwrite</span></span><span>如此则收到一个完整数据包的过程都不用重新拼包，收到一个完整数据包之后可以调用虚函数让上层进行处理。</span></span></p>
<p><span><span>在</span><span><span>IocpClient</span></span><span>层其实是不支持数据包的，在这个层次只有流的概念，这个后面会专门讲解。</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><strong><span>四、</span><span><span>IocpServer</span></span></strong><strong><span>的接入部分管理</span></strong></span></p>
<p><span><span>我把</span><span><span>IocpServer</span></span><span>设计为可以支持打开多个监听端口，对每个监听端口接入用户后调用</span><span><span>IocpServer</span></span><span>的虚函数分配客户端：</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual CIocpClient *CreateNewClient(int nServerPort)</span></span></span></p>
<p><span><span>分配客户端之后会调用</span><span><span>IocpClient</span></span><span>的函数</span><span><span><span> </span>virtual void OnInitialize();</span></span><span>分配内部接收和发送缓冲区，这样就可以根据来自不同监听端口的客户端分配不同的缓冲区和其他资源。</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span>Accept</span></span><span>其实是个可以有很多选择的，最简单的做法可以用一个线程</span><span><span>+accept</span></span><span>，当然这个不是高效的，也可以采用多个线程的领导者</span><span><span>-</span></span><span>追随者模式</span><span><span>+accept</span></span><span>实现，还可以是一个线程</span><span><span>+WSAAccept</span></span><span>，或者多个线程的领导者</span><span><span>-</span></span><span>追随者模式</span><span><span>+WSAAccept</span></span><span>模式，也可以采用</span><span><span>AcceptEx</span></span><span>模式，我是采用</span><span><span>AcceptEx</span></span><span>模式做的，做法是有接入后投递一个</span><span><span>AcceptEx</span></span><span>，接入后重复利用此</span><span><span>OVERLAPPED</span></span><span>再投递，这样即使管理大量连接也只有起初的几十个连接会分配</span><span><span> OVERLAPPED</span></span><span>后面的都是重复利用前面分配的结构，不会导致再度分配。</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span>IocpServer</span></span><span>还提供了一个虚函数</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual bool CanAccept(const char *pip, int port){return true;}</span></span></span></p>
<p><span><span>来管理是否接入某个</span><span><span>ip:port </span></span><span>的连接，如果不接入直接会关闭该连接并重复利用此前分配的</span><span><span>WSASocket</span></span><span>。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>五、资源管理</span></span></strong></p>
<p><span><span><span>Iocp</span></span><span>网络模块最难的就是这个了，什么时候客户端关闭或服务器主动关闭某个连接并收回资源，这是最难处理的问题，我尝试了几种做法，最后是采用计数器管理模式，具体做法是这样的：</span></span></p>
<p><span><span><span>CIocpClient</span></span><span>有</span><span><span>2</span></span><span>个计数变量</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>volatile long m_nSending;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>是否正发送中</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>volatile long m_nRef;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>发送接收关联字</span></span></p>
<p><span><span><span>m_nSending</span></span><span>表示是否有数据已</span><span><span>WSASend</span></span><span>中没有返回</span></span></p>
<p><span><span><span>m_nRef</span></span><span>表示</span><span><span>WSASend</span></span><span>和</span><span><span>WSARecv</span></span><span>有效调用未返回和</span></span></p>
<p><span><span>在合适的位置调用</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>inline void AddRef(const char *psource);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>inline void Release(const char *psource);</span></span></span></p>
<p><span><span>增引用计数和释放引用计数</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(InterlockedDecrement(&amp;m_nRef)&lt;=0)</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//glog.print("iocpclient %p Release %s ref %d\r\n", this, psource, m_nRef);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>m_server-&gt;DelClient(this);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span>当引用计数减少到</span><span><span>0</span></span><span>的时候删除客户端（其实是将内存返回给内存池）。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>六、锁使用</span></span></strong></p>
<p><span><span>锁的使用至关重要，多了效率低下，少了不能解决问题，用多少个锁在什么粒度上用锁也是这个模块的关键所在。</span></span></p>
<p><span><span><span>IocpClient</span></span><span>有一个锁</span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DECLARE_SIGNEDLOCK_NAME(send);<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>发送同步锁</span></span></p>
<p><span><span>这个锁是用来控制发送数据链管理的，该锁和前面提到的</span><span><span>volatile long m_nSending;</span></span><span>共同配合管理发送数据链。</span></span></p>
<p><span><span>可能有人会说</span><span><span>recv</span></span><span>怎么没有锁同步，是的，</span><span><span>recv</span></span><span>的确没有锁，</span><span><span>recv</span></span><span>不用锁是为了最大限度提高效率，如果和发送共一个锁则很多问题可以简化，但没有充分发挥</span><span><span>iocp</span></span><span>的效率。</span><span><span>Recv</span></span><span>接收数据后就调用</span><span><span>OnReceive</span></span><span>虚函数进行处理。可以直接</span><span><span>io</span></span><span>线程内部处理，也可以提交到某个队列由独立的逻辑线程处理。具体如何使用完全由使用者决定，底层不做任何限制。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>七、服务器定时器管理</span></span></strong></p>
<p><span><span>服务器定义了如下定时器函数，利用系统提供的时钟队列进行管理。</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>bool AddTimer(int uniqueid, DWORD dueTime, DWORD period, ULONG nflags=WT_EXECUTEINTIMERTHREAD);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>bool ChangeTimer(int uniqueid, DWORD dueTime, DWORD period);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>bool DelTimer(int uniqueid);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>获取</span><span><span>Timers</span></span><span>数量</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int GetTimerCount() const;</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>TimerIterator GetFirstTimerIterator();</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>TimerNode *GetNextTimer(TimerIterator &amp;it);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>bool IsValidTimer(TimerIterator it)</span></span></span></p>
<p><span><span>设计思路是给每个定时器分配一个独立的</span><span><span>id</span></span><span>，根据</span><span><span>id</span></span><span>可修改定时器的首次触发时间和后续每次触发时间，可根据</span><span><span>id</span></span><span>删除定时器，也可遍历定时器。定时器时间单位为毫秒。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>八、模块类结构</span></span></strong></p>
<p><span><span>模块中最重要的就是两个类</span><span><span>CIocpClient</span></span><span>和</span><span><span>CIocpServer</span></span><span>，其他有几个类从这两个类派生，图示如下：</span></span></p>
<table border=1 cellSpacing=0 cellPadding=0>
    <tbody>
        <tr>
            <td vAlign=top width=325>
            <p>&nbsp;</p>
            <p><span><span><img alt="" src="http://hi.csdn.net/attachment/201010/2/0_12860366862P0q.gif">图表</span><span> <span><span>1</span></span></span></span></p>
            </td>
            <td vAlign=top width=325>
            <p>&nbsp;</p>
            <p><span><span><img alt="" src="http://hi.csdn.net/attachment/201010/2/0_12860366934gcR.gif">图表</span><span> <span><span>2</span></span></span></span></p>
            </td>
        </tr>
    </tbody>
</table>
<p><span>&nbsp;</span></p>
<p><span><span><span>CIocpClient</span></span><span>是完全流式的，没有包概念。</span><span><span>CIocpMsgClient</span></span><span>从</span><span><span>CIocpClient</span></span><span>派生，内部支持包概念：</span></span></p>
<p><span><span>class CIocpMsgClient : public CIocpClient</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnDataTooLong(){};</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnMsg(PKHEAD *ph){};</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>bool SendMsg(WORD mtype, WORD stype, const char *pdata, int length);</span></span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span>};</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>template &lt;class TYPE&gt;</span></span></p>
<p><span><span>class CIocpMsgClientT : public CIocpMsgClient</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>void AddMsg(DWORD id, CBFN pfn);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>BOOL DelMsg(DWORD id);</span></span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span>};</span></span></p>
<p><span><span><span>CIocpMsgClientT</span></span><span>模板类支持内嵌入式定义，如在</span></span></p>
<p><span><span><span>CMyDoc</span></span><span>中可这样定义</span></span></p>
<p><span><span>CIocpMsgClientT&lt;CMyDoc&gt; client;</span></span></p>
<p><span><span>后面可以调用</span><span><span>client.AddMsg(UMSG_LOGIN, OnLogin);</span></span><span>关联一个类成员函数作为消息处理函数，使用很方便。</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span>CIocpServerT</span></span><span>定义很简单，从</span><span><span>CIocpServer</span></span><span>派生，重载了</span><span><span>CreateNewClient</span></span><span>函数</span></span></p>
<p><span><span>template &lt;class TClient&gt;</span></span></p>
<p><span><span>class CIocpServerT : public CIocpServer</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>public:</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>如果</span><span><span>CIocpClient</span></span><span>派生了则也需要重载下面的函数，这里可以根据</span><span><span>nServerPort</span></span><span>分配不同的</span><span><span>CIocpClient</span></span><span>派生类</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual CIocpClient *CreateNewClient(int nServerPort)</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CIocpClient *pclient = new TClient;</span></span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return pclient;</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span>};</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>八、应用举例</span></span></strong></p>
<p><span>&nbsp;</span></p>
<p><span><span>class CMyClient : public CIocpMsgClient</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>public:</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CMyClient() : CIocpMsgClient()</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual ~CMyClient()</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnConnect()</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span>Printf(&#8220;</span></span><span>用户连接</span><span><span>%s:%d</span></span><span>连接到服务器</span><span><span>\r\n&#8221;, GetPeerAddr().ip(),GetPeerAddr().port());</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnClose()</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span>Printf(&#8220;</span></span><span>用户</span><span><span>%s:%d</span></span><span>关闭连接</span><span><span>\r\n&#8221;, GetPeerAddr().ip(),GetPeerAddr().port());</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnMsg(PKHEAD *phead)</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SendData((const char *)phead, phead-&gt;len+PKHEADLEN);</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnSend(DWORD dwbyte)</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span>Printf(&#8220;</span></span><span>成功发送</span><span><span>%d</span></span><span>个字符</span><span><span>\r\n&#8221;, dwbyte);</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnInitialize()</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>m_sendbuf = newbuf(1024);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>m_recvbuf = newbuf(4096);</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>friend class CMyServer;</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>};</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>class CMyServer : public CIocpServer</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>public:</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CMyServer() : CIocpServer</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnConnect(CIocpClient *pclient)</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>printf("%p : %d </span></span><span>远端用户</span><span><span>%s:%d</span></span><span>连接到本服务器</span><span><span>.\r\n", pclient, pclient-&gt;m_socket, </span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>pclient-&gt;GetPeerAddr().ip(), pclient-&gt;GetPeerAddr().port());</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnClose(CIocpClient *pclient)</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>printf("%p : %d </span></span><span>远端用户</span><span><span>%s:%d</span></span><span>退出</span><span><span>.\r\n", pclient, pclient-&gt;m_socket, </span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>pclient-&gt;GetPeerAddr().ip(), pclient-&gt;GetPeerAddr().port());</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual void OnTimer(int uniqueid)</span></span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>If(uniqueid == 10)</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>}</span></span></p>
<p><span><span>Else if(uniqueid == 60)</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span>}</span></span></p>
<p><span><span>}</span></span></p>
<p><span><span><span>//</span></span><span>这里可以根据</span><span><span>nServerPort</span></span><span>分配不同的</span><span><span>CIocpClient</span></span><span>派生类</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>virtual CIocpClient *CreateNewClient(int nServerPort)</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>If(nServerPort == ?)</span></span></span></p>
<p><span><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CIocpClient *pclient = new CMyClient;</span></span></span></p>
<p><span><span>&#8230;</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return pclient;</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>};</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>Int main(int argc, char *argv[])</span></span></p>
<p><span><span>{</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CMyServer server;</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>server.AddTimer(60, 10000, 60000);</span></span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>server.AddTimer(10, 10000, 60000);</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span></span><span>第二个参数为</span><span><span>0</span></span><span>表示使用默认</span><span><span>cpu*2</span></span><span>个</span><span><span>io</span></span><span>线程，</span><span><span>&gt;0</span></span><span>表示使用该数目的</span><span><span>io</span></span><span>线程。</span></span></p>
<p><span><span><span>//</span></span><span>第三个参数为</span><span><span>0</span></span><span>表示使用默认</span><span><span>cpu*4</span></span><span>个逻辑线程，如果为</span><span><span>-1</span></span><span>表示不使用逻辑线程，逻辑在</span><span><span>io</span></span><span>线程内计算。</span><span><span>&gt;0</span></span><span>则表示使用该数目的逻辑线程</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>server.StartServer("1000;2000;4000", 0, 0);</span></span></span></p>
<p><span><span>}</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>从示例可看出，对使用该网络模块的人来说非常简单，只要派生两个类，集中精力处理消息函数即可，其他内容内部全部包装了。</span></span></p>
<p><span>&nbsp;</span></p>
<p><strong><span><span>九、后记</span></span></strong></p>
<p><span><span>我研究</span><span><span>iocp</span></span><span>大概在</span><span><span>2005</span></span><span>年初，前一个版本的网络模块是用多线程</span><span><span>+</span></span><span>异步事件来做的，</span><span><span>iocp</span></span><span>网络模块基本成型在</span><span><span>2005</span></span><span>年中，后来又持续进行了一些改进，</span><span><span>2005</span></span><span>底进入稳定期，</span><span><span>2006</span></span><span>年又做了一些大的改动，后来又持续进行了一些小的改进，目前该模块作为服务程序框架已经在很多项目中稳定运行了</span><span><span>1</span></span><span>年半左右的时间。在此感谢大宝、</span><span><span>Chost Cheng</span></span><span>、</span><span><span>Sunway</span></span><span>等众多网友，是你们的讨论给了我灵感和持续改进的动力，也是你们的讨论给了我把这些写出来的决心。若此文能给后来者们一点点启示我将甚感欣慰，若有错误欢迎批评指正。</span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>oldworm</span></span></p>
<p><span><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#111;&#108;&#100;&#119;&#111;&#114;&#109;&#64;&#50;&#49;&#99;&#110;&#46;&#99;&#111;&#109;"><span>oldworm@21cn.com</span></a></span></p>
<p><span><span>2007.9.24</span></span></p>
<img src ="http://www.cppblog.com/oldworm/aggbug/128473.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/oldworm/" target="_blank">袁斌</a> 2010-10-03 14:25 <a href="http://www.cppblog.com/oldworm/archive/2010/10/03/128473.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>定时器模块改造 </title><link>http://www.cppblog.com/oldworm/archive/2010/10/03/128470.html</link><dc:creator>袁斌</dc:creator><author>袁斌</author><pubDate>Sun, 03 Oct 2010 06:23:00 GMT</pubDate><guid>http://www.cppblog.com/oldworm/archive/2010/10/03/128470.html</guid><wfw:comment>http://www.cppblog.com/oldworm/comments/128470.html</wfw:comment><comments>http://www.cppblog.com/oldworm/archive/2010/10/03/128470.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/oldworm/comments/commentRss/128470.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/oldworm/services/trackbacks/128470.html</trackback:ping><description><![CDATA[<p><span><span>之前的文章讲过，我设计的网络框架有几组线程，分别是</span><span><span>io</span></span><span>、异步、同步、定时器，各个不同应用</span><span><span>server</span></span><span>几组线程组合形式不尽相同，简单的可只有</span><span><span>io</span></span><span>线程，复杂一点的可</span><span><span>io+</span></span><span>同步，更复杂一点的也可</span><span><span>io+</span></span><span>同步</span><span><span>+</span></span><span>异步</span><span><span>+</span></span><span>定时器，总之我以几组线程的自由组合方式应付各种应用，在我负责的</span><span><span>server</span></span><span>全是这一套框架实现的，不管是支持几万人连接的服务器，还是只有几个用户连接的内部服务器，这套框架也算是久经考验，稳定运行多年，内部使用也非常简单，如给</span><span><span>sync</span></span><span>线程组发一个消息只要</span><span><span>PostSyncEvent</span></span><span>，如果要给异步线程发一个消息只要发</span><span><span>PostAsyncEvent</span></span><span>，虽然只能开发的时候确定哪个任务在哪组线程执行，但修改还是非常方便的，执行体就是一组这样的函数：</span></span></p>
<p><span><span>OnSyncEvent(DWORD dwEvent, DWORD wParam, DWORD lParam);</span></span></p>
<p><span><span>OnAsyncEvent(DWORD dwEvent, DWORD wParam, DWORD lParam);</span></span></p>
<p><span><span>一眼就知道是在哪个线程组里面执行，当然有的线程组是一个线程，有的线程组是多个，这涉及到有的资源是不是要加锁，有经验的开发人员很容易理解。</span></span></p>
<p><span><span>说了一下框架才容易理解我的问题，之前定时器是一个独立的线程组，同步线程组、异步线程组、</span><span><span>io</span></span><span>组都没有定时器功能，定时器触发后要发送消息到相应线程组，有的要发给异步线程组，有的要发给同步线程组，这就会引起线程切换，这是问题之一，还有一个问题，之前的定时器是由</span><span><span>windows</span></span><span>的时钟队列实现的，这个定时器优点是很明显的，定时精确，功能强大，参数众多，独立线程组，但也有很明显的问题，如果要删除一个定时器则有线程依赖，就是要在定时器线程才能删除定时器，这个依赖约束很大，也很容易引起问题，用起来很不方便，使得一些资源的释放不能够即时进行。正因为有这么些问题，也为了使得时钟模块更容易移植，我设计了一个新时钟模块，为实现以下目标：</span></span></p>
<p><span><span><span>1</span></span><span>、无线程依赖，随便调用者在哪个线程调用都可删除指定的定时器。</span></span></p>
<p><span><span><span>2</span></span><span>、和事件消息集成在一个线程内，实现无需切换的定时器功能，这样主线程、同步线程组、异步线程组都可在内部处理定时器消息，无需单独的定时器线程辅助，方便很多。</span></span></p>
<p><span><span>为实现以上目标，我引入了</span><span><span>libevent</span></span><span>里面的</span><span><span>minheap</span></span><span>管理定时器，并根据之前管理事件的处理办法，继续使用</span><span><span>iocp</span></span><span>队列管理线程消息，在每个线程组用</span><span><span>iocp</span></span><span>管理事件，根据最短触发的定时器计算</span><span><span>wait</span></span><span>时间，这样就在同一组线程内实现了定时器和事件合并处理，当然实现方法有很多，也可用</span><span><span>iocp+WaitableTimer</span></span><span>等，也可用</span><span><span>apc</span></span><span>，但那些实现的</span><span><span>windows</span></span><span>烙印都太深刻，虽然精度更高，实现更容易，我用</span><span><span>minheap+iocp</span></span><span>队列方式的实现相对来说对</span><span><span>windows</span></span><span>的依赖较少，因为替换一个</span><span><span>iocp</span></span><span>队列处理事件是很容易的，这样也方便移植和复用代码。经这样修改之后，各个线程组包括主线程都可处理定时器和事件消息，也使得以前鸡肋式的主线程终于可当同步线程发挥作用，以前的定时器线程组也不一定需要了，既减少了线程，也减少了切换，现在各个线程组（包括主线程）都有完全一致的消息处理和时钟处理函数。</span></span></p>
<p><span><span>事件函数：</span></span></p>
<p><span><span>OnTimerEvent(DWORD dwEvent, DWORD wParam, DWORD lParam);</span></span></p>
<p><span><span>OnSyncEvent(DWORD dwEvent, DWORD wParam, DWORD lParam);</span></span></p>
<p><span><span>OnAsyncEvent(DWORD dwEvent, DWORD wParam, DWORD lParam);</span></span></p>
<p><span><span>OnServiceEvent(DWORD dwEvent, DWORD wParam, DWORD lParam);</span></span></p>
<p><span><span>定时器函数：</span></span></p>
<p><span><span>OnTimerTimer(TlsInfo *ptls, EventTimer *et);</span></span></p>
<p><span><span>OnSyncTimer(TlsInfo *ptls, EventTimer *et);</span></span></p>
<p><span><span>OnAsyncTimer(TlsInfo *ptls, EventTimer *et);</span></span></p>
<p><span><span>OnIoTimer(TlsInfo *ptls, EventTimer *et);</span></span></p>
<p><span><span>OnServiceTimer(TlsInfo *ptls, EventTimer *et);</span></span></p>
<p><span><span>可以给线程组增加定时器删除定时器</span></span></p>
<p><span><span><span>AddTimer</span></span><span>、</span><span><span>AddSyncTimer</span></span><span>、</span><span><span>AddAsyncTimer</span></span><span>、</span><span><span>AddServiceTimer</span></span><span>、</span><span><span>AddIoTimer</span></span></span></p>
<p><span><span><span>DelTimer</span></span><span>、</span><span><span>DelSyncTimer</span></span><span>、</span><span><span>DelAsyncTimer</span></span><span>、</span><span><span>DelServiceTimer</span></span><span>、</span><span><span>DelIoTimer</span></span></span></p>
<p><span><span>可给各线程组发消息</span></span></p>
<p><span><span><span>PostTimerEvent</span></span><span>、</span><span><span>PostSyncEvent</span></span><span>、</span><span><span>PostAsyncEvent</span></span><span>、</span><span><span>PostServiceEvent</span></span></span></p>
<p><span>&nbsp;</span></p>
<p><span><span>这套框架是我多年服务器端开发的得意之作，体现了我简洁实用的设计思想，用起来非常方便，可任意组合，适应各种需求的应用，由于除主线程之外的</span><span><span>io</span></span><span>线程组、同步线程组、异步线程组、定时器线程都是可以关、开</span><span><span>1</span></span><span>个、开多个，所以组合非常灵活，开</span><span><span>1</span></span><span>个可当同步线程，开多个可当异步线程（内部抢资源），关闭就不存在该组线程，即使是</span><span><span>io</span></span><span>线程组也是可关的，这样就使得这套框架不仅仅用在标准</span><span><span>server</span></span><span>上，就算是当作一般的消息队列服务器也没问题，高度的灵活性使得这套框架可适应各种规模的应用，这次对定时器的改造使得这种组合更灵活，虽然现在的实现方法定时器的精度有一些下降，但瑕不掩瑜，这样改造之后功能无疑是更强大了。</span></span></p>
<img src ="http://www.cppblog.com/oldworm/aggbug/128470.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/oldworm/" target="_blank">袁斌</a> 2010-10-03 14:23 <a href="http://www.cppblog.com/oldworm/archive/2010/10/03/128470.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>