• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

               C++ 技術中心

               :: 首頁 :: 聯系 ::  :: 管理
              160 Posts :: 0 Stories :: 87 Comments :: 0 Trackbacks

            公告

            鄭重聲明:本BLOG所發表的原創文章,作者保留一切權利。必須經過作者本人同意后方可轉載,并注名作者(天空)和出處(CppBlog.com)。作者Email:coder@luckcoder.com

            留言簿(27)

            搜索

            •  

            最新隨筆

            最新評論

            評論排行榜

            概述

            epoll是linux提供一種多路復用的技術,類似各個平臺都支持的select,只是epoll在內核的實現做了更多地優化,可以支持比select更多的文件描述符,當然也支持 socket這種網絡的文件描述符。linux上的大并發的接入服務器,目前的實現方式肯定都通過epoll實現。


            epoll和線程

            有很多開發人員用epoll的時候,會開多個線程來進行數據通信,比如一個線程專門accept(我個人早些年在FreeBSD用kqueue的時候,由于對內部機制沒有基本了解也這樣搞),一個線程收發,或者接收和發送都用各自獨立的線程。

            通常情況下,accept獨立線程是沒必要的,accept對于內核而言,就應用程序從內核的未完成的SYN隊列讀取一點數據而已。具體參見 accept部分:

            TCP三次握手過程與對應的Berkeley Socket APIs的介紹

             

            收發獨立成兩個線程也沒有必要,因為大部分的應用服務器,通常情況下,啟動一個線程收發數據,最大數據的收發量瓶頸在于網卡,而不是CPU;像網游接入服務器配置一個KM的網卡,很少有游戲會申請1G的帶寬,那一臺機器得有多少數據輸入和輸出。所以我們通信線程為epoll服務器就夠了。


            epoll的基本原理

            為了讓某些朋友能讀得更連慣,我還是說一下epoll基本原理。

            epoll外部表現和select是一樣的。他提供READ, WRITE和ERROR等事件。

            大致流程像下面這樣:

            1. 應用注冊感興趣的事件到內核;

            2. 內核在某種條件下,將事件通知應用程序;

            3. 應用程序收到事件后,根據事件類型做對應的邏輯處理。


            原理部分我再說一下,不容易理解的地方,包括水平觸發和邊緣觸發,WRITE事件的事件利用(這個可以結合參考文獻1的kqueue的WRITE事件,原理一致的)和修改事件的細節。

            水平觸發

            READ事件,socket recv buff有數據,將一直向應用程序通知,直到buff為空。

            WRITE事件,socket send buff從滿的狀態到可發送數據,將一直通知應用程序,直到buff滿。


            邊緣觸發

            READ事件,socket recv buff有數據了,只通知應用一次,不管應用程序有沒有調用read api,接下來都不通知了。

            WRITE事件,socket send buff從滿的狀態到可以發送數據,只通知一次。

            上面這個解釋不知道大家能否理解,也只能這樣說了。有疑問的做一下試驗。另外,這些細節的東西,前幾年固定下來后,這幾年做的項目,是直接用的,也就很少在涉及細節,是憑理解和記憶寫下的文字,萬一有錯請指正^-^。


            WRITE事件的利用

            這個還一下不好描述。大概描述一下,詳細看參考文獻1。大致這樣:

            1. 邏輯層寫數據到應用層的發送buff,向epoll注冊一下WRITE事件;

            2. 這時epoll會通知應用程序一個WRITE事件;

            3. 在WRITE事件響應函數里,從應用層的發送buff讀數據,然后用socket send api發送。

            因為我在很多實際項目中,看到大家沒有利用epoll的WRITE的事件來發數據,特意地說一下。大部分的項目,是直接輪詢應用程序的發送隊列,我早期項目也是這么干的。


            epoll的修改事件

            對于這個我的映像比較深刻。epoll的修改事件比較坑爹,不能單獨修改某個事件!怎么說呢?比如epoll里已經注冊了READ&WRITE事件,你如果想單單重注冊一下WRITE事件而且READ事件不變,epoll的epoll_ctl API是做不到的,你必須同時注冊READ&WRITE,這個在下面的代碼中可以看到。FreeBSD的kqueue在這一點完全滿足我們程序員的要求。


            抽象epoll API

            我把herm socket epoll封裝部分貼出來,讓朋友們參考一下epoll的用法。大部分錯誤拋異常代碼被我去掉了。

             

            1. class Multiplexor
            2. {
            3. public:
            4. Multiplexor(int size, int timeout = -1, bool lt = true);
            5. ~Multiplexor();
            6. void Run();
            7. void Register(ISockHandler* eh, MultiplexorMask mask);
            8. void Remove(ISockHandler* eh);
            9. void EnableMask(ISockHandler* eh, MultiplexorMask mask);
            10. void DisableMask(ISockHandler* eh, MultiplexorMask mask);
            11. private:
            12. inline bool OperateHandler(int op, ISockHandler* eh, MultiplexorMask mask)
            13. {
            14. struct epoll_event evt;
            15. evt.data.ptr = eh;
            16. evt.events = mask;
            17. return epoll_ctl(m_epfd, op, eh->GetHandle(), &evt) != -1;
            18. }
            19. private:
            20. int m_epfd;
            21. struct epoll_event* m_evts;
            22. int m_size;
            23. int m_timeout;
            24. __uint32_t m_otherMasks;
            25. };
            1. Multiplexor::Multiplexor(int size, int timeout, bool lt)
            2. {
            3. m_epfd = epoll_create(size);
            4. if (m_epfd == -1)
            5. throw HERM_SOCKET_EXCEPTION(ST_OTHER);
            6. m_size = size;
            7. m_evts = new struct epoll_event[size];
            8. m_timeout = timeout;
            9. // sys/epoll.h is no EPOLLRDHUP(0X2000), don't add EPOLLRDHUP
            10. m_otherMasks = EPOLLERR | EPOLLHUP;
            11. if (!lt)
            12. m_otherMasks |= EPOLLET;
            13. }
            14. Multiplexor::~Multiplexor()
            15. {
            16. close(m_epfd);
            17. delete[] m_evts;
            18. }
            19. void Multiplexor::Run()
            20. {
            21. int fds = epoll_wait(m_epfd, m_evts, m_size, m_timeout);
            22. if (fds == -1)
            23. {
            24. if (errno == EINTR)
            25. return;
            26. }
            27. for (int i = 0; i < fds; ++i)
            28. {
            29. __uint32_t evts = m_evts[i].events;
            30. ISockHandler* eh = reinterpret_cast<ISockHandler*>(m_evts[i].data.ptr);
            31. int stateType = ST_SUCCESS;
            32. if (evts & EPOLLIN)
            33. stateType = eh->OnReceive();
            34. if (evts & EPOLLOUT)
            35. stateType = eh->OnSend();
            36. if (evts & EPOLLERR || evts & EPOLLHUP)
            37. stateType = ST_EXCEPT_FAILED;
            38. if (stateType != ST_SUCCESS)
            39. eh->OnError(stateType, errno);
            40. }
            41. }
            42. void Multiplexor::Register(ISockHandler* eh, MultiplexorMask mask)
            43. {
            44. MultiplexorMask masks = mask | m_otherMasks;
            45. OperateHandler(EPOLL_CTL_ADD, eh, masks);
            46. }
            47. void Multiplexor::Remove(ISockHandler* eh)
            48. {
            49. // Delete fd from epoll, don't need masks
            50. OperateHandler(EPOLL_CTL_DEL, eh, ALL_EVENTS_MASK);
            51. }
            52. void Multiplexor::EnableMask(ISockHandler* eh, MultiplexorMask mask)
            53. {
            54. MultiplexorMask masks = mask | Herm::READ_MASK | Herm::WRITE_MASK;
            55. OperateHandler(EPOLL_CTL_MOD, eh, masks | m_otherMasks);
            56. }
            57. void Multiplexor::DisableMask(ISockHandler* eh, MultiplexorMask mask)
            58. {
            59. MultiplexorMask masks = (Herm::READ_MASK | Herm::WRITE_MASK) & (~mask);
            60. if (!OperateHandler(EPOLL_CTL_MOD, eh, masks | m_otherMasks))
            61. throw HERM_SOCKET_EXCEPTION(ST_OTHER);
            62. }

            上面類就用到epoll_create(), epoll_ctl()和epoll_wait(),以及幾種事件。epoll用起來比select清爽一些。


            大致用法類似下面這樣:

            先定義一個Handler

             

            1. class StreamHandler : public Herm::ISockHandler
            2. {
            3. public:
            4. virtual Herm::Handle GetHandle() const;
            5. virtual int OnReceive(int);
            6. virtual int OnSend(int);
            7. };

             

            在OnReceive()處理收到數據的動作,在OnSend()。。。。

            在通信線程中,大概類似這樣的代碼,實際看情況。

             

            1. Multiplexor multiplexor;
            2. StreamHandler sh;
            3. multiplexor.Register(&sh, READ_EVT);
            4. multiplexor.Run(...);


             

            參考文獻

            1.

             

            使用 kqueue 在 FreeBSD 上開發高性能應用服務器

            FreeBSD上kqueue和epoll是類似的,有興趣的朋友請參考。

            http://blog.csdn.net/herm_lib/article/details/6047038

             

            2.

            【Herm程序員開發指導】第2章 Herm Framework和網絡通信組件

            這里涉及到epoll的通信層如何和邏輯數據交互的問題

             

            http://blog.csdn.net/herm_lib/article/details/5980657

            posted on 2013-09-17 17:31 C++技術中心 閱讀(3289) 評論(0)  編輯 收藏 引用 所屬分類: Linux 編程
            精品久久香蕉国产线看观看亚洲| 色婷婷噜噜久久国产精品12p | 69久久夜色精品国产69| 99精品国产99久久久久久97| 99久久夜色精品国产网站| 久久99精品国产麻豆宅宅| 久久久久黑人强伦姧人妻| 久久精品青青草原伊人| 青草影院天堂男人久久| 久久91精品国产91| 久久r热这里有精品视频| 亚洲欧洲久久久精品| 91麻精品国产91久久久久| 亚洲人成电影网站久久| 久久婷婷久久一区二区三区| 欧美伊人久久大香线蕉综合| 伊人久久大香线蕉影院95| 亚洲va久久久噜噜噜久久狠狠 | 亚洲国产精品综合久久一线| 精品无码久久久久久午夜| 一级女性全黄久久生活片免费| 久久精品成人免费看| 久久九九精品99国产精品| 漂亮人妻被中出中文字幕久久 | 久久精品国产99久久丝袜| 国产精品久久久久影院嫩草| 午夜精品久久久久久久久| 午夜视频久久久久一区 | 久久青青草原亚洲av无码| 久久久无码精品亚洲日韩按摩| 亚洲国产日韩综合久久精品| 91精品国产色综久久| 久久综合中文字幕| 久久精品视频网| 热99re久久国超精品首页| 69国产成人综合久久精品| 99久久无码一区人妻a黑| 97久久香蕉国产线看观看| 久久99精品久久只有精品| 狠狠色婷婷久久一区二区三区| 国产精品女同久久久久电影院|