• <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>

            置頂隨筆

             

             

            最近給自己換了個老板,忙了一段時間,所以有幾個月沒寫博客,今后還是要爭取多寫啊,呵呵。

             

            換來新地方,第一件大的事情就是修改后端架構和通信協議,架構也設計得很普通,因為這邊的業務不需要太過復雜的后端,所以就簡單設計了一下,基本是參照web的模型,符合我一貫的向web學習的思想,弄了個gate管理入口,相當于web下的webserver,后端其他服務器掛在該gate下,相當于web模型下的appserver,或者fastcgi模型的fastcgi進程,gate上管理連接、合法性檢測、登錄、加密、壓縮、緩存。Gate和后端通信本來想參照fastcgi協議,但看了之后覺得fastcgi協議還是復雜了,所以就設計了一個更簡單的協議,gate和后端server之間可傳遞key:value型數據對,value不局限于字符串,可以是任意數據,這樣基本滿足了當前的需求,第一版放上去之后也運行良好,到今天也基本持續穩定運行快一個月了,沒出過什么事情。由于在gate這邊緩沖了job管理,所以后端server升級很方便,隨時可關閉更新,gate會在窗口時間內將未執行完成的任務重新提交,有此功能可放心大膽的升級后端,這個月這樣的工作做了幾次,在架構修改之前這樣的事情幾乎是不敢做的,因為一旦升級所有用戶全部斷開連接,而現在用戶則基本無感覺。Gate上的緩存層為后端減少了一些壓力,這個緩存是按照請求的md5key做的,并根據協議配置時效,有此cache后端大多數服務可不設計緩存或降低緩存設計的復雜度。Gate上針對敏感數據統一做了加密處理,主要是辛辛苦苦整理的數據不能輕易讓競爭對手竊去了,呵呵。Gate也做了壓縮,現在是針對>=128長度的包進行壓縮,使用了qlz,壓縮效率還是很不錯的,速度很快。目前gate后端掛接的既有win上的server也有linux上的server,這是一開始就這么規劃的,現在看來當初的目的達到了,混合發揮各自的優勢,有的項目在原有系統上跑得好好的,沒必要重新開發嘛。

             

            協議設計上本來我是計劃二進制混合json格式,以二進制為主,但嘗試了一個協議之后發現,這邊的小伙子們對直接操縱內存普遍技術不過關,他們大多是從java開始的,后來才學習c,對字符串用得很熟練,權衡之下采用了json為主,混合二進制為輔的方案,這樣修改之后的協議和他們之前使用的xml類似,就是更小更緊湊一點,使用方法上很類似,從現在的效果看還行,使用json格式為主的協議當然不能跟使用pb之類的相比,解析效率上大約單線程每秒解析20來萬10obj的對象,速度上不算太快但也不算太慢,對付一秒至多幾萬數據包的應用來說還是夠的,因為現在cpu計算能力普遍過剩,使用json的另個好處就是增刪字段很方便,各個版本之間不需要太考慮版本的問題,要是全用二進制格式就要麻煩很多了,在使用壓縮之后,目前的json格式協議比之前的xml協議減少了2/3的帶寬使用,總體效果還是可以的。使用json調試也很方便,我提供了一個工具,寫后端的就直接用該工具按照json格式收發數據,無需等client開發好了再去做后端,之后做client也很方便,請求發過去之后返回來的就是標準的json格式數據,同樣的解析方法,每個不同的應用就按照不同的格式處理下即可,和web等模塊交互也很方便,這可算是額外的好處了。

             

            總之,雖然json格式存儲效率和解析效率跟二進制方式還差半個量級到一個量級,但合理使用還是可以的,特別是跟xml相比優勢很明顯,權衡使用吧,當然追求極致效率可能還是用pb之類的更合適一些,或者自己設計tlv格式。

             

            posted @ 2011-01-11 13:33 袁斌 閱讀(2532) | 評論 (3)編輯 收藏

            07年我寫了一篇文章叫《我的網絡模塊設計》,姑且叫那個為第一版吧,由于持續對網絡模塊進行改進,所以現在的實現和當時有很大改變,加上上層應用越來越多,又經過了幾年時間考驗,現在的實現方式比之前的更靈活更有效率,也因為最近看了一些人做網絡程序多年竟毫無建樹,一直要用別人寫的網絡模塊,所以有感而寫此文,為了使得此文不受上一篇《我的網絡模塊設計》的影響,我決定寫之前不看原來的文章,所以此文跟原文那篇文章可能沒有太多相似性。
             一個基本的網絡模塊,無非就是管理N個連接,快速處理每個連接的收發數據、消息等,所謂好的網路模塊,無非就是穩定、高效、靈活,下面分幾部分來寫:
             一、 連接管理
             之所以首先寫連接管理,是因為連接管理是核心,也是最難的地方,我寫第一個網絡庫之前,搜索過很多當時可以找到的例子工程,當時幾乎找不到可穩定運行的工程,當然更找不到好的,于是摸索前進,期間對連接管理使用了各種方法,從最早一個cs(臨界區CriticalSection,我簡稱cs),recv send都用這個cs,到后來send用一個cs,recv用一個cs,用多個的時候還出過錯,最后使用一個cs+一個原子值ref管理一個連接,每個連接send的時候用cs,recv的時候用ref,如果該連接的消息要跨線程異步執行,也使用ref,如此較簡單的解決了連接管理的問題。
             同樣使用生存期管理方法,也有人用智能指針,雖然原理和我直接操縱生存期一樣,但實現方法畢竟不同,不過我為了讓實現依賴少一些沒有引入智能指針。
             當然我后來也發現很多人不是用這種方法,如有些人就id來管理連接,每個連接分個id,其他操作全部用id,每次對連接的調用先翻譯一下,如果id找得到映射目標就調用,否則就說明該連接不存在了,這種方法簡單只是不直接,多了個查找過程,另外查找的時候可能還需要全局鎖(這依賴于連接數據組織)。
             也有人使用一個線程管理連接,其他所有與該連接有關的生存期問題全部到該線程處理,這樣也是可行的,只是需要做一個較好的包裝,如果包裝好上層調用方便,如果包裝不好,可能上層調用就有一些約束。
             雖然各種方法都有人使用,但我一直選擇直接的生存期管理方法,其實內部實現的時候還是有很多優化措施的,減少了大量addref、release的調用,進一步提高了效率。
             二、 線程組
             我最初做網絡庫的時候還不是很清楚上層如何使用這個庫,后來在上面做了幾個應用之后慢慢有了更多想法,最近的網絡庫是設計了這么幾組線程:io線程組、同步線程組、異步線程組、時鐘線程組、log線程組,每組線程都可開可關,就算io線程組也是可關的,這只是為了整個庫更靈活適用性更廣泛,如只用同步線程組或異步線程組僅將這個線程組當一個消息隊列使用。
             Io線程組就是處理io收發的,listen recv send 以及解密解壓縮都是在這組線程,一般這組線程會開2個或2*cpu個。
             同步線程組,一般這組線程開1個,用來處理logic。
             異步線程組,這組線程根據需要開0個或n個,簡單應用無db等慢速操作的應用不開,有很多db等慢速操作的可以開很多個。
             時鐘線程組,一般不開或開1個。
             Log線程組,一般開1個,主要為了避免其他線程調用WriteLog的時候被磁盤io阻塞,所以弄了一個log線程。
             其實還有一個主線程,我的每組線程(包括主線程)都支持事件和定時器,io線程、同步線程、異步線程組、時鐘線程組、甚至log線程組都支持事件和定時器,到去年我還只是讓每組線程都支持事件,今年為了更好的使用時鐘我給每組線程設計了定時器,現在定時器線程組有點雞肋的味道,一般是用不上專門的定時器線程組,不過我還沒有將它刪掉,主要在我的設計里面,它和同步異步線程組一樣,都只是一組線程,如果必要的時候可以將它用作同步線程或者異步線程組,所以繼續保留了它的存在。
             這幾組線程之間都是可互發消息的,所以一個邏輯要異步到別的線程執行是非常方便的,只要調用一下PostXXEvent(TlsInfo *ptls, DWORD dwEvent, WPARAM wParam, LPARAM lParam);我憑借這個設計使得這套網絡庫幾乎可以適用上層各種應用,不管是非常簡單的網絡應用還是復雜的,一框打盡。對最簡單的,一個io線程搞定,其他線程全關,對于復雜的io線程+同步+異步+log全開。
             三、 內存池
             內存池其實沒有想象中的那么神秘,當然如果要讓一個網絡程序持續7*24小時穩定高效運行,內存池幾乎必不可少的,內存池的作用首先是減少內存碎片,其次是為了提高速度,我想這兩點很容易想明白的,關于內存池我之前寫了系列文章,可參考我的博客:
             
            《內存池之引言》 http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx
             《單線程內存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx
             《多線程內存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx
             《dlmalloc、nedmalloc》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx
             《線程關聯內存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx
             《線程關聯內存池再提速》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx
             
            四、 定時器
             關于定時器,上面講線程組的時候已經講過,我現在的設計是每個線程(包括主線程)都支持定時器,調用方法都是一樣的,回調函數形式也是一樣的,由于定時器放到各組線程里面,所以減少了線程之間的切換,提高了效率。
             關于定時器,可參考《定時器模塊改造》 http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx
             
            五、 包格式
             關于包格式可參考《常用cs程序自定義數據包描述》 http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx
             
            六、 Buffer
             之前的文章其實我一直沒有提過我的buffer,其實我的buffer設計是很靈活的,現在它和pool也是有些關聯的,我的poolset其實底下就是按照各種不同大小的buffer預設的尺寸。Buffer我設計為循環式,不允許回繞,包含
             Char *pbase 塊基址
             Char *pread 當前讀指針
             Char *pwrite 當前寫指針
             DWORD tag;
             Buffer *next;
             Capacity 總分配尺寸,上面分配的時候可能只是指定了19,但實際可能分配的是32個字節,所以內部用的時候要根據capacity來最大限度的利用緩沖區。
             Buffer分配還利用了一個技巧,事實上分配的時候是一次分配一個需要的大緩沖,前面為Buffer自身的數據,后面為數據部分,pbase指向數據部分,這樣處理減少了一次分配,我估計很多人都在用這個技巧。
             Pwrite總是不會小于pread的,但pread可能和pbase不一樣,僅當后面空余空間不夠用的時候才可能會移動數據,否則數據不會移動。
             WSARecv的時候我是這么處理的,如果首次獲取了一個包的一部分,但buffer中還有足夠的空間放下包的剩余部分,我不會再分配一個buffer去recv,而是直接用原buffer指定一個合適的偏移和size去WSARecv,這樣可以最大限度的減少復制。
             剛才還有朋友問到我recv的層次組織,我的網絡庫里面是這樣組織的,OnRecv是個虛函數,最基礎的IocpClient的OnRecv只處理數據而不解析格式,IocpClientMsg就會認識默認的一種包格式,這個類的OnRecv會將m_recvbuf中的數據組織為msg,并盡可能的一次返回更多個msg,回調OnMsg函數,由上層決定該消息在哪個線程處理,這樣我認為是最靈活的,如果是個很小的server,可能直接就在io線程里面處理了,也可postevent到同步線程處理,亦可PostEvent到異步線程處理。
             
            七、 TLSINFO
             TlsInfo顧名思義就是每個線程關聯的一組數據,暫時我還沒有看到別人這么設計,也許我設計得有些復雜了,在這個數據里面有一些常用的和該線程相關的數據,如該線程的分配基、步長,用這兩個參數可讓每個線程制造出唯一序列,還有常用pool的地址,如tm_pool *p1k; tm_pool *p2k;… 這樣設計使得要分配的時候直接取tm_pool,最大限度的發揮了分配速度,還有一些常規參量long c; long d; DWORD a; DWORD b;… 這幾個值可理解為棧內值,其實為了減少上層調用復雜度的,如我將一個連接的包從io線程PostEvent到同步線程處理,PostEvent首參數就是tlsinfo,PostEvent會根據tlsinfo里面的一個內部值決定是不是要調用addref,因為我有個地方預增了2,所以大多數情況下在io發到其他線程的時候是無需調用addref的,提高了效率,tlsinfo里的其他一些值上層應用可使用,用在邏輯處理等情況下。
             
            八、 性能分析
             *nix下有很多知名的網絡庫,但在win下特別是使用iocp的庫里面,一直就沒有一個能作為基準的庫,即使asio也因為出來太晚不為大多數人熟悉而不能成為基準庫,libevent接iocp由于采用0 buffer模擬所以也沒有發揮出足夠的性能,對比spserver我比它快70%左右,我總在想要是微軟能將他那個iocp的例子寫得更好一點就好了,至少學的人有一個更高一點的基礎,而不至于讓http://www.codeproject.com/KB/IP/iocp_server_client.aspx這樣的垃圾代碼都能成為很多人的樣板。
             
            九、 雜談
             為了寫好一個win下穩定高效的網絡庫,我07年的時候幾乎搜遍了那個時間段之前所有能找到的iocp例子,還包括通過朋友等途徑看到的如snda等網絡庫,可惜真沒找到好的,大多數例子是只要多線程發起幾千個連接不斷發送數據馬上就死了,偶爾幾個不死的(包括snda的)只要隨機連接并斷開就會產生句柄泄漏,關閉所有連接之后句柄并不關閉等,也就是說這些例子連基本的生存期管理都沒搞定,能通過生存期管理并且不死的只有有限的幾個,可惜性能又太差,杯具啊。
             早年寫網絡庫的時候也加入了sodme在google上建的那個群,當時群還是很熱鬧的,可惜大多數人都是摸索,所以很多問題只是討論卻從無定論,沒有誰能說服別人,也沒有人可輕易被說服,要是現在或許有一些很有經驗的人,可惜那個群由于GFW現在雖能訪問也不大活躍了。
             最近看到有些寫網絡程序7年甚至更久的人還在用libevent、ace等感想很復雜,可悲的是那些人還沒意識到用一個庫和寫一個庫有多大的區別,可能那些人一輩子也認識不到寫一個庫比用一個庫難多少,那些人以為這些庫基本會用了,讓他自己去寫也基本是照這個模式,不會有什么突破,就無需自己動手了,悲哀啊。當然,要寫一個穩定的網絡庫需要耗費很多時間,特別是要寫一個能和知名庫性能接近或更好的庫,更是要費神費力,沒點耐心和持久力是不可能做好的。在中文領域隨便查什么稍有些名氣的代碼,總是能找到很多剖析類文章,可原創的東西總是很少,也不知道那些大俠怎么搞的,什么都能剖析可怎么總寫不出什么像樣的東西呢。
             其實本來沒有打算寫這篇文章,可能是看了陳碩的muduo才使得我有了寫出來的沖動,大概是受到他的開源鼓勵吧。
             謹以此文記錄本人最近3年對網絡模塊的修改并簡短總結。

             

            posted @ 2010-10-03 14:25 袁斌 閱讀(3253) | 評論 (5)編輯 收藏

            實用云計算環境簡述

             

            如今it領域沒聽說過云計算的絕對是out了,雖然大家都知道云計算,雖然很多高校很多專業都開設了云計算專業,雖然很多人都在討論云計算,雖然也有少數人走在了應用云計算的前列,然而,可悲的是,大多數人對云計算的認識僅限于amazongooglemicrosoftibm有能力架設云計算環境,其他公司都靠邊,甚至唯他們的云計算才叫云計算,別的企業根本不可能做云計算,各級政府部門最搞笑了,動不動花多少錢引進某某云計算環境,填補某某空白,多少cpu多少機器每秒多少萬億次計算,最終是不是一堆浪費電力的擺設也沒有人知道,也沒人去過問。

            略感欣慰的是,很多企業都在務實地部署自己的云計算環境,大如騰訊、淘寶、百度、小如我們這樣剛成立的小公司,其實要部署一個私有云計算環境并沒有那么難,以我個人的經驗來看,如果有一個精干的小團隊,幾個人一個月部署一個私有云計算環境是完全可能可行的。在我看來,所謂云計算就是分布式存儲+分布式計算,不局限于底下oswin還是*nix,也不局限于是局域網環境還是廣域網環境,也不管上面跑的是c++的程序還是javascript的程序,下面簡單介紹下我設計的一個即時查詢價格的云計算體系:

            我一直在win下開發,win用得非常熟練,所以我把云計算環境部署在windows之上,當然也考慮到windows的機器眾多,tasknode可輕易找到非常多的目標機器,我部署的云計算環境主要分兩類節點,jobservertasknodejobserver主管任務切割、任務調度,tasknode是計算節點。另外還有一些節點,jobowner可連接jobserver并提交任務,并可查詢該任務的執行情況,admin可連接jobserver查詢jobserver的狀態。

             

            其實這些上篇博客已經寫過,我再講的詳細一點,看具體的執行情況,首先jobownerjobserver提交package,這個package是一個zip文件,包含一組文件,jobowner提交package之后jobserver會根據約定的規則管理package,并在jobserver展開該package,如下:

             

             

            Jobowner連到jobserver之后,發出如下的命令到jobserver

            0x49 0x0 0x0 0x0 0x2 0x0 0xb 0x0 127.0.0.1 0x0 ppsget.dll 0x0

            {type:[0,1,2,3,4],rmax:5,wb:"pc",text:"諾基亞 e63"} 0x0

            上面是用我設計的一種混合顯示格式顯示的包數據,可以看到里面帶上了ppsget.dll,這就是指定包內部名,其實還可以這樣ppsget.dll:getpage,如此一個dll就可支持多個IJobTask輸出,getpage只是獲得其中一個IJobTask接口(關于IJobTask接口參考上一篇云計算實踐2的文章)。具體命令是json格式,主要是為了方便信息傳輸和解析。Jobserver接收到該命令之后,調用ppsget.dllIJobTask接口中的split函數,將該任務分解,之后調度Tasknode執行,tasknode收到jobserver發過來的任務之后,檢查包名稱,如果缺少就會主動向jobserver要求發送相應的包,并進行部署,待部署完成之后從包獲取指定的IJobTask接口,執行該接口的map函數,將結果按照約定的格式發給jobserver,最后由jobserver調用IJobTask中的reduce函數進行打包,最后將結果發給jobowner并記錄相關Log

            上圖中還可看到一個HashCrackCloud.dll,這是另一個云計算環境下破解md5密碼的dll,這個上篇文章也寫了一下,這里就不詳述了。

             

            為使得tasknode可適應各種機器環境,我把tasknode設計為一個dll,該dll內部自己管理消息及任務執行,該dll可被加載到各種容器進程(如gui進程、console進程、service進程)等執行,看下我的tasknode和它的容器進程:

             

            這也算是我的得意設計吧,這樣設計的tasknodewindows系統下的確具有很高的靈活性。

            這樣的tasknode甚至可直接加載在jobserver進程,也可被任意win系列機器的任意進程加載參與運算,用主動加載或被動加載都很方便,極大的方便了云計算環境的部署,反正具體執行的任務都由package完成,tasknode只要按照約定的規則部署 package即可,所以這種云計算環境是非常輕量級又非常靈活的,開發一個新的任務只要做一個新的IJobTask即可,目前我這套體系除了沒有考慮太多安全性之外,這個云計算環境的實施還是非常容易的,實際上我們這個價格查詢的后臺云計算環境只用了不到2周的時間就開發完成。

            再看下jobserver記錄的每個joblog

             

            log中可很容易的分析出一個job每個task的執行情況,并可根據這些數據進行相應的優化處理。

            之所以把jobservertasknode以及package都寫出來,主要是為了表達一個看法,要實現一個簡單的云計算環境其實并不難,有經驗的團隊很容易就能做出來,參考下googlemap/reduce論文,按照自己的需要簡化實現,真理在實踐中,如果只是仰望googleamazon,那就真的是在云中霧里,另一個想要表達的就是云的形式是多種多樣的,并不一定amazonegoogle的云計算環境才是標準的,對實用派來說,形式都是次要的,實用才是關鍵的。

            posted @ 2010-10-03 14:23 袁斌 閱讀(1812) | 評論 (1)編輯 收藏

            2011年2月1日

            Windows下兩種iocp實現的差距

             

             

            之前幾天說過,因為經典iocp實現(以下簡稱經典實現)多個io線程綁定在一個iocp上,這樣內部管理了iocp隊列的處理,內部決定是不是需要線程切換,我上次修改的一個版本(以下簡稱實現2),用了多個io線程,每個iocp隊列僅綁定一個io線程,一組用戶共享一個io線程,這和經典的多線程epoll模型的做法是很相似的,這樣每個io線程是可以獨立控制了,但理論上這種做法沒有發揮iocp自動管理線程切換的優勢,昨晚沒事用這兩種實現分別做了個echoserver測試了一下,這兩套實現代碼僅40行左右不同,其他完全一樣,效果真的是差很多,測試僅用一個進程模擬了4000個客戶端,每秒1個包,先看實現2的,cpu14%2io線程,1accept線程,1個主線程,其他線程都沒干活閑置。

             

            Cpu

            Memory

            Threads

            handles

            14

            40088k

            8

            4236

             

            再看經典實現,cpu幾乎一直是0%2io線程,accept也是在io線程里面處理,其他跟實現2一樣,測試客戶端也一樣。

            Cpu

            Memory

            Threads

            handles

            0

            39244k

            7

            4336

             

            說實話,在測試之前我也沒想到有這么大的差距,經典實現就是1.2w個連接連上來還是這樣,就是內存占用多一點:

            Cpu

            Memory

            Threads

            handles

            0

            112068k

            7

            12280

             

            習慣上總有人喜歡拿epolliocp來對比,我到現在也沒看到真正公平的對比,就算是相對公平的也沒見到,因為在我看來,要對比硬件應該是一樣的,os都應該是最新的,最重要的是,server端程序應該都是發揮了各自優勢的,如果拿我這里的實現2去代表iocp的水平和epoll對比,勢必造成比epoll差很多的結果,然而這顯然是不正確的。

             

            epoll經典多線程模式實際實現和實現2很相似,理論上也有類似的線程切換問題,不知道效率怎樣。

             

             

            posted @ 2011-02-01 10:48 袁斌 閱讀(11954) | 評論 (3)編輯 收藏

            2011年1月30日

            回調函數的常見實現方式及速度比較

             

             

            回調函數實在是用得太廣泛,回調函數又有多種實現方式,如:

            1、  靜態函數

            2、  虛函數

            3、  函數對象

            4、  傳統c函數,通過一個void *傳遞對象地址,內部強制轉換

            5、  fastdelegate

            6、  Tr1::function + bind

            7、  Boost::Function + bind

            基本上速度是按照由快到慢的順序排列的,就是

            1 > 2 > 3 > 4 > 5 > 6 > 7

            其實234速度很接近,有的時候函數對象效率更高一點,基本上越是高級的方法使用起來越方便,但速度越慢,越是傳統的方法速度越快,呵呵,看來做server端程序要綜合考慮效率太新的東西還是要少用啊,還是用傳統的方法比較靠譜一點,當然如果調用次數不多的地方,使用更方便的方法還是好一些,畢竟我們要綜合權衡,而不能死板恪守教條。

             

             

            posted @ 2011-01-30 11:19 袁斌 閱讀(4589) | 評論 (3)編輯 收藏

            2011年1月26日

            一套網絡框架的杯具

            之前設計了一套網絡框架,持續改進了很多年,使用在很多項目上,綜合效率還行,也很穩定,一直以來對這套東西信心滿滿,總以為啥問題都好解決,但最近就有個需求讓我選擇還是改了下這個框架。

            之前的框架是這樣的,可以開一組Nio線程,可以開一組N個同步線程(默認1個),可以開一組N個異步線程(默認1個),可以開一組Ntimer線程(默認1個),可以開一組N個異步線程(默認cpu個),每組可獨立受控,每組可支持自定義消息,可支持timer,一組N個如果N大于1則無法直接給這組里面的特定線程發消息,只能給一組發消息,這個組里面會選擇某個合適的線程處理這個消息,這也是iocp高效和典型的用法了,但這也正是問題的結癥所在。

            Linux下的多線程服務器更常見的做法跟這個不大相似,一般都是將某些socket分配到某些線程epoll,分好之后就是固定的,不再變化,跟Iocpsocket綁定到一組線程的做法不同,由于某個socket直接綁定到了某個線程,所以有些問題就變得簡單了,如同一個連接的在同一個線程內消息進行了同步,要跟io線程綁定私有化數據也簡單了,而且每個線程可獨立受控,所以很容易實現一組io各自掛tls(線程局部存儲)數據,而我現在做的這套框架就是這方面不好控了,其實也很難說這兩種意義上的框架到底誰更優,如用在web型應用上這種socket被一組io線程管理的模式很方便效率也高,但我現在的需求需要某個socket使用線程相關數據,以避免數據之間的鎖,我用內存換時間,由于在原來的框架上增邏輯難以實現可直接控制io線程的框架的,所以花了一個晚上重新改寫了一套框架,在原來iocpframe的基礎上派生了一組帶2名稱的類,除替換類名之外只修改了幾十行代碼就做好了,總的來說花的時間還是比較少的。修改后io線程一組,但獨立受控,外部可對這組線程中的某一個直接發消息,基本滿足了需求,現在要給每個io線程綁定私有數據并觸發特定消息比之前簡單多了,而且絕對無鎖。

             

            posted @ 2011-01-26 16:14 袁斌 閱讀(2778) | 評論 (1)編輯 收藏

            2011年1月23日

             

            由于原先的appserver功能不斷增多,最近又增了兩個功能,需要不斷從后端memcached中提取數據并進行計算,由于提取數據量大且頻繁,導致效率很低,粗測了一下,獲取數據和格式化等操作花了90%以上的時間,由此設想將memcached改寫或重寫一個支持memcached的服務器,將計算功能和memcached做到一起,讓獲取數據的路徑最短,也就最大限度減少了數據傳輸和格式化等操作,就是類似存儲過程一樣啦,這部分可以考慮使用插件來實現,甚至可考慮使用腳本語言來實現。

            網上搜了一下,果然發現早有人這么干了,正所謂英雄所見啊,呵呵。具體方法倒很多,自定義key命名,根據特殊keygetsetreplace上做特殊操作,或者根據命令中的flag等做特殊處理,或者擴充stat命令等,都是可以的,我們暫時就考慮修改特殊的鍵值做特殊處理。

            要做一個完備的既支持ascii命令又支持binary命令的兼容memcached還是有一點點麻煩的,我暫時也沒有太多需求,所以就僅支持了ascii命令,其實也是考慮支持ascii的客戶端更多,各種語言的支持mc的客戶端數不勝數,但大多只支持ascii命令。由于我之前為了測試服務器框架效率,做過一個支持ascii命令的memcached兼容版本,因此拿過來直接使用太方便了,這個版本的實現其實很容易,如果有一個較好的框架代碼的話基本上在一天之內可做完,當然要做到很好可能需要多花一些時間,我現在做的也不是特好,要完全取代memcached使用還是有些差距,主要是一些過期機制等沒完全實現,雖然速度上比標準mc版本還要快一點,呵呵,因為暫時的確是不需要這些過期機制,所以也沒打算這個版本實現,其他功能基本上都有。

            以后準備將這個memcached解碼部分作為一個單獨的解析器,和支持其他協議一樣,換上這個解析那就支持mc協議了,還是很方便的,以后有空還是要做個支持binary協議的,以便可以更高效的解決問題。

            想到server能支持Memcached協議真是好啊,客戶端基本只要用個libmemcached就好了,多服務器分布,容錯,多份數據啥的都有現成的解決方案,只要把server做穩定了就基本ok了,對咱這種小團隊來說再合適不過了,節省了很多開發維護成本啊,現在內存這么便宜,部署幾個點實在是很easy的問題。

             

             

            posted @ 2011-01-23 17:13 袁斌 閱讀(2063) | 評論 (1)編輯 收藏

            2011年1月21日

             

            關于內存數據庫

             

            最近要將一些數據放到內存里面做很高的并發操作,考慮了很多方案,

            1、 簡單點使用map hash_map等自己管理。

            2、 sqlite內存表。

            3、 fastdb內存數據庫。

            4、 ExtremeDbTimesTen等。

            比較測試了一下123,發現還是自己實現速度最快,比fastdb模式快3-5倍,fastdb模式比sqlite內存表模式快10倍左右,由于自己實現不具有典型通用性,多線程下訪問效率會下降,要管理多線程下各種更新查找等還是比較麻煩的,所以在13方案之間糾結。

            為了使得決策更好一些,暫時還沒做決定,順便到萬方等上面搜索了一些論文來看,看來看去看得真來氣啊,雖然都叫內存數據庫但各種實現的都有,有用gdbm來做的,有直接map管理的,有hash管理數據的,有t樹管理的,有數組隊列管理的,有的明顯就是個不大變的東西還弄個啥事務的,靠,剛剛居然還看到一篇鳥文《電網監控系統實時數據庫的設計與實現》里面的測試居然是1000條,插入時間80毫秒,真可笑啊,區區這么點數據也好意思測,還要花80毫秒,還自以為很快,這個速度至少可提高1000倍以上啊,這幫垃圾,寫的啥鳥文章,研究個屁啊。

            看完這十來篇論文,俺的思緒又回到1999年,當年我給別人優化過一個電信計費的軟件(看的論文里面有好幾篇講電信計費的),當時有個朋友的朋友拿了個需求過來,7000萬條記錄,原來計算費單要花十幾個小時吧,我幫他改了下,十來分鐘就算完了,朋友很滿意,當時的做法很簡單,就是弄了個mmtable,大體就是跟map類似的東西吧,那個時候map還沒流行起來,俺也不知道,所以就自己弄了個內存表,內部基本就是二分查找了,那個時候我對hash都不大熟悉,B樹之類的算法剛接觸也不會用,就這么個東西當時的電腦也只要花十來分鐘,我估計就算是那個老程序放在現在的普通臺式機上要不了幾秒鐘就可算完。也不知道這么幾千萬條記錄的小需求怎么在這幫人眼里就成了什么海量數據,對俺來說跟玩似的,區區幾千萬嘛,不過是俺拿來測試用的。

            去年中做了個md5 hash反查的東西,數據都是幾百億到幾萬億的,后來的效果就是一個文件可存萬億記錄,一次查詢平均1.2IO,即使全放在SATA磁盤上也就十來毫秒而已。

            區區幾千萬條記錄咋就叫什么海量數據呢,海量個毛啊,內存都放得下的叫什么海量,現在服務器動不動都是幾十G內存,區區千萬根本算不上什么,查詢定位都可到微妙了,1秒插入至少千萬條了,居然還看到1000條插入的測試,真是不得不佩服國內這幫垃圾研究生的水平,也不知道這種論文咋就能通過審查,只能得出結論他們的老師也都是豬。

                     罵歸罵自己的問題還需要繼續努力,對咱目前的需求來說自己管理數據,即使一個線程都搞得定,因為不過區區幾個表,幾十萬條記錄而已,不過這種10年前咱就會的技術還真是拿不出手,怎么的也得做得更好一點,呵呵,繼續研究吧,多線程下內存數據庫,從概念上看的確是個很有吸引力的東西,要是性能跟得上,其實在很多地方可以取代普通的數據結構用法了,可以大大減少編程難度,甚至我在想如果有個支持事務的內存數據庫,之前設計的cad類軟件的undo/redo都可以用事務來實現,完全可以拋棄先前設計的復雜結構,其實這種東西即使不用內存數據庫就算是用個sqlite都完全能搞定,唉,往事不堪回首啊,看來數據庫方面的確得多花功夫,特別是多線程和分布式模式下的內存數據庫。

             

             

            posted @ 2011-01-21 13:37 袁斌 閱讀(8916) | 評論 (8)編輯 收藏

            2011年1月11日

             

             

            最近給自己換了個老板,忙了一段時間,所以有幾個月沒寫博客,今后還是要爭取多寫啊,呵呵。

             

            換來新地方,第一件大的事情就是修改后端架構和通信協議,架構也設計得很普通,因為這邊的業務不需要太過復雜的后端,所以就簡單設計了一下,基本是參照web的模型,符合我一貫的向web學習的思想,弄了個gate管理入口,相當于web下的webserver,后端其他服務器掛在該gate下,相當于web模型下的appserver,或者fastcgi模型的fastcgi進程,gate上管理連接、合法性檢測、登錄、加密、壓縮、緩存。Gate和后端通信本來想參照fastcgi協議,但看了之后覺得fastcgi協議還是復雜了,所以就設計了一個更簡單的協議,gate和后端server之間可傳遞key:value型數據對,value不局限于字符串,可以是任意數據,這樣基本滿足了當前的需求,第一版放上去之后也運行良好,到今天也基本持續穩定運行快一個月了,沒出過什么事情。由于在gate這邊緩沖了job管理,所以后端server升級很方便,隨時可關閉更新,gate會在窗口時間內將未執行完成的任務重新提交,有此功能可放心大膽的升級后端,這個月這樣的工作做了幾次,在架構修改之前這樣的事情幾乎是不敢做的,因為一旦升級所有用戶全部斷開連接,而現在用戶則基本無感覺。Gate上的緩存層為后端減少了一些壓力,這個緩存是按照請求的md5key做的,并根據協議配置時效,有此cache后端大多數服務可不設計緩存或降低緩存設計的復雜度。Gate上針對敏感數據統一做了加密處理,主要是辛辛苦苦整理的數據不能輕易讓競爭對手竊去了,呵呵。Gate也做了壓縮,現在是針對>=128長度的包進行壓縮,使用了qlz,壓縮效率還是很不錯的,速度很快。目前gate后端掛接的既有win上的server也有linux上的server,這是一開始就這么規劃的,現在看來當初的目的達到了,混合發揮各自的優勢,有的項目在原有系統上跑得好好的,沒必要重新開發嘛。

             

            協議設計上本來我是計劃二進制混合json格式,以二進制為主,但嘗試了一個協議之后發現,這邊的小伙子們對直接操縱內存普遍技術不過關,他們大多是從java開始的,后來才學習c,對字符串用得很熟練,權衡之下采用了json為主,混合二進制為輔的方案,這樣修改之后的協議和他們之前使用的xml類似,就是更小更緊湊一點,使用方法上很類似,從現在的效果看還行,使用json格式為主的協議當然不能跟使用pb之類的相比,解析效率上大約單線程每秒解析20來萬10obj的對象,速度上不算太快但也不算太慢,對付一秒至多幾萬數據包的應用來說還是夠的,因為現在cpu計算能力普遍過剩,使用json的另個好處就是增刪字段很方便,各個版本之間不需要太考慮版本的問題,要是全用二進制格式就要麻煩很多了,在使用壓縮之后,目前的json格式協議比之前的xml協議減少了2/3的帶寬使用,總體效果還是可以的。使用json調試也很方便,我提供了一個工具,寫后端的就直接用該工具按照json格式收發數據,無需等client開發好了再去做后端,之后做client也很方便,請求發過去之后返回來的就是標準的json格式數據,同樣的解析方法,每個不同的應用就按照不同的格式處理下即可,和web等模塊交互也很方便,這可算是額外的好處了。

             

            總之,雖然json格式存儲效率和解析效率跟二進制方式還差半個量級到一個量級,但合理使用還是可以的,特別是跟xml相比優勢很明顯,權衡使用吧,當然追求極致效率可能還是用pb之類的更合適一些,或者自己設計tlv格式。

             

            posted @ 2011-01-11 13:33 袁斌 閱讀(2532) | 評論 (3)編輯 收藏

            2010年10月3日

            昨天去見兩個老鄉,多年的朋友同學,也是搞技術的,大家都在上海,只是交流不是太多,聊起我做過的一些東西,他覺得不大相信,我說我寫的遠程控制程序全dll組成,所有模塊可熱升級,包括主模塊,主模塊小于20k,他似乎難于相信,我跟他說這個程序還是2001年做的,他就更難相信了。后來又說起我最近做的那個云計算的價格查詢,他也很難相信底下是云計算,由于沒帶機器也沒法給他看后臺服務器,所以我估計他最后還是半信半疑吧。上周另一個朋友說他們老板有個項目十來個人做了3年,一直做不穩定,我說給我一段時間我肯定能把他整穩定,后來給他看了我之前做的一些東西,游戲等,似乎他還在懷疑我的能力,這幾個其實都算是對我有些了解的朋友了,看來我還是宣傳得太少啊。我知道大家都對出身大公司的人有種崇拜,我等一直在小公司混的人沒什么人瞧得上,可是我又不能跟他們說哪年哪月,我到某個公司轉了下,看了某某寫的代碼,你所崇拜的人不過如此,哪個工程里面寫了個敗筆 等等

            可能我身邊也就合作伙伴、曾經的老板、同事、敵人知道我到底什么水平,2000年的時候就帶隊做幾十萬行的項目,連續做了幾個,為他們申請軟件企業奠定基礎。云計算的價格查詢,3周完成,帶2個客戶端的棋牌游戲帶了兩個朋友一起半年完成,全部模塊接口化,模塊可單獨升級。Netdongle,一個masterslave多重保護的網絡驗證系統,支持由控制端上傳dlldb等,也就一個月完成。這些程序上線之后就幾乎不用修改,一直穩定運行哦,一般的程序要做到第一版版本出去就幾乎不出錯是很難的,沒有一定功力的人是做不到的。

            昨天吹了下牛,我說windows下應用層的軟件基本沒有做不出來的,或許牛吹得有點大,那朋友驚訝了一下。

            今年濕疹治好之后發現自己戰斗力提升很多,之前做事情總覺得差一口氣,精力不濟,現在覺得精力充沛,酒也能喝了,活也能干了,速度也快了,也敢出去跟別人交流了,之前一直自卑,沒病真好啊。

            以后要多宣傳,多吹牛啊!

            posted @ 2010-10-03 14:26 袁斌 閱讀(658) | 評論 (0)編輯 收藏

            07年我寫了一篇文章叫《我的網絡模塊設計》,姑且叫那個為第一版吧,由于持續對網絡模塊進行改進,所以現在的實現和當時有很大改變,加上上層應用越來越多,又經過了幾年時間考驗,現在的實現方式比之前的更靈活更有效率,也因為最近看了一些人做網絡程序多年竟毫無建樹,一直要用別人寫的網絡模塊,所以有感而寫此文,為了使得此文不受上一篇《我的網絡模塊設計》的影響,我決定寫之前不看原來的文章,所以此文跟原文那篇文章可能沒有太多相似性。
             一個基本的網絡模塊,無非就是管理N個連接,快速處理每個連接的收發數據、消息等,所謂好的網路模塊,無非就是穩定、高效、靈活,下面分幾部分來寫:
             一、 連接管理
             之所以首先寫連接管理,是因為連接管理是核心,也是最難的地方,我寫第一個網絡庫之前,搜索過很多當時可以找到的例子工程,當時幾乎找不到可穩定運行的工程,當然更找不到好的,于是摸索前進,期間對連接管理使用了各種方法,從最早一個cs(臨界區CriticalSection,我簡稱cs),recv send都用這個cs,到后來send用一個cs,recv用一個cs,用多個的時候還出過錯,最后使用一個cs+一個原子值ref管理一個連接,每個連接send的時候用cs,recv的時候用ref,如果該連接的消息要跨線程異步執行,也使用ref,如此較簡單的解決了連接管理的問題。
             同樣使用生存期管理方法,也有人用智能指針,雖然原理和我直接操縱生存期一樣,但實現方法畢竟不同,不過我為了讓實現依賴少一些沒有引入智能指針。
             當然我后來也發現很多人不是用這種方法,如有些人就id來管理連接,每個連接分個id,其他操作全部用id,每次對連接的調用先翻譯一下,如果id找得到映射目標就調用,否則就說明該連接不存在了,這種方法簡單只是不直接,多了個查找過程,另外查找的時候可能還需要全局鎖(這依賴于連接數據組織)。
             也有人使用一個線程管理連接,其他所有與該連接有關的生存期問題全部到該線程處理,這樣也是可行的,只是需要做一個較好的包裝,如果包裝好上層調用方便,如果包裝不好,可能上層調用就有一些約束。
             雖然各種方法都有人使用,但我一直選擇直接的生存期管理方法,其實內部實現的時候還是有很多優化措施的,減少了大量addref、release的調用,進一步提高了效率。
             二、 線程組
             我最初做網絡庫的時候還不是很清楚上層如何使用這個庫,后來在上面做了幾個應用之后慢慢有了更多想法,最近的網絡庫是設計了這么幾組線程:io線程組、同步線程組、異步線程組、時鐘線程組、log線程組,每組線程都可開可關,就算io線程組也是可關的,這只是為了整個庫更靈活適用性更廣泛,如只用同步線程組或異步線程組僅將這個線程組當一個消息隊列使用。
             Io線程組就是處理io收發的,listen recv send 以及解密解壓縮都是在這組線程,一般這組線程會開2個或2*cpu個。
             同步線程組,一般這組線程開1個,用來處理logic。
             異步線程組,這組線程根據需要開0個或n個,簡單應用無db等慢速操作的應用不開,有很多db等慢速操作的可以開很多個。
             時鐘線程組,一般不開或開1個。
             Log線程組,一般開1個,主要為了避免其他線程調用WriteLog的時候被磁盤io阻塞,所以弄了一個log線程。
             其實還有一個主線程,我的每組線程(包括主線程)都支持事件和定時器,io線程、同步線程、異步線程組、時鐘線程組、甚至log線程組都支持事件和定時器,到去年我還只是讓每組線程都支持事件,今年為了更好的使用時鐘我給每組線程設計了定時器,現在定時器線程組有點雞肋的味道,一般是用不上專門的定時器線程組,不過我還沒有將它刪掉,主要在我的設計里面,它和同步異步線程組一樣,都只是一組線程,如果必要的時候可以將它用作同步線程或者異步線程組,所以繼續保留了它的存在。
             這幾組線程之間都是可互發消息的,所以一個邏輯要異步到別的線程執行是非常方便的,只要調用一下PostXXEvent(TlsInfo *ptls, DWORD dwEvent, WPARAM wParam, LPARAM lParam);我憑借這個設計使得這套網絡庫幾乎可以適用上層各種應用,不管是非常簡單的網絡應用還是復雜的,一框打盡。對最簡單的,一個io線程搞定,其他線程全關,對于復雜的io線程+同步+異步+log全開。
             三、 內存池
             內存池其實沒有想象中的那么神秘,當然如果要讓一個網絡程序持續7*24小時穩定高效運行,內存池幾乎必不可少的,內存池的作用首先是減少內存碎片,其次是為了提高速度,我想這兩點很容易想明白的,關于內存池我之前寫了系列文章,可參考我的博客:
             
            《內存池之引言》 http://blog.csdn.net/oldworm/archive/2010/02/04/5288985.aspx
             《單線程內存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289003.aspx
             《多線程內存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289006.aspx
             《dlmalloc、nedmalloc》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289010.aspx
             《線程關聯內存池》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289015.aspx
             《線程關聯內存池再提速》 http://blog.csdn.net/oldworm/archive/2010/02/04/5289018.aspx
             
            四、 定時器
             關于定時器,上面講線程組的時候已經講過,我現在的設計是每個線程(包括主線程)都支持定時器,調用方法都是一樣的,回調函數形式也是一樣的,由于定時器放到各組線程里面,所以減少了線程之間的切換,提高了效率。
             關于定時器,可參考《定時器模塊改造》 http://blog.csdn.net/oldworm/archive/2010/09/11/5877425.aspx
             
            五、 包格式
             關于包格式可參考《常用cs程序自定義數據包描述》 http://blog.csdn.net/oldworm/archive/2010/03/24/5413013.aspx
             
            六、 Buffer
             之前的文章其實我一直沒有提過我的buffer,其實我的buffer設計是很靈活的,現在它和pool也是有些關聯的,我的poolset其實底下就是按照各種不同大小的buffer預設的尺寸。Buffer我設計為循環式,不允許回繞,包含
             Char *pbase 塊基址
             Char *pread 當前讀指針
             Char *pwrite 當前寫指針
             DWORD tag;
             Buffer *next;
             Capacity 總分配尺寸,上面分配的時候可能只是指定了19,但實際可能分配的是32個字節,所以內部用的時候要根據capacity來最大限度的利用緩沖區。
             Buffer分配還利用了一個技巧,事實上分配的時候是一次分配一個需要的大緩沖,前面為Buffer自身的數據,后面為數據部分,pbase指向數據部分,這樣處理減少了一次分配,我估計很多人都在用這個技巧。
             Pwrite總是不會小于pread的,但pread可能和pbase不一樣,僅當后面空余空間不夠用的時候才可能會移動數據,否則數據不會移動。
             WSARecv的時候我是這么處理的,如果首次獲取了一個包的一部分,但buffer中還有足夠的空間放下包的剩余部分,我不會再分配一個buffer去recv,而是直接用原buffer指定一個合適的偏移和size去WSARecv,這樣可以最大限度的減少復制。
             剛才還有朋友問到我recv的層次組織,我的網絡庫里面是這樣組織的,OnRecv是個虛函數,最基礎的IocpClient的OnRecv只處理數據而不解析格式,IocpClientMsg就會認識默認的一種包格式,這個類的OnRecv會將m_recvbuf中的數據組織為msg,并盡可能的一次返回更多個msg,回調OnMsg函數,由上層決定該消息在哪個線程處理,這樣我認為是最靈活的,如果是個很小的server,可能直接就在io線程里面處理了,也可postevent到同步線程處理,亦可PostEvent到異步線程處理。
             
            七、 TLSINFO
             TlsInfo顧名思義就是每個線程關聯的一組數據,暫時我還沒有看到別人這么設計,也許我設計得有些復雜了,在這個數據里面有一些常用的和該線程相關的數據,如該線程的分配基、步長,用這兩個參數可讓每個線程制造出唯一序列,還有常用pool的地址,如tm_pool *p1k; tm_pool *p2k;… 這樣設計使得要分配的時候直接取tm_pool,最大限度的發揮了分配速度,還有一些常規參量long c; long d; DWORD a; DWORD b;… 這幾個值可理解為棧內值,其實為了減少上層調用復雜度的,如我將一個連接的包從io線程PostEvent到同步線程處理,PostEvent首參數就是tlsinfo,PostEvent會根據tlsinfo里面的一個內部值決定是不是要調用addref,因為我有個地方預增了2,所以大多數情況下在io發到其他線程的時候是無需調用addref的,提高了效率,tlsinfo里的其他一些值上層應用可使用,用在邏輯處理等情況下。
             
            八、 性能分析
             *nix下有很多知名的網絡庫,但在win下特別是使用iocp的庫里面,一直就沒有一個能作為基準的庫,即使asio也因為出來太晚不為大多數人熟悉而不能成為基準庫,libevent接iocp由于采用0 buffer模擬所以也沒有發揮出足夠的性能,對比spserver我比它快70%左右,我總在想要是微軟能將他那個iocp的例子寫得更好一點就好了,至少學的人有一個更高一點的基礎,而不至于讓http://www.codeproject.com/KB/IP/iocp_server_client.aspx這樣的垃圾代碼都能成為很多人的樣板。
             
            九、 雜談
             為了寫好一個win下穩定高效的網絡庫,我07年的時候幾乎搜遍了那個時間段之前所有能找到的iocp例子,還包括通過朋友等途徑看到的如snda等網絡庫,可惜真沒找到好的,大多數例子是只要多線程發起幾千個連接不斷發送數據馬上就死了,偶爾幾個不死的(包括snda的)只要隨機連接并斷開就會產生句柄泄漏,關閉所有連接之后句柄并不關閉等,也就是說這些例子連基本的生存期管理都沒搞定,能通過生存期管理并且不死的只有有限的幾個,可惜性能又太差,杯具啊。
             早年寫網絡庫的時候也加入了sodme在google上建的那個群,當時群還是很熱鬧的,可惜大多數人都是摸索,所以很多問題只是討論卻從無定論,沒有誰能說服別人,也沒有人可輕易被說服,要是現在或許有一些很有經驗的人,可惜那個群由于GFW現在雖能訪問也不大活躍了。
             最近看到有些寫網絡程序7年甚至更久的人還在用libevent、ace等感想很復雜,可悲的是那些人還沒意識到用一個庫和寫一個庫有多大的區別,可能那些人一輩子也認識不到寫一個庫比用一個庫難多少,那些人以為這些庫基本會用了,讓他自己去寫也基本是照這個模式,不會有什么突破,就無需自己動手了,悲哀啊。當然,要寫一個穩定的網絡庫需要耗費很多時間,特別是要寫一個能和知名庫性能接近或更好的庫,更是要費神費力,沒點耐心和持久力是不可能做好的。在中文領域隨便查什么稍有些名氣的代碼,總是能找到很多剖析類文章,可原創的東西總是很少,也不知道那些大俠怎么搞的,什么都能剖析可怎么總寫不出什么像樣的東西呢。
             其實本來沒有打算寫這篇文章,可能是看了陳碩的muduo才使得我有了寫出來的沖動,大概是受到他的開源鼓勵吧。
             謹以此文記錄本人最近3年對網絡模塊的修改并簡短總結。

             

            posted @ 2010-10-03 14:25 袁斌 閱讀(3253) | 評論 (5)編輯 收藏

            我的IOCP網絡模塊設計

             

            為了設計一個穩定易用高效的iocp網絡模塊,我前前后后花了好幾個月的時間,也曾閱讀過網上很多資料和代碼,但是非常遺憾,能找到的資料一般都說得很含糊,很少有具體的,能找到的代碼離真正能商用的網絡模塊差得太遠,大多只是演示一下最基本的功能,而且大多是有很多問題的,主要問題如下:

            1、 很多代碼沒有處理一次僅發送成功部分數據的情況。

            2、 幾乎沒有找到能正確管理所有資源的代碼。

            3、 大多沒有采用用pool,有的甚至畫蛇添足用什么map查找對應客戶端,沒有充分使用perhandle, perio

            4、 接收發送數據大多拷貝太多次數。

            5、 接收管理大多很低效,沒有充分發揮iocp能力。

            6、 幾乎都沒有涉及上層如何處理邏輯,也沒有提供相應解決方案(如合并io線程處理或單獨邏輯線程)。

            7、 大多沒有分離流數據和包數據。

            問題還有很多,就不一一列出來了,有一定設計經驗的人應該有同感。要真正解決這些問題也不是那么容易的,特別是在win下用iocp的時候資源釋放是個麻煩的問題,我在資源管理上花了很多時間,起初也犯了很多錯誤,后來在減少同步對象上又花了不少時間(起初client用了兩個同步對象,后來減少為1個)。下面我就我所設計的網絡模塊的各個部分進行簡單的講解

            一、內存管理。

            內存管理是采用池模式,設計了一個基礎池類,可以管理某固定大小的池

            class CBufferPool

            {

                   

                    void *newobj();

                    void delobj(void *pbuf);

                   

            };

            在基礎池類上提供了一個模板的對象池

            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);

                    }

            };

             

            在基礎池的基礎上定義了一個簡單的通用池

            class CMemoryPool

            {

            private:

                    CBufferPool bp[N];

            };

            通用池是由N個不同大小的基礎池組成的,分配的時候圓整到合適的相近基礎池并由基礎池分配。

            最后還提供了一個內存分配適配器類,從該類派生的類都支持內存池分配。

            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分配速度比CObjpool<>稍微慢一點點,所以我在用的時候就直接用t_alloc類派生,而不是用對象池,這是個風格問題,也許有很多人喜歡用更高效一點的objpool方式,但這個并不大礙。

             

            在網絡模塊中OVERLAPPED派生類就要用池進行分配,還有CIocpClient也要用池分配,再就是CBlockBuffer也是從池分配的。

            如下定義:

            struct IOCP_ACCEPTDATA : public IOCP_RECVDATA, public t_alloc

            class CIocpClient : public t_alloc

             

            二、數據緩沖區。

            數據緩沖區CBlockBuffer為環形,大小不固定,隨便分配多少,主要有以下幾個元素:

            Char *pbase;           //環形首部

            Char *pread;           //當前讀指針

            Char *pwrite;          //當前寫指針

            Int nCapacity;         //緩沖區大小

            Long nRef;              //關聯計數器

            用這種形式管理緩沖區有很多好處,發送數據的時候如果只發送了部分數據只要修改pread指針即可,不用移動數據,接收數據并處理的時候如果只處理了部分數據也只要修改pread指針即可,有新數據到達后直接寫到pwrite并修改pwrite指針,不用多次拷貝數據。nRef關聯計數還可處理一個包發給N個人的問題,如果要給N個人發送相同的包,只要分配一個緩沖區,并設置nRefN就可以不用復制N份。

             

            三、收發緩沖區管理

            發送緩沖區

            我把CIocpClient的發送數據設計為一個CBlockBuffer 的隊列,如果隊列內有多個則WSASend的時候一次發送多個,如果只有一個則僅發送一個,CIocpClient發送函數提供了兩個,分別是:

            Bool SendData(char *pdata, int len);

            Bool SendData(CBlockBuffer *pbuffer);

            第一個函數會檢測發送鏈的最后一個數據塊能否容納發送數據,如果能復制到最后一個塊,如果不能則分配一個CBlockBuffer掛到發送鏈最后面,當然這個里面要處理同步。

             

            接收緩沖區

            接收管理是比較簡單的,只有一個CBlockBufferWSARecv的時候直接指向CBlockBuffer->pwrite,所以如果塊大小合適的話基本上是不用拼包的,如果一次沒有收到一個完整的數據包,并且塊還有足夠空間容納剩余空間,那么再提交一個WSARecv讓起始緩沖指向CBlockBuffer->pwrite如此則收到一個完整數據包的過程都不用重新拼包,收到一個完整數據包之后可以調用虛函數讓上層進行處理。

            IocpClient層其實是不支持數據包的,在這個層次只有流的概念,這個后面會專門講解。

             

            四、IocpServer的接入部分管理

            我把IocpServer設計為可以支持打開多個監聽端口,對每個監聽端口接入用戶后調用IocpServer的虛函數分配客戶端:

                    virtual CIocpClient *CreateNewClient(int nServerPort)

            分配客戶端之后會調用IocpClient的函數 virtual void OnInitialize();分配內部接收和發送緩沖區,這樣就可以根據來自不同監聽端口的客戶端分配不同的緩沖區和其他資源。

             

            Accept其實是個可以有很多選擇的,最簡單的做法可以用一個線程+accept,當然這個不是高效的,也可以采用多個線程的領導者-追隨者模式+accept實現,還可以是一個線程+WSAAccept,或者多個線程的領導者-追隨者模式+WSAAccept模式,也可以采用AcceptEx模式,我是采用AcceptEx模式做的,做法是有接入后投遞一個AcceptEx,接入后重復利用此OVERLAPPED再投遞,這樣即使管理大量連接也只有起初的幾十個連接會分配 OVERLAPPED后面的都是重復利用前面分配的結構,不會導致再度分配。

             

            IocpServer還提供了一個虛函數

                    virtual bool CanAccept(const char *pip, int port){return true;}

            來管理是否接入某個ip:port 的連接,如果不接入直接會關閉該連接并重復利用此前分配的WSASocket

             

            五、資源管理

            Iocp網絡模塊最難的就是這個了,什么時候客戶端關閉或服務器主動關閉某個連接并收回資源,這是最難處理的問題,我嘗試了幾種做法,最后是采用計數器管理模式,具體做法是這樣的:

            CIocpClient2個計數變量

                    volatile long m_nSending;              //是否正發送中

                    volatile long m_nRef;                     //發送接收關聯字

            m_nSending表示是否有數據已WSASend中沒有返回

            m_nRef表示WSASendWSARecv有效調用未返回和

            在合適的位置調用

                    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的時候刪除客戶端(其實是將內存返回給內存池)。

             

            六、鎖使用

            鎖的使用至關重要,多了效率低下,少了不能解決問題,用多少個鎖在什么粒度上用鎖也是這個模塊的關鍵所在。

            IocpClient有一個鎖      DECLARE_SIGNEDLOCK_NAME(send);        //發送同步鎖

            這個鎖是用來控制發送數據鏈管理的,該鎖和前面提到的volatile long m_nSending;共同配合管理發送數據鏈。

            可能有人會說recv怎么沒有鎖同步,是的,recv的確沒有鎖,recv不用鎖是為了最大限度提高效率,如果和發送共一個鎖則很多問題可以簡化,但沒有充分發揮iocp的效率。Recv接收數據后就調用OnReceive虛函數進行處理。可以直接io線程內部處理,也可以提交到某個隊列由獨立的邏輯線程處理。具體如何使用完全由使用者決定,底層不做任何限制。

             

            七、服務器定時器管理

            服務器定義了如下定時器函數,利用系統提供的時鐘隊列進行管理。

                    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)

            設計思路是給每個定時器分配一個獨立的id,根據id可修改定時器的首次觸發時間和后續每次觸發時間,可根據id刪除定時器,也可遍歷定時器。定時器時間單位為毫秒。

             

            八、模塊類結構

            模塊中最重要的就是兩個類CIocpClientCIocpServer,其他有幾個類從這兩個類派生,圖示如下:

             

            圖表 1

             

            圖表 2

             

            CIocpClient是完全流式的,沒有包概念。CIocpMsgClientCIocpClient派生,內部支持包概念:

            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模板類支持內嵌入式定義,如在

            CMyDoc中可這樣定義

            CIocpMsgClientT<CMyDoc> client;

            后面可以調用client.AddMsg(UMSG_LOGIN, OnLogin);關聯一個類成員函數作為消息處理函數,使用很方便。

             

            CIocpServerT定義很簡單,從CIocpServer派生,重載了CreateNewClient函數

            template <class TClient>

            class CIocpServerT : public CIocpServer

            {

            public:

                    //如果CIocpClient派生了則也需要重載下面的函數,這里可以根據nServerPort分配不同的CIocpClient派生類

                    virtual CIocpClient *CreateNewClient(int nServerPort)

                    {

                            CIocpClient *pclient = new TClient;

                            return pclient;

                    }

            };

             

            八、應用舉例

             

            class CMyClient : public CIocpMsgClient

            {

            public:

                    CMyClient() : CIocpMsgClient()

                    {

                    }

                    virtual ~CMyClient()

            {

            }

                    virtual void OnConnect()

            {

            Printf(“用戶連接%s:%d連接到服務器\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());

            }

                    virtual void OnClose()

            {

            Printf(“用戶%s:%d關閉連接\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());

            }

                    virtual void OnMsg(PKHEAD *phead)

            {

                    SendData((const char *)phead, phead->len+PKHEADLEN);

            }

                    virtual void OnSend(DWORD dwbyte)

            {

            Printf(“成功發送%d個字符\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 遠端用戶%s:%d連接到本服務器.\r\n", pclient, pclient->m_socket,

                                    pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());

            }

                    virtual void OnClose(CIocpClient *pclient)

            {

                    printf("%p : %d 遠端用戶%s:%d退出.\r\n", pclient, pclient->m_socket,

                                    pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());

            }

                    virtual void OnTimer(int uniqueid)

            {

            If(uniqueid == 10)

            {

            }

            Else if(uniqueid == 60)

            {

            }

            }

            //這里可以根據nServerPort分配不同的CIocpClient派生類

                    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);

             

                    //第二個參數為0表示使用默認cpu*2io線程,>0表示使用該數目的io線程。

            //第三個參數為0表示使用默認cpu*4個邏輯線程,如果為-1表示不使用邏輯線程,邏輯在io線程內計算。>0則表示使用該數目的邏輯線程

                    server.StartServer("1000;2000;4000", 0, 0);

            }

             

            從示例可看出,對使用該網絡模塊的人來說非常簡單,只要派生兩個類,集中精力處理消息函數即可,其他內容內部全部包裝了。

             

            九、后記

            我研究iocp大概在2005年初,前一個版本的網絡模塊是用多線程+異步事件來做的,iocp網絡模塊基本成型在2005年中,后來又持續進行了一些改進,2005底進入穩定期,2006年又做了一些大的改動,后來又持續進行了一些小的改進,目前該模塊作為服務程序框架已經在很多項目中穩定運行了1年半左右的時間。在此感謝大寶、Chost ChengSunway等眾多網友,是你們的討論給了我靈感和持續改進的動力,也是你們的討論給了我把這些寫出來的決心。若此文能給后來者們一點點啟示我將甚感欣慰,若有錯誤歡迎批評指正。

             

            oldworm

            oldworm@21cn.com

            2007.9.24

            posted @ 2010-10-03 14:25 袁斌 閱讀(918) | 評論 (0)編輯 收藏

            It行業從業十幾年,雖然接觸的人并不多,但算上網絡上有點交往的人,也不算太少,閱讀過無數代碼,很容易得出這樣的感悟,國內it行業能說的人太多,能做的人太少,能說大意就是能說會道,說起來頭頭是道,從架構到體系到模塊到接口都能說得很專業,但實施起來就不行,不但架構做不好,接口定義不清,就連小小模塊也未見得可搞定,這樣的人實在是太多,之前一直以為一個項目組多幾個人總能加快點速度,現在終于明白,一個項目組加幾個不合適的人不但不能加速反而要降速,甚至直接導致項目開發失控、失敗。關鍵模塊如果讓一個不稱職的人去負責,最終該模塊可能需要耗費核心人員更多時間去修改,甚至要重寫,輕則導致項目延時,重則導致項目失敗。

            實施一個成功的it項目(只說技術不說市場),概括起來就是一句話:找合適的人做合適的項目。說起來容易做起來難啊,每個人都有他的領域,如果找來一個擅長a領域的讓他做b領域的項目也未必做得好,雖然有的人學習能力超強,但總歸是需要時間積累經驗的。見過聽說過很多開發失敗的例子,莫不如此,曾幫人家優化一個電信計費項目,原實施的人只會用數據庫,所有的計算全用數據庫實現,速度比其對手慢一個數量級以上,將計算需要的數據預裝入內存,之后全在內存里面查找計算,速度一下提高了上百倍,修改后速度領先其對手好幾倍,其實這個修改很容易,只要幾天時間就搞定了,還包括熟悉他們的數據及規則。 還幫別人看過一個棋牌的項目,原項目組十幾個人搞了1年多,整了40多萬行代碼,結果bug不斷,一直不能穩定運行,項目組無人能搞得定,我看了之后下的結論是重寫,他們傻眼了,還以為改一個項目的時間肯定要少,畢竟寫了那么多代碼,他們那些外行哪里知道,修改一個漏洞無數的工程哪有重寫快啊,這是典型的找了一群不恰當的人做了一個不恰當的項目,幾百萬投入打了水漂,要是讓一個有能力的人設計把關,他那棋牌項目100萬足夠開發得出來了。

            剛畢業那會做項目的時候帶過幾個水平較差的手下(都是俺領導招的人),他們最常說的話是“優化”,我把某某地方“優化”了一下,呵呵,外行要聽到這個還以為真的是優化呢,其實很多時候他只是改寫了一下,是不是算優化還值得商量,大多數時候都算不上優化,有的時候還改錯引入了更多bug。對高手來說更喜歡說重構,我把某某模塊重新設計了一下,以前模塊有哪些缺陷,重新設計一下之后這些缺陷就不存在了,還有某某好處,等等,高手著眼于大局,低手只能看到一個小角落,高手優化關鍵之處,低手優化無關緊要之處。看豆瓣網技術發展,就是一個不斷重構的過程,看qq98年開始到現在的蜂鳥內核,大的重構就3次了,完全重寫3次了。最近迅雷終于官方承認v6開發失敗,我之前在群里表達過我的觀點,我估計他們v6是開發失敗了,所以一直以v5頂著,現在等到v7出來終于承認了,這種自詡市值超100億美刀的公司這么長時間的開發還能失敗,就不要說小公司小團隊了,見光死的項目很多,未見光死的項目更多。

            國內真正懂得開發的老板很少,大多數老板覺得一個人開5w 10w的月薪太高了,這個待遇找1w的能找好幾個呢,其實他們不知道,it行業一個重要的人能頂100個普通的人,甚至不可比較,因為一個普通的人去做一個項目可能根本不能完成,成功為010這個比值是無窮大啊,可惜等老板們失敗了幾個項目之后才能悟出這個道理,it項目做得好和做不好的差別不是差一點的問題,而是10的問題。俗人總是忙忙碌碌,天天維護自己前一天制造的bug,看起來很敬業,高手總是懶懶散散,因為100天后可能出現的問題都已經了然于胸,于是整天看起來無所事事時,不懂的老板很可能青睞于前者而打壓后者。也正因為能說的人太多,懂行的老板又太少,所以使得整個行業充斥著浮躁和急功近利,很多關鍵職位其實只是個鸚鵡在頂著,有能力的人被壓制,悲哀啊。千里馬難找,伯樂更難找啊。

            posted @ 2010-10-03 14:24 袁斌 閱讀(852) | 評論 (2)編輯 收藏

            僅列出標題  下一頁
            久久久久高潮综合影院| 久久精品嫩草影院| 久久亚洲精品人成综合网| 久久青草国产手机看片福利盒子| 国产真实乱对白精彩久久| 久久亚洲av无码精品浪潮| 亚洲色欲久久久综合网| 99久久夜色精品国产网站| 麻豆久久久9性大片| 久久99精品国产麻豆宅宅| 亚洲欧美另类日本久久国产真实乱对白 | 国产—久久香蕉国产线看观看| 久久久久99这里有精品10| 伊人久久综在合线亚洲2019| 久久婷婷午色综合夜啪| 2021国产成人精品久久| 少妇内射兰兰久久| 久久只有这精品99| 久久91这里精品国产2020| 国产精品99久久免费观看| 综合久久精品色| 久久露脸国产精品| 一本大道加勒比久久综合| 久久精品国产亚洲av影院| 久久婷婷五月综合国产尤物app | 久久九九亚洲精品| 九九久久自然熟的香蕉图片| 伊人久久无码精品中文字幕| 狠狠久久综合伊人不卡| 国产精品岛国久久久久| 久久久精品2019免费观看| 国色天香久久久久久久小说 | 色播久久人人爽人人爽人人片aV| 午夜不卡888久久| 国产精品久久久久…| 久久久精品2019免费观看| 无码人妻久久一区二区三区免费 | 亚洲AV日韩AV天堂久久| 综合网日日天干夜夜久久| 日本强好片久久久久久AAA | 精品水蜜桃久久久久久久|