??xml version="1.0" encoding="utf-8" standalone="yes"?>
之前几天说过Q因为经?/span>iocp实现Q以下简U经典实玎ͼ多个ioU程l定在一?/span>iocp上,q样内部理?/span>iocp队列的处理,内部军_是不是需要线E切换,我上ơ修改的一个版本(以下U实?/span>2Q,用了多个ioU程Q每?/span>iocp队列仅绑定一?/span>ioU程Q一l用户共享一?/span>ioU程Q这和经典的多线E?/span>epoll模型的做法是很相似的Q这h?/span>ioU程是可以独立控制了Q但理论上这U做法没有发?/span>iocp自动理U程切换的优势,昨晚没事用这两种实现分别做了?/span>echoserver试了一下,q两套实C码仅40行左右不同,其他完全一P效果真的是差很多Q测试仅用一个进E模拟了4000个客LQ每U?/span>1个包Q先看实?/span>2的,cpu?/span>14%Q?/span>2?/span>ioU程Q?/span>1?/span>acceptU程Q?/span>1个主U程Q其他线E都没干z闲|?/span>
Cpu |
Memory |
Threads |
handles |
14 |
40088k |
8 |
4236 |
Cpu |
Memory |
Threads |
handles |
0 |
39244k |
7 |
4336 |
说实话,在测试之前我也没惛_有这么大的差距,l典实现是1.2w个连接连上来q是q样Q就是内存占用多一点:
Cpu |
Memory |
Threads |
handles |
0 |
112068k |
7 |
12280 |
习惯上L人喜Ƣ拿epoll?/span>iocp来对比,我到现在也没看到真正公^的对比,q是相对公q的也没见到Q因为在我看来,要对比硬件应该是一LQ?/span>os都应该是最新的Q最重要的是Q?/span>server端程序应该都是发挥了各自优势的,如果拿我q里的实?/span>2M?/span>iocp的水q_epollҎQ势必造成?/span>epoll差很多的l果Q然而这昄是不正确的?/span>
epolll典多线E模式实际实现和实现2很相|理论上也有类似的U程切换问题Q不知道效率怎样?/span>
回调函数实在是用得太q泛Q回调函数又有多U实现方式,如:
1?span style="font:7.0pt "Times New Roman""> 静态函?/span>
2?span style="font:7.0pt "Times New Roman""> 虚函?/span>
3?span style="font:7.0pt "Times New Roman""> 函数对象
4?span style="font:7.0pt "Times New Roman""> 传统c函数Q通过一?/span>void *传递对象地址Q内部强制{?/span>
5?span style="font:7.0pt "Times New Roman""> fastdelegate
6?span style="font:7.0pt "Times New Roman""> Tr1::function + bind
7?span style="font:7.0pt "Times New Roman""> Boost::Function + bind
基本上速度是按照由快到慢的序排列的,是
1 > 2 > 3 > 4 > 5 > 6 > 7
其实234速度很接q,有的时候函数对象效率更高一点,基本上越是高U的Ҏ使用h方便,但速度慢Q越是传l的Ҏ速度快Q呵呵,看来?/span>server端程序要l合考虑效率太新的东西还是要用啊,q是用传l的Ҏ比较靠谱一点,当然如果调用ơ数不多的地方,使用更方便的Ҏq是好一些,毕竟我们要综合权衡,而不能死板恪守教条?/span>
之前设计了一套网l框Ӟ持箋改进了很多年Q用在很多目上,l合效率q行Q也很稳定,一直以来对q套东西信心满满QM为啥问题都好解决Q但最q就有个需求让我选择q是改了下这个框架?/span>
之前的框架是q样的,可以开一l?/span>N?/span>ioU程Q可以开一l?/span>N个同步线E(默认1个)Q可以开一l?/span>N个异步线E(默认1个)Q可以开一l?/span>N?/span>timerU程Q默?/span>1个)Q可以开一l?/span>N个异步线E(默认cpu个)Q每l可独立受控Q每l可支持自定义消息,可支?/span>timerQ一l?/span>N个如?/span>N大于1则无法直接给q组里面的特定线E发消息Q只能给一l发消息Q这个组里面会选择某个合适的U程处理q个消息Q这也是iocp高效和典型的用法了,但这也正是问题的l症所在?/span>
Linux下的多线E服务器更常见的做法跟这个不大相|一般都是将某些socket分配到某些线E?/span>epollQ分好之后就是固定的Q不再变化,?/span>Iocp?/span>socketl定Cl线E的做法不同Q由于某?/span>socket直接l定C某个U程Q所以有些问题就变得单了Q如同一个连接的在同一个线E内消息q行了同步,要跟ioU程l定U有化数据也单了Q而且每个U程可独立受控,所以很Ҏ实现一l?/span>io各自?/span>tlsQ线E局部存储)数据Q而我现在做的q套框架是q方面不好控了,其实也很难说q两U意义上的框架到底谁更优Q如用在web型应用上q种socket被一l?/span>ioU程理的模式很方便效率也高Q但我现在的需求需要某?/span>socket使用U程相关数据Q以避免数据之间的锁Q我用内存换旉Q由于在原来的框架上增逻辑难以实现可直接控?/span>ioU程的框架的Q所以花了一个晚上重新改写了一套框Ӟ在原?/span>iocpframe的基上派生了一l带2名称的类Q除替换cd之外只修改了几十行代码就做好了,ȝ来说q旉q是比较的。修改后ioU程一l,但独立受控,外部可对q组U程中的某一个直接发消息Q基本满了需求,现在要给每个ioU程l定U有数据q触发特定消息比之前单多了,而且l对无锁?/span>
׃原先?/span>appserver功能不断增多Q最q又增了两个功能Q需要不断从后端memcached中提取数据ƈq行计算Q由于提取数据量大且频繁Q导致效率很低,_测了一下,获取数据和格式化{操作花?/span>90%以上的时_由此设想?/span>memcached改写或重写一个支?/span>memcached的服务器Q将计算功能?/span>memcached做到一P让获取数据的路径最短,也就最大限度减了数据传输和格式化{操作,是cM存储q程一样啦Q这部分可以考虑使用插g来实玎ͼ甚至可考虑使用脚本语言来实现?/span>
|上搜了一下,果然发现早有么干了,正所谓英雄所见啊Q呵c具体方法倒很多,自定?/span>key命名Q根据特D?/span>key?/span>get?/span>set?/span>replace上做Ҏ操作Q或者根据命令中?/span>flag{做Ҏ处理Q或者扩?/span>stat命o{,都是可以的,我们暂时p虑修改Ҏ的键值做Ҏ处理?/span>
要做一个完备的既支?/span>ascii命o又支?/span>binary命o的兼?/span>memcachedq是有一点点ȝ的,我暂时也没有太多需求,所以就仅支持了ascii命oQ其实也是考虑支持ascii的客L更多Q各U语a的支?/span>mc的客LC胜数Q但大多只支?/span>ascii命o。由于我之前Z试服务器框架效率,做过一个支?/span>ascii命o?/span>memcached兼容版本Q因此拿q来直接使用太方便了Q这个版本的实现其实很容易,如果有一个较好的框架代码的话基本上在一天之内可做完Q当然要做到很好可能需要多׃些时_我现在做的也不是特好Q要完全取代memcached使用q是有些差距Q主要是一些过期机制等没完全实玎ͼ虽然速度上比标准mc版本q要快一点,呵呵Q因为暂时的是不需要这些过期机Ӟ所以也没打这个版本实玎ͼ其他功能基本上都有?/span>
以后准备这?/span>memcached解码部分作ؓ一个单独的解析器,和支持其他协议一P换上q个解析那就支持mc协议了,q是很方便的Q以后有I是要做个支持binary协议的,以便可以更高效的解决问题?/span>
惛_server能支?/span>Memcached协议真是好啊Q客L基本只要用个libmemcached好了,多服务器分布Q容错,多䆾数据啥的都有现成的解x案,只要?/span>server做稳定了基?/span>ok了,对咱q种团队来说再合适不q了Q节省了很多开发维护成本啊Q现在内存这么便宜,部v几个点实在是?/span>easy的问题?/span>
最q要一些数据放到内存里面做很高的ƈ发操作,考虑了很多方案,
1?nbsp;单点使用map hash_map{自q理?/span>
2?nbsp;?/span>sqlite内存表?/span>
3?nbsp;?/span>fastdb内存数据库?/span>
4?nbsp;?/span>ExtremeDbQ?/span>TimesTen{?/span>
比较试了一?/span>123Q发现还是自己实现速度最快,?/span>fastdb模式?/span>3-5倍,fastdb模式?/span>sqlite内存表模式快10倍左叻I׃自己实现不具有典型通用性,多线E下讉K效率会下降,要管理多U程下各U更新查扄q是比较ȝ的,所以在1?/span>3Ҏ之间U结?/span>
Z使得决策更好一些,暂时q没做决定,ZC方等上面搜烦了一些论文来看,看来看去看得真来气啊Q虽焉叫内存数据库但各U实现的都有Q有?/span>gdbm来做的,有直?/span>map理的,?/span>hash理数据的,?/span>t树管理的Q有数组队列理的,有的明显是个不大变的东西还弄个啥事务的Q靠Q刚刚居然还看到一鸟文《电|监控系l实时数据库的设计与实现》里面的试居然?/span>1000条,插入旉80毫秒Q真可笑啊,区区q么Ҏ据也好意思测Q还要花80毫秒Q还自以为很快,q个速度臛_可提?/span>1000倍以上啊Q这帮垃圾,写的啥鸟文章Q研I个屁啊?/span>
看完q十来篇论文Q俺的思A又回?/span>1999q_当年我给别h优化q一个电信计费的软gQ看的论文里面有好几讲电信计费的)Q当时有个朋友的朋友拿了个需求过来,7000万条记录Q原来计费单要花十几个时吧,我帮他改了下Q十来分钟就完了,朋友很满意,当时的做法很单,是弄了?/span>mmtableQ大体就是跟mapcM的东西吧Q那个时?/span>mapq没行hQ俺也不知道Q所以就自己弄了个内存表Q内部基本就是二分查找了Q那个时候我?/span>hash都不大熟悉,B树之cȝ法刚接触也不会用,p么个东西当时的电脑也只要花十来分钟,我估计就是那个老程序放在现在的普通台式机上要不了几秒钟就可算完。也不知道这么几千万条记录的需求怎么在这帮h眼里成了什么v量数据,对俺来说跟玩似的Q区区几千万嘛,不过是俺拿来试用的?/span>
d中做了个md5 hash反查的东西,数据都是几百亿到几万亿的Q后来的效果是一个文件可存万亿记录,一ơ查询^?/span>1.2?/span>IOQ即使全攑֜SATA盘上也十来毫U而已?/span>
区区几千万条记录咋就叫什么v量数据呢Qv量个毛啊Q内存都攑־下的叫什么v量,现在服务器动不动都是几十G内存Q区区千万根本算不上什么,查询定位都可到微妙了Q?/span>1U插入至千万条了,居然q看?/span>1000条插入的试Q真是不得不佩服国内q帮垃圾研究生的水^Q也不知道这U论文咋p通过审查Q只能得出结Z们的老师也都是猪?/span>
骂归骂自q问题q需要l努力,对咱目前的需求来说自q理数据,即一个线E都搞得定,因ؓ不过区区几个表,几十万条记录而已Q不q这U?/span>10q前咱就会的技术还真是拿不出手Q怎么的也得做得更好一点,呵呵Ql研I吧Q多U程下内存数据库Q从概念上看的确是个很有吸引力的东西Q要是性能跟得上,其实在很多地方可以取代普通的数据l构用法了,可以大大减少~程隑ֺQ甚x在想如果有个支持事务的内存数据库Q之前设计的cadcY件的undo/redo都可以用事务来实玎ͼ完全可以抛弃先前设计的复杂结构,其实q种东西即不用内存数据库就是用个sqlite都完全能搞定Q唉Q往事不堪回首啊Q看来数据库斚w的确得多花功夫,特别是多U程和分布式模式下的内存数据库?/span>
最q给自己换了个老板Q忙了一D|_所以有几个月没写博客,今后q是要争取多写啊Q呵c?/span>
换来新地方,W一件大的事情就是修改后端架构和通信协议Q架构也设计得很普通,因ؓq边的业务不需要太q复杂的后端Q所以就单设计了一下,基本是参?/span>web的模型,W合我一贯的?/span>web学习的思想Q弄了个gate理入口Q相当于web下的webserverQ后端其他服务器挂在?/span>gate下,相当?/span>web模型下的appserverQ或?/span>fastcgi模型?/span>fastcgiq程Q?/span>gate上管理连接、合法性检、登录、加密、压~、缓存?/span>Gate和后端通信本来惛_?/span>fastcgi协议Q但看了之后觉得fastcgi协议q是复杂了,所以就设计了一个更单的协议Q?/span>gate和后?/span>server之间可传?/span>key:value型数据对Q?/span>value不局限于字符Ԍ可以是Q意数据,q样基本满了当前的需求,W一版放上去之后也运行良好,C天也基本持箋E_q行快一个月了,没出q什么事情。由于在gateq边~冲?/span>job理Q所以后?/span>server升很方便,随时可关闭更斎ͼgate会在H口旉内将未执行完成的d重新提交Q有此功能可攑ֿ大胆的升U后端,q个月这L工作做了几次Q在架构修改之前q样的事情几乎是不敢做的Q因Z旦升U所有用户全部断开q接Q而现在用户则基本无感觉?/span>Gate上的~存层ؓ后端减少了一些压力,q个~存是按照请求的md5?/span>key做的QƈҎ协议配置时效Q有?/span>cache后端大多数服务可不设计缓存或降低~存设计的复杂度?/span>Gate上针Ҏ感数据统一做了加密处理Q主要是辛辛苦苦整理的数据不能轻易让竞争ҎH去了,呵呵?/span>Gate也做了压~,现在是针?/span>>=128长度的包q行压羃Q用了qlzQ压~效率还是很不错的,速度很快。目?/span>gate后端挂接的既?/span>win上的server也有linux上的serverQ这是一开始就q么规划的,现在看来当初的目的达CQ合发挥各自的优势Q有的项目在原有pȝ上跑得好好的Q没必要重新开发嘛?/span>
协议设计上本来我是计划二q制混合json格式Q以二进制ؓ主,但尝试了一个协议之后发玎ͼq边的小伙子们对直接操纵内存普遍技术不q关Q他们大多是?/span>java开始的Q后来才学习cQ对字符串用得很熟练Q权衡之下采用了jsonZQ合二q制的方案,q样修改之后的协议和他们之前使用?/span>xmlcMQ就是更更紧凑一点,使用Ҏ上很cMQ从现在的效果看q行Q?/span>json格式Z的协议当然不能跟使用pb之类的相比,解析效率上大U单U程每秒解析20来万10?/span>obj的对象,速度上不太快但也不太慢,对付一U至多几万数据包的应用来说还是够的,因ؓ现在cpu计算能力普遍q剩Q?/span>json的另个好处就是增删字D很方便Q各个版本之间不需要太考虑版本的问题,要是全用二进制格式就要麻烦很多了Q在使用压羃之后Q目前的json格式协议比之前的xml协议减少?/span>2/3的带宽用,M效果q是可以的。?/span>json调试也很方便Q我提供了一个工P写后端的q接用该工h?/span>json格式收发数据Q无需{?/span>client开发好了再d后端Q之后做client也很方便Q请求发q去之后q回来的是标准?/span>json格式数据Q同L解析ҎQ每个不同的应用按照不同的格式处理下即可,?/span>web{模块交互也很方便,q可是额外的好处了?/span>
MQ虽?/span>json格式存储效率和解析效率跟二进制方式还差半个量U到一个量U,但合理用还是可以的Q特别是?/span>xml相比优势很明显,权衡使用吧,当然q求极致效率可能q是?/span>pb之类的更合适一些,或者自p?/span>tlv格式?/span>
可能我n边也合作伙伴、曾l的老板、同事、敌人知道我到底什么水qI2000q的时候就带队做几十万行的目Q连l做了几个,Z们申误Y件企业奠定基。云计算的h格查询,3周完成,?/span>2个客L的棋牌游戏带了两个朋友一起半q完成,全部模块接口化,模块可单独升U?/span>NetdongleQ一?/span>master?/span>slave多重保护的网l验证系l,支持由控制端上传dll?/span>db{,也就一个月完成。这些程序上U之后就几乎不用修改Q一直稳定运行哦Q一般的E序要做到第一版版本出d几乎不出错是很难的,没有一定功力的人是做不到的?/span>
昨天吹了下牛Q我?/span>windows下应用层的Y件基本没有做不出来的Q或许牛吹得有点大,那朋友惊讶了一下?/span>
今年湿疹d之后发现自己战斗力提升很多,之前做事情总觉得差一口气Q精力不,现在觉得_֊充沛Q酒也能喝了Q活也能q了Q速度也快了,也敢出去跟别Z了Q之前一直自卑,没病真好啊?/span>
以后要多宣传Q多吹牛啊!
Z设计一个稳定易用高效的iocp|络模块Q我前前后后׃好几个月的时_也曾阅读q网上很多资料和代码Q但是非帔R憾,能找到的资料一般都说得很含p,很少有具体的Q能扑ֈ的代码离真正能商用的|络模块差得太远Q大多只是演CZ下最基本的功能,而且大多是有很多问题的,主要问题如下Q?/span>
1?/span> 很多代码没有处理一ơ仅发送成功部分数据的情况?/span>
2?/span> 几乎没有扑ֈ能正管理所有资源的代码?/span>
3?/span> 大多没有采用?/span>poolQ有的甚至画蛇添用什?/span>map查找对应客户端,没有充分使用perhandle, perio?/span>
4?/span> 接收发送数据大多拷贝太多次数?/span>
5?/span> 接收理大多很低效,没有充分发挥iocp能力?/span>
6?/span> 几乎都没有涉及上层如何处理逻辑Q也没有提供相应解决ҎQ如合ƈioU程处理或单独逻辑U程Q?/span>
7?/span> 大多没有分离数据和包数据?/span>
…
问题q有很多Q就不一一列出来了Q有一定设计经验的人应该有同感。要真正解决q些问题也不是那么容易的Q特别是?/span>win下用iocp的时候资源释放是个麻烦的问题Q我在资源管理上׃很多旉Qv初也犯了很多错误Q后来在减少同步对象上又׃不少旉Qv?/span>client用了两个同步对象Q后来减ؓ1个)。下面我我所设计的网l模块的各个部分q行单的讲解
一、内存管理?/span>
内存理是采用池模式Q设计了一个基池类Q可以管理某固定大小的池
class CBufferPool
{
…
void *newobj();
void delobj(void *pbuf);
…
};
在基池类上提供了一个模板的对象?/span>
template <class T>
class CObjPool : public CBufferPool
{
public:
T *newobj()
{
void *p = CBufferPool::newobj();
T *pt = new(p) T;
return pt;
}
void delobj(T* pt)
{
pt->~T();
CBufferPool::delobj(pt);
}
};
在基池的基础上定义了一个简单的通用?/span>
class CMemoryPool
{
private:
CBufferPool bp[N];
…
};
通用池是?/span>N个不同大的基础池组成的Q分配的时候圆整到合适的相近基础池ƈ由基池分配?/span>
最后还提供了一个内存分配适配器类Q从该类z的类都支持内存池分配?/span>
class t_alloc
{
public:
static void *operator new(size_t size)
{
return CMemoryPool::instance().newobj(size);
}
static void operator delete(void *p, size_t size)
{
CMemoryPool::instance().delobj(p, size);
}
};
Ҏ试CMempool分配速度?/span>CObjpool<>E微慢一点点Q所以我在用的时候就直接?/span>t_alloccL生,而不是用对象池,q是个风格问题,也许有很多h喜欢用更高效一点的objpool方式Q但q个q不大碍?/span>
在网l模块中OVERLAPPEDzcd要用池进行分配,q有CIocpClient也要用池分配Q再是CBlockBuffer也是从池分配的?/span>
如下定义Q?/span>
struct IOCP_ACCEPTDATA : public IOCP_RECVDATA, public t_alloc
class CIocpClient : public t_alloc
二、数据缓冲区?/span>
数据~冲?/span>CBlockBuffer为环形,大小不固定,随便分配多少Q主要有以下几个元素Q?/span>
Char *pbase; //环Ş首部
Char *pread; //当前L?/span>
Char *pwrite; //当前写指?/span>
Int nCapacity; //~冲区大?/span>
Long nRef; //兌计数?/span>
用这UŞ式管理缓冲区有很多好处,发送数据的时候如果只发送了部分数据只要修改pread指针卛_Q不用移动数据,接收数据q处理的时候如果只处理了部分数据也只要修改pread指针卛_Q有新数据到辑直接写到pwriteq修?/span>pwrite指针Q不用多ơ拷贝数据?/span>nRef兌计数q可处理一个包发给N个h的问题,如果要给N个h发送相同的包,只要分配一个缓冲区Qƈ讄nRef?/span>N可以不用复?/span>N份?/span>
三、收发缓冲区理
发送缓冲区
我把CIocpClient的发送数据设计ؓ一?/span>CBlockBuffer 的队列,如果队列内有多个?/span>WSASend的时候一ơ发送多个,如果只有一个则仅发送一个,CIocpClient发送函数提供了两个Q分别是Q?/span>
Bool SendData(char *pdata, int len);
Bool SendData(CBlockBuffer *pbuffer);
W一个函C发送链的最后一个数据块能否容纳发送数据,如果能复制到最后一个块Q如果不能则分配一?/span>CBlockBuffer挂到发送链最后面Q当然这个里面要处理同步?/span>
接收~冲?/span>
接收理是比较简单的Q只有一?/span>CBlockBufferQ?/span>WSARecv的时候直接指?/span>CBlockBuffer->pwriteQ所以如果块大小合适的话基本上是不用拼包的Q如果一ơ没有收C个完整的数据包,q且块还有够空间容U_余空_那么再提交一?/span>WSARecv让v始缓冲指?/span>CBlockBuffer->pwrite如此则收C个完整数据包的过E都不用重新拼包Q收C个完整数据包之后可以调用虚函数让上层q行处理?/span>
?/span>IocpClient层其实是不支持数据包的,在这个层ơ只有流的概念,q个后面会专门讲解?/span>
四?/span>IocpServer的接入部分管?/span>
我把IocpServer设计为可以支持打开多个监听端口Q对每个监听端口接入用户后调?/span>IocpServer的虚函数分配客户端:
virtual CIocpClient *CreateNewClient(int nServerPort)
分配客户端之后会调用IocpClient的函?/span> virtual void OnInitialize();分配内部接收和发送缓冲区Q这样就可以Ҏ来自不同监听端口的客L分配不同的缓冲区和其他资源?/span>
Accept其实是个可以有很多选择的,最单的做法可以用一个线E?/span>+acceptQ当然这个不是高效的Q也可以采用多个U程的领D?/span>-q随者模?/span>+accept实现Q还可以是一个线E?/span>+WSAAcceptQ或者多个线E的领导?/span>-q随者模?/span>+WSAAccept模式Q也可以采用AcceptEx模式Q我是采?/span>AcceptEx模式做的Q做法是有接入后投递一?/span>AcceptExQ接入后重复利用?/span>OVERLAPPED再投递,q样即ɽ理大量q接也只有v初的几十个连接会分配 OVERLAPPED后面的都是重复利用前面分配的l构Q不会导致再度分配?/span>
IocpServerq提供了一个虚函数
virtual bool CanAccept(const char *pip, int port){return true;}
来管理是否接入某?/span>ip:port 的连接,如果不接入直接会关闭该连接ƈ重复利用此前分配?/span>WSASocket?/span>
五、资源管?/span>
Iocp|络模块最隄是q个了,什么时候客L关闭或服务器d关闭某个q接q收回资源,q是最隑֤理的问题Q我试了几U做法,最后是采用计数器管理模式,具体做法是这LQ?/span>
CIocpClient?/span>2个计数变?/span>
volatile long m_nSending; //是否正发送中
volatile long m_nRef; //发送接收关联字
m_nSending表示是否有数据已WSASend中没有返?/span>
m_nRef表示WSASend?/span>WSARecv有效调用未返回和
在合适的位置调用
inline void AddRef(const char *psource);
inline void Release(const char *psource);
增引用计数和释放引用计数
if(InterlockedDecrement(&m_nRef)<=0)
{
//glog.print("iocpclient %p Release %s ref %d\r\n", this, psource, m_nRef);
m_server->DelClient(this);
}
当引用计数减到0的时候删除客LQ其实是内存返回给内存池)?/span>
六、锁使用
锁的使用臛_重要Q多了效率低下,了不能解决问题Q用多少个锁在什么粒度上用锁也是q个模块的关键所在?/span>
IocpClient有一个锁 DECLARE_SIGNEDLOCK_NAME(send); //发送同步锁
q个锁是用来控制发送数据链理的,该锁和前面提到的volatile long m_nSending;共同配合理发送数据链?/span>
可能有h会说recv怎么没有锁同步,是的Q?/span>recv的确没有锁,recv不用锁是Z最大限度提高效率,如果和发送共一个锁则很多问题可以简化,但没有充分发?/span>iocp的效率?/span>Recv接收数据后就调用OnReceive虚函数进行处理。可以直?/span>ioU程内部处理Q也可以提交到某个队列由独立的逻辑U程处理。具体如何用完全由使用者决定,底层不做M限制?/span>
七、服务器定时器管?/span>
服务器定义了如下定时器函敎ͼ利用pȝ提供的时钟队列进行管理?/span>
bool AddTimer(int uniqueid, DWORD dueTime, DWORD period, ULONG nflags=WT_EXECUTEINTIMERTHREAD);
bool ChangeTimer(int uniqueid, DWORD dueTime, DWORD period);
bool DelTimer(int uniqueid);
//获取Timers数量
int GetTimerCount() const;
TimerIterator GetFirstTimerIterator();
TimerNode *GetNextTimer(TimerIterator &it);
bool IsValidTimer(TimerIterator it)
设计思\是给每个定时器分配一个独立的idQ根?/span>id可修改定时器的首ơ触发时间和后箋每次触发旉Q可Ҏid删除定时器,也可遍历定时器。定时器旉单位为毫U?/span>
八、模块类l构
模块中最重要的就是两个类CIocpClient?/span>CIocpServerQ其他有几个cMq两个类zQ图C如下:
|
|
CIocpClient是完全流式的Q没有包概念?/span>CIocpMsgClient?/span>CIocpClientzQ内部支持包概念Q?/span>
class CIocpMsgClient : public CIocpClient
{
…
virtual void OnDataTooLong(){};
virtual void OnMsg(PKHEAD *ph){};
bool SendMsg(WORD mtype, WORD stype, const char *pdata, int length);
…
};
template <class TYPE>
class CIocpMsgClientT : public CIocpMsgClient
{
…
void AddMsg(DWORD id, CBFN pfn);
BOOL DelMsg(DWORD id);
…
};
CIocpMsgClientT模板cL持内嵌入式定义,如在
CMyDoc中可q样定义
CIocpMsgClientT<CMyDoc> client;
后面可以调用client.AddMsg(UMSG_LOGIN, OnLogin);兌一个类成员函数作ؓ消息处理函数Q用很方便?/span>
CIocpServerT定义很简单,?/span>CIocpServerzQ重载了CreateNewClient函数
template <class TClient>
class CIocpServerT : public CIocpServer
{
public:
//如果CIocpClientz了则也需要重载下面的函数Q这里可以根?/span>nServerPort分配不同?/span>CIocpClientzc?/span>
virtual CIocpClient *CreateNewClient(int nServerPort)
{
CIocpClient *pclient = new TClient;
…
return pclient;
}
};
八、应用D?/span>
class CMyClient : public CIocpMsgClient
{
public:
CMyClient() : CIocpMsgClient()
{
}
virtual ~CMyClient()
{
}
virtual void OnConnect()
{
Printf(“用户q接%s:%dq接到服务器\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());
}
virtual void OnClose()
{
Printf(“用户%s:%d关闭q接\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());
}
virtual void OnMsg(PKHEAD *phead)
{
SendData((const char *)phead, phead->len+PKHEADLEN);
}
virtual void OnSend(DWORD dwbyte)
{
Printf(“成功发?/span>%d个字W?/span>\r\n”, dwbyte);
}
virtual void OnInitialize()
{
m_sendbuf = newbuf(1024);
m_recvbuf = newbuf(4096);
}
friend class CMyServer;
};
class CMyServer : public CIocpServer
{
public:
CMyServer() : CIocpServer
{
}
virtual void OnConnect(CIocpClient *pclient)
{
printf("%p : %d q端用户%s:%dq接到本服务?/span>.\r\n", pclient, pclient->m_socket,
pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());
}
virtual void OnClose(CIocpClient *pclient)
{
printf("%p : %d q端用户%s:%d退?/span>.\r\n", pclient, pclient->m_socket,
pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());
}
virtual void OnTimer(int uniqueid)
{
If(uniqueid == 10)
{
}
Else if(uniqueid == 60)
{
}
}
//q里可以ҎnServerPort分配不同?/span>CIocpClientzc?/span>
virtual CIocpClient *CreateNewClient(int nServerPort)
{
// If(nServerPort == ?)
// …
CIocpClient *pclient = new CMyClient;
…
return pclient;
}
};
Int main(int argc, char *argv[])
{
CMyServer server;
server.AddTimer(60, 10000, 60000);
server.AddTimer(10, 10000, 60000);
//W二个参Cؓ0表示使用默认cpu*2?/span>ioU程Q?/span>>0表示使用该数目的ioU程?/span>
//W三个参Cؓ0表示使用默认cpu*4个逻辑U程Q如果ؓ-1表示不用逻辑U程Q逻辑?/span>ioU程内计?/span>>0则表CZ用该数目的逻辑U程
server.StartServer("1000;2000;4000", 0, 0);
}
从示例可看出Q对使用该网l模块的人来说非常简单,只要z两个c,集中_֊处理消息函数卛_Q其他内容内部全部包装了?/span>
九、后?/span>
我研I?/span>iocp大概?/span>2005q初Q前一个版本的|络模块是用多线E?/span>+异步事g来做的,iocp|络模块基本成型?/span>2005q中Q后来又持箋q行了一些改q,2005底进入稳定期Q?/span>2006q又做了一些大的改动,后来又持l进行了一些小的改q,目前该模块作为服务程序框架已l在很多目中稳定运行了1q半左右的时间。在此感谢大宝?/span>Chost Cheng?/span>Sunway{众多网友,是你们的讨论l了我灵感和持箋改进的动力,也是你们的讨论给了我把这些写出来的决心。若此文能给后来者们一点点启示我将甚感ƣ慰Q若有错误欢q批评指正?/span>
oldworm
2007.9.24
实施一个成功的it目Q只说技术不说市场)Q概括v来就是一句话Q找合适的人做合适的目。说hҎ做v来难啊,每个人都有他的领域,如果找来一个擅?/span>a领域的让他做b领域的项目也未必做得好,虽然有的人学习能力超强,但d是需要时间积累经验的。见q听说过很多开发失败的例子Q莫不如此,曑ָ人家优化一个电信计贚w目,原实施的人只会用数据库,所有的计算全用数据库实玎ͼ速度比其Ҏ慢一个数量以上Q将计算需要的数据预装入内存,之后全在内存里面查找计算Q速度一下提高了上百倍,修改后速度领先其对手好几倍,其实q个修改很容易,只要几天旉搞定了Q还包括熟悉他们的数据及规则?/span> q帮别h看过一个棋牌的目Q原目l十几个人搞?/span>1q多Q整?/span>40多万行代码,l果bug不断Q一直不能稳定运行,目l无搞得定,我看了之后下的结论是重写Q他们傻gQ还以ؓ改一个项目的旉肯定要少Q毕竟写了那么多代码Q他们那些外行哪里知道,修改一个漏z无数的工程哪有重写快啊Q这是典型的找了一不恰当的h做了一个不恰当的项目,几百万投入打了水漂,要是让一个有能力的h设计把关Q他那棋牌项?/span>100万够开发得出来了?/span>
刚毕业那会做目的时候带q几个水q差的手下Q都是俺领导招的人)Q他们最常说的话?#8220;优化”Q我把某某地?#8220;优化”了一下,呵呵Q外行要听到q个q以为真的是优化呢,其实很多时候他只是改写了一下,是不是算优化q值得商量Q大多数时候都不上优化,有的时候还攚w引入了更?/span>bug。对高手来说更喜Ƣ说重构Q我把某某模块重新设计了一下,以前模块有哪些缺P重新设计一下之后这些缺陷就不存在了Q还有某某好处,{等Q高手着g大局Q低手只能看C个小角落Q高手优化关键之处,低手优化无关紧要之处。看豆瓣|技术发展,是一个不断重构的q程Q看qq?/span>98q开始到现在的蜂鸟内核,大的重构?/span>3ơ了Q完全重?/span>3ơ了。最q迅L于官Ҏ?/span>v6开发失败,我之前在里表达q我的观点,我估计他?/span>v6是开发失败了Q所以一直以v5着Q现在等?/span>v7出来l于承认了,q种自诩市D100亿美刀的公司这么长旉的开发还能失败,׃要说公司小团队了,见光ȝ目很多Q未见光ȝ目更多?/span>
国内真正懂得开发的老板很少Q大多数老板觉得一个h开5w 10w的月薪太高了Q这个待遇找1w的能扑֥几个呢,其实他们不知道,it行业一个重要的?/span>100个普通的人,甚至不可比较Q因Z个普通的人去做一个项目可能根本不能完成,成功?/span>0Q?/span>1Q?/span>0q个比值是无穷大啊Q可惜等老板们失败了几个目之后才能悟出q个道理Q?/span>it目做得好和做不好的差别不是差一点的问题Q而是1?/span>0的问题。俗人L忙忙碌Q天天维护自己前一天制造的bugQ看h很敬业,高手L懒懒散散Q因?/span>100天后可能出现的问题都已经了然于胸Q于是整天看h无所事事Ӟ不懂的老板很可能青睐于前者而打压后者。也正因说的人太多,懂行的老板又太,所以得整个行业充斥着躁和急功q利Q很多关键职位其实只是个鹦鹉在顶着Q有能力的h被压Ӟ悲哀啊。千里马难找Q伯乐更难找啊?/span>