IOCP编程小结(中)棋牌

上一篇紧要谈了有的主导见解,本篇将琢磨自己个人总计的有些IOCP编程技巧。

■扣扣保镖:彪悍的QQ电脑管家攻势,彪悍的360“安全之名”,彪悍的竞争不需要解释。■天涯论坛博客园:作为“纯纯”的信息门户,天涯论坛等待一款如搜狐信箱、腾讯QQ这样的产品太久了。■联想乐Phone:移动互联网是可口蛋糕,而蛋糕旁已经蹲了如狼似虎的苹果、谷歌,仍是可以等啊?■Taobao商城:独立的、分拆的天猫商城,由于京东商城、乐酷天的B2C攻势,发力的必要性什么人都掌握。■团购:从单个产品扩充到10亿元营收产业,省钱理念逼出来需求。■盛大Bambook:华丽的网络农学需要飞速变现的读书终端,莱睿2000万视频头免驱超高清油画头夜视麦克风,更何况PC端付费阅读正饱受免费挑战。■人人小组:不爱偷菜了,也尚未停车位了,社交网站重归“社交圈子”,豆瓣网的情势变得实惠。■“凡客”牌:网购平台多年来都是为旁人做嫁衣,伙食美食,薄利多销但没有自己的品牌,“凡客”们需要雄起。■英雄杀:当竞争对手盛大搞出那么一款风靡的“三国杀”,腾讯棋牌游戏感觉压力是毫无疑问的。■网络剧:土豆、优酷们说,购买影视剧,烧钱压力实在太大了,《老男孩》或许能够名利双收。商报记者张绪旺
相关的主旨著作:

 

网络游戏前端服务器的急需和规划

  首先介绍一下这么些服务器的技艺背景。在分布式网络游戏服务器中,前端连接服务器是一种很普遍的规划。他的天职重大有:

  1. 为客户端和后端的一日游逻辑服务器提供一个软件路由 ——
客户端一旦和前端服务器建立TCP连接将来就足以由此那个连续和后端的游玩服务器进行通讯,而不再需要和后端的服务器再建立新的连日。

  2. 承担来自客户端的IO压力 ——
一组优异的网络游戏服务器需要服务少则几千多则上万(休闲游戏则足以多达几十万)的游乐客户端,这多少个IO处理的载荷万分可观,由一组前端服务器承载这么些IO负担可以使得的减轻后端服务器的IO负担,并且让后端服务器也只需要关怀游戏逻辑的贯彻,有效的贯彻IO和工作逻辑的解耦。

  架构如图:

棋牌 1

 

  对于网络游戏来说,客户端与服务器之间需要展开反复的报道,然则每个数据包的尺寸基本都很小,典型的深浅为多少个字节到几十个字节不等,同时用户上行的数据量要比下行数据量小的多。不同的游乐项目对延期的渴求不太雷同,FPS类的娱乐希望延迟要自愧不如50ms,MMO类型的100~400ms,一些休闲类的棋牌游戏1000ms左右的推移也是可以接受的。由此,网络游戏的通讯是以优化延迟的还要又不可能不兼顾小包的合并以避免网络拥塞,哪个因素为主则需要遵照现实的游艺项目来控制。

  技术背景就介绍这么些,前面介绍的IOCP连接服务器就是以这一个要求为设计目标的。

 

对IOCP服务器框架的洞察

  在入手实现这么些连续服务器从前,我先是观望了部分现有的开源IOCP服务器框架库,老牌的如ACE,整个库太多庞大臃肿,代码也显老态,无好感。boost.asio据说是个不错的网络框架也补助IOCP,我编译运行了刹那间她的例子,然后尝试着读书了一晃asio的代码,感觉特别恐怖,完全弄不领悟里边是怎么落实的,于是摒弃。asio秉承了boost一贯的变态作风,将C++的言语技巧凌驾于规划和代码可读性之上,这是自己非凡反对的。其他一些不入流的IOCP框架也看了有的,真是写的丰盛多彩怎么着的落实都有,总体感觉下来IOCP确实不太容易把握和架空,所以才导致五花八门的兑现。最终,如故决定自己再度造轮子。

 

劳动框架的纸上谈兵

  任何的服务器框架从本质上说都是包裹一个风波(伊夫(Eve)nt)音讯循环。而应用层只要向框架注册事件处理函数,响应事件并举办拍卖就可以了。一般的一块IO处理框架是先吸收IO事件然后再展开IO操作,这类的事件处理框架我们称之为Reactor。而IOCP的新鲜之处在于用户是首发起IO操作,然后接收IO完成的风波,次序和Reactor是倒转的,这类的事件处理框架我们称为Proactor。从词根Re和Pro上,大家也可以容易的知道那两者的差别。除了网络IO事件之外,服务器应该还是可以够响应提姆(Tim)er事件及用户自定义事件。框架做的事体就是把这个事件统统放到一个音讯队列里,然后从队列中取出事件,调用相应的事件处理函数,如此循环。

  IOCP为我们提供了一个系统级的信息队列(称之为完成队列),事件循环就是围绕着这些完成队列展开的。在倡议IO操作后系统会开展异步处理(假诺能立刻处理的话也会一贯处理掉),当操作完成后自行向这么些行列投递一条音讯,不管是一向处理仍然异步处理,最终总会投递完成消息。

*  顺便提一下:这里存在一个性能优化的机遇:当IO操作可以立刻完成的话,假设让系统不要再投递完成消息,那么就可以减小两次系统调用(这足足可以节约多少个飞秒的付出),做法是调用SetFileCompletionNotificationModes(handle,
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS),具体的能够查看MSDN。*

  对于用户自定义事件可以拔取Post来投递。对于提姆er事件,我的做法则是促成一个提姆(Tim)erHeap的数据结构,然后在音讯循环中定期检查那么些提姆(Tim)erHeap,对逾期的提姆(Tim)er事件举行调度。

  IOCP完成队列再次来到的音讯是一个OVERLAPPED结构体和一个ULONG_PTR
complete_key。complete_key是在用户将Socket
handle关联到IOCP的时候绑定的,其实用性不是很大,而OVERLAPPED结构体则是在用户发起IO操作的时候设置的,并且OVERLAPPED结构得以由用户通过持续的办法来扩张,由此咋样用好OVERLAPPED结构在螺丝壳里做道场,就成了打包好IOCP的机要。

  这里,我动用了一个C++模板技巧来增加OVERLAPPED结构,先看代码:

struct IOCPHandler
{
    virtual void Complete(ULONG_PTR key, DWORD size) = 0;
    virtual void OnError(ULONG_PTR key, DWORD error){}
    virtual void Destroy() = 0;
};

struct Overlapped : public OVERLAPPED
{
    IOCPHandler* handler;
};

template<class T>
struct OverlappedWrapper : T
{
    Overlapped overlap;

    OverlappedWrapper(){
        ZeroMemory(&overlap, sizeof(overlap));
        overlap.handler = this;
    }

    operator OVERLAPPED*(){return &overlap;}
};

  IOCPHandler是用户对象的接口,用户扩充那些接口来促成IO完成事件的处理。然后通过一个OverlappedWrapper<T>的模板类将用户对象和OVERLAPPED结构封装成一个目的,T类型就是用户扩大的靶子,由于用户对象位于OVERLAPPED结构体的先头,因而我们会将OVERLAPPED的指针传递给IO操作的API,同时我们在OVERLAPPED结构的前面还放置了一个用户对象的指针,当GetQueuedCompletionStatus接收到OVERLAPPED结构体指针后,我们经过那些指针就可以找到用户对象的岗位了,然后调用虚函数Complete或者OnError就足以了。

  图解一下对象社团:

棋牌 2

 

在事变循环里的拍卖方法 :

DWORD size;
ULONG_PTR key;
Overlapped* overlap;
BOOL ret = ::GetQueuedCompletionStatus(_iocp, &size, &key, (LPOVERLAPPED*)&overlap, dt);
if(ret){
    if(overlap == 0){
        OnExit();
        break;
    }
    overlap->handler->Complete(key, size);
    overlap->handler->Destroy();
}
else {
    DWORD err = GetLastError();
    if(err == WAIT_TIMEOUT)
        UpdateTimer();
    else if(overlap) {
        overlap->handler->OnError(key, err);
        overlap->handler->Destroy();
    }
}

   在此地运用我们拔取了C++的多态来扩展OVERLAPPED结构,在框架层完全不用关爱接收到的是何许IO事件,只需要应用层自己关注就够了,同时也避免了选取丑陋的难于扩展的switch..case结构。

  对于异步操作来说,最令人痛苦的业务就是需要把原来顺序逻辑的代码强行拆分成多块来回调,这使得代码中原本蕴含的逐条逻辑被打散,并且在各类代码块里的上下文变量无法共享,必须其余生成一个目的放置那多少个上下文变量,而这又掀起一个对象生存期管理的题目,对于尚未GC的C++来说尤其痛苦。解决异步逻辑的伤痛之道如今有二种方案:一种是用coroutine(协作式线程)将异步逻辑变成同步逻辑,在Windows上可以动用Fiber来实现coroutine;另一种方案是拔取闭包,闭包原本是函数式语言的特征,在C++里并不曾,可是幸运的是大家得以经过一个稍稍麻烦一点的不二法门来效仿闭包行为。coroutine在化解异步逻辑方面是最擅长的,特别是一个函数里需要各样进行多少个异步操作的时候尤其强大(在这种气象下闭包也相形见拙),可是另一方面coroutine的落实相比较复杂,线程的手工调度平时把人绕晕,对于IOCP这种异步操作相比较有限的情景有点杀鸡用牛刀的感到。由此最后自己要么决定接纳C++来模拟闭包行为。

  这里演示一个卓越的异步IO用法,看代码: 

棋牌 3棋牌 4一个异步发送的例证:

void Client::Send(const char* data, int size)
{
    const char* buf = AllocSendBuffer(data, size);

    struct SendHandler : public IOCPHandler
    {
        Client* client;
        int     cookie;

        virtual void Destroy(){    delete this; }
        virtual void Complete(ULONG_PTR key, DWORD size){
            if(!client->CheckAvaliable(cookie))
                return;
            client->EndSend(size);
        }
        virtual void OnError(ULONG_PTR key, DWORD error){
            if(!client->CheckAvaliable(cookie))
                return;
            client->OnError(E_SocketError, error);
        }
    };

    OverlappedWrapper<SendHandler>* handler = new OverlappedWrapper<SendHandler>();
    handler->cookie = _clientId;
    handler->client = this;
    int sent = 0;
    Error e = _socket.AsyncSend(buf, size, *handler, &sent);
    if(e.Check()){
        LogError2(“SendAsync Failed. %s”, FormatAPIError(_socket.CheckError()).c_str());
        handler->Destroy();
        OnError(E_SocketError, _socket.CheckError());
    }
    else if(sent == size){
        handler->Destroy();
        EndSend(size);
    }
}

   这一个事例中,咱们在函数内部定义了一个SendHandler对象,模拟出了一个闭包的所作所为,我们能够把需要动用的上下文变量放置在SendHandler内,当下次回调的时候就可以访问到那多少个变量了。本例中,大家在SendHandler里记了一个cookie,其效率是当异步操作再次回到时,可能那个Client对象已经被回收了,这多少个时候假若再调用EndSend必然会促成错误的结果,由此我们由此cookie来判定这些Client对象是否是这一个异步操作发起时的Client对象。

  使用闭包即使尚无coroutine这样漂亮的种种逻辑结构,然则也丰裕便利你把各样异步回调代码串起来,同时在闭包内共享需要采取的上下文变量。其它,最新版的C++标准对闭包有了原生的襄助,实现起来会更有益于一些,假使您的编译器充分新的话可以品尝使用新的C++特性。

 

  

IO工作线程 单线程vs多线程

  在大部分讲师IOCP的篇章中都会提出采纳五个干活线程来拍卖IO事件,并且把工作线程数设置为CPU焦点数的2倍。遵照自家的印象,这种说法的出处来自于微软最初的官方文档。可是,在我看来这统统是一种误导。IOCP的计划性初衷就是用尽可能少的线程来处理IO事件,由此采用单线程处理自己是没有问题的,这足以使贯彻简化很多。反之,用多线程来拍卖的话,必须处处小心线程安全的问题,同时也会提到到加锁的问题,而不适用的加锁反而会使性能急剧下降,甚至不如单线程程序。有些同学可能会觉得利用多线程能够发挥多核CPU的优势,不过当前CPU的速度充裕用来处理IO事件,一般现代CPU的单个要旨要拍卖一块千兆网卡的IO事件是绰绰有余的,最多的可以而且处理2块网卡的IO事件,瓶颈往往在网卡上。假使是想经过多块网卡提高IO吞吐量的话,我的提出是接纳多进程来横向增加,多进程不仅可以在单台物理服务器上举办扩充,并且还是能扩张到多台物理服务器上,其伸缩性要比多线程更强。

  
当时微软指出的那个指出我想着重是考虑到在IO线程中除了IO处理之外还有工作逻辑需要处理,使用多线程可以化解事情逻辑不通的问题。不过将事情逻辑放在IO线程里处理我不是一种好的设计情势,这并未很好的完结IO和业务解耦,同时也限制了服务器的伸缩性。优秀的计划应该将IO和事情解耦,使用多进程或者多线程将业务逻辑放在另外的进程或者线程里展开拍卖,而IO线程只需要承担最简便的IO处理,并将吸纳的音信转发到业务逻辑的历程或者线程里处理就足以了。我的前端连接服务器也是依据了这种规划艺术。

   

关闭发送缓冲区实现和谐的nagle算法

  IOCP最大的优势就是她的灵活性,关闭socket上的发送缓冲区就是一例。很多少人认为关闭发送缓冲的市值是足以减掉两回内存拷贝的付出,在我看来这只是捡了一粒芝麻而已。主流的千兆网卡其最大数据吞吐量然而区区120MB/s,而内存数据拷贝的吞吐量是10GB/s以上,多四回120MB/s数据拷贝,仅消耗1%的内存带宽,意义相当有限。

  在平凡的Socket编程中,我们唯有打开nagle算法或者不打开的抉择,策略的拔取和参数的微调是绝非艺术成功的。而当我们关闭发送缓冲之后,每便Send操作必然会等到数量发送到对方的说道栈里并且吸纳ACK确认才会回去完成消息,这就给了俺们一个落实自定义的nagle算法的机会。对于网络游戏那种需要频繁发送小数据包,打开nagle算法可以有效的统一发送小数目包以降低网络IO负担,但一头也加大了延期,对游戏性造成不利影响。有了关闭发送缓冲的风味之后,我们就足以自行决定nagle算法的兑现细节,在上一个send操作没有完结在此以前,我们得以控制是立时发送新的数目(以降低延迟),依旧累积数据等待上一个send截至或者逾期后再发送。更复杂一点的国策是可以让服务器容忍五个未竣工的send操作,当不止一个阈值后再累积数据,使得在IO吞吐量和延迟上直达一个理所当然的平衡。

 

出殡缓冲的分配政策

  后边提到了关闭socket的殡葬缓冲,那么就关乎到我们团结一心怎么来分配发送缓冲的题材。

  一种政策是给各种Socket分配一个固定大小的环形缓冲区。这会存在一个题目:当缓冲区内累积的未发送数据加上新发送的数目大小超出了缓冲区的深浅,那个时候就会磕磕碰碰麻烦,要么阻塞以等待眼前的数额发送完毕(不过IO线程不可以阻隔),要么干脆直接把Socket关闭,一个低头的法门是拼命三郎把发送缓冲区设置的大一部分,但那又会白白浪费很多内存。

  另一种政策是让拥有的客户端socket共享一个非凡大的环形缓冲区,假如大家保留一个1G的内存区域给这多少个环形缓冲区,每趟需要向客户端发送数据时就从这多少个环形缓冲区分配内存,当缓冲区分配到底了再绕到起始重新分配。由于那些缓冲区非凡大,1G的内存对千兆网卡来说至少需要花费10s才能发送完,并且在实质上采取中这些刻钟会远超10s。因而当新的数额从头起先分配的时候,老的多少早已经发送掉了,不用顾虑将老的多寡覆盖,即使遇见网络堵塞,一个数目包领先10s还未发送掉的话,我们也足以通过超时判断主动关闭那些socket。

 

socket池和对象池的分红政策

  允许socket重用是IOCP另一个优势,我们得以在server启动时,依据我们对最大服务人口的预测,将兼具的socket资源都分配好。一般的话每个socket必需对应一个client对象,用来记录一些客户端的音信,这一个目的池也可以和socket绑定并优先分配好。在服务运行前将享有的大块对象的内存资源都预先分配好,用一个FreeList来做对象池的分红,在客户端下线之后再将资源回收到池中。这样就足以制止在劳务运行过程中动态的分红大的对象,而有些急需暂时分配的小目的(例如OVERLAPPED结构),我们可以动用诸如tcmalloc之类的通用内存分配器来做,tcmalloc内部使用小对象池算法,其分配性能和平安相当好,并且他的接口是非侵入式的,我们仍是可以够在代码里保留malloc/free及new/delete。很多劳动在深入运行之后出现运行效用下降,内存占用过大等问题,都跟频繁的分配和释放内存导致出现大量的内存碎片有关。所以做好服务器的内存分配管理是任重而道远的一环。

 

待续….

 

下一篇将通过多少个压力测试和profiling的事例,来分析服务器的属性和瓶颈所在,请我们关心。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注