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

            loop_in_codes

            低調(diào)做技術(shù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

            #

            實(shí)現(xiàn)LUA腳本同步處理事件:LUA的coroutine

            author : Kevin Lynx

            需求

                受WOW的影響,LUA越來越多地被應(yīng)用于游戲中。腳本被用于游戲中主要用于策劃編寫游戲規(guī)則相關(guān)。實(shí)際運(yùn)用中,
            我們會將很多宿主語言函數(shù)綁定到LUA腳本中,使腳本可以更多地控制程序運(yùn)行。例如我們可以綁定NPCDialog之類的函數(shù)
            到LUA中,然后策劃便可以在腳本里控制游戲中彈出的NPC對話框。
                我們現(xiàn)在面臨這樣的需求:對于宿主程序而言,某些功能是不能阻塞程序邏輯的(對于游戲程序尤其如此),但是為
            了方便策劃,我們又需要讓腳本看起來被阻塞了。用NPCDialog舉個(gè)例子,在腳本中有如下代碼 :

                ret = NPCDialog( "Hello bitch" )
               
            if ret == OK then print("OK") end


                對于策劃而言,NPCDialog應(yīng)該是阻塞的,除非玩家操作此對話框,點(diǎn)擊OK或者關(guān)閉,不然該函數(shù)不會返回。而對于
            宿主程序C++而言,我們?nèi)绾螌?shí)現(xiàn)這個(gè)函數(shù)呢:

             

                static int do_npc_dialog( lua_State *L )
               
            {
                   
            const char *content = lua_tostring( L, -1 );
                   
                    lua_pushnumber( ret );
                   
            return 1;
                }


                顯然,該函數(shù)不能阻塞,否則它會阻塞整個(gè)游戲線程,這對于服務(wù)器而言是不可行的。但是如果該函數(shù)立即返回,那
            么它并沒有收集到玩家對于那個(gè)對話框的操作。
                綜上,我們要做的是,讓腳本感覺某個(gè)操作阻塞,但事實(shí)上宿主程序并沒有阻塞。

            事件機(jī)制

                一個(gè)最簡單的實(shí)現(xiàn)(對于C程序員而言也許也是優(yōu)美的),就是使用事件機(jī)制。我們將對話框的操作結(jié)果作為一個(gè)事件。
            腳本里事實(shí)上沒有哪個(gè)函數(shù)是阻塞的。為了處理一些“阻塞”函數(shù)的處理結(jié)果,腳本向宿主程序注冊事件處理器(同GUI事件
            處理其實(shí)是一樣的),例如腳本可以這樣:

                function onEvent( ret )
                   
            if ret == OK then print("OK") end
                end
               
            -- register event handler
                SetEventHandler(
            "onEvent" )
                NPCDialog(
            "Hello bitch")


                宿主程序保存事件處理器onEvent函數(shù)名,當(dāng)玩家操作了對話框后,宿主程序回調(diào)腳本中的onEvent,完成操作。
                事實(shí)上我相信有很多人確實(shí)是這么做的。這樣做其實(shí)就是把一個(gè)順序執(zhí)行的代碼流,分成了很多塊。但是對于sleep
            這樣的腳本調(diào)用呢?例如:

             

                --do job A
                sleep(
            10)
               
            --do job B
                sleep(
            10)
               
            --do job C
               


                那么采用事件機(jī)制將可能會把代碼分解為:

                function onJobA
                   
            --do job A
                    SetEventHandlerB(
            "onJobB")
                    sleep(
            10)
                end
                function onJobB
                   
            --do job B
                    SetEventHandlerC(
            "onJobC")
                end
                function onJobC
                   
            --do job C
                end
               
            -- script starts here
                SetEventHandlerA(
            "onJobA" )
                sleep(
            10)


                代碼看起來似乎有點(diǎn)難看了,最重要的是它不易編寫,策劃估計(jì)會抓狂的。我想,對于非專業(yè)程序員而言,程序的
            順序執(zhí)行可能理解起來更為容易。

            SOLVE IT

                我們的解決方案,其實(shí)只有一句話:當(dāng)腳本執(zhí)行到阻塞操作時(shí)(如NPCDialog),掛起腳本,當(dāng)宿主程序某個(gè)操作完
            成時(shí),讓腳本從之前的掛起點(diǎn)繼續(xù)執(zhí)行。
                這不是一種假想的功能。我在剛開始實(shí)現(xiàn)這個(gè)功能之前,以為LUA不支持這個(gè)功能。我臆想著如下的操作:
                腳本:
                ret = NPCDialog("Hello bitch")
                if ret == 0 then print("OK") end
                宿主程序:

                static int do_npc_dialog( lua_State *L )
               
            {
                   
                    lua_suspend_script( L );
                   
                }


                某個(gè)地方某個(gè)操作完成了:
                lua_resume_script( L );
                當(dāng)我實(shí)現(xiàn)了這個(gè)功能后,我猛然發(fā)現(xiàn),實(shí)際情況和我這里想的差不多(有點(diǎn)汗顏)。


            認(rèn)識Coroutine

                coroutine是LUA中類似線程的東西,但是它其實(shí)和fiber更相似。也就是說,它是一種非搶占式的線程,它的切換取決
            于任務(wù)本身,也就是取決你,你決定它們什么時(shí)候發(fā)生切換。建議你閱讀lua manual了解更多。
                coroutine支持的典型操作有:lua_yield, lua_resume,也就是我們需要的掛起和繼續(xù)執(zhí)行。
                lua_State似乎就是一個(gè)coroutine,或者按照LUA文檔中的另一種說法,就是一個(gè)thread。我這里之所以用’似乎‘是
            因?yàn)槲易约阂矡o法確定,我只能說,lua_State看起來就是一個(gè)coroutine。
                LUA提供lua_newthread用于手工創(chuàng)建一個(gè)coroutine,然后將新創(chuàng)建的coroutine放置于堆棧頂,如同其他new出來的
            對象一樣。網(wǎng)上有帖子說lua_newthread創(chuàng)建的東西與腳本里調(diào)用coroutine.create創(chuàng)建出來的東西不一樣,但是根據(jù)我
            的觀察來看,他們是一樣的。lua_newthread返回一個(gè)lua_State對象,所以從這里可以看出,“lua_State看起來就是一個(gè)
            coroutine”。另外,網(wǎng)上也有人說創(chuàng)建新的coroutine代價(jià)很大,但是,一個(gè)lua_State的代價(jià)能有多大?當(dāng)然,我沒做過
            測試,不敢多言。
                lua_yield用于掛起一個(gè)coroutine,不過該函數(shù)只能用于coroutine內(nèi)部,看看它的參數(shù)就知道了。
                lua_resume用于啟動一個(gè)coroutine,它可以用于coroutine沒有運(yùn)行時(shí)啟動之,也可以用于coroutine掛起時(shí)重新啟動
            之。lua_resume在兩種情況下返回:coroutine掛起或者執(zhí)行完畢,否則lua_resume不返回。
                lua_yield和lua_resume對應(yīng)于腳本函數(shù):coroutine.yield和coroutine.resume,建議你寫寫腳本程序感受下coroutine,
            例如:

                function main()
                    print(
            "main start")
                    coroutine.yield()
                    print(
            "main end")
                end
                co
            =coroutine.create( main );
                coroutine.resume(co)


            REALLY SOLVE IT

                你可能會想到,我們?yōu)槟_本定義一個(gè)main,然后在宿主程序里lua_newthread創(chuàng)建一個(gè)coroutine,然后將main放進(jìn)去,
            當(dāng)腳本調(diào)用宿主程序的某個(gè)’阻塞‘操作時(shí),宿主程序獲取到之前創(chuàng)建的coroutine,然后yield之。當(dāng)操作完成時(shí),再resume
            之。
                事實(shí)上方法是對的,但是沒有必要再創(chuàng)建一個(gè)coroutine。如之前所說,一個(gè)lua_State看上去就是一個(gè)coroutine,
            而恰好,我們始終都會有一個(gè)lua_State。感覺上,這個(gè)lua_State就像是main coroutine。(就像你的主線程)
                思路就是這樣,因?yàn)榫唧w實(shí)現(xiàn)時(shí),還是有些問題,所以我羅列每個(gè)步驟的代碼。
                初始lua_State時(shí)如你平時(shí)所做:

                lua_State *L = lua_open();
                luaopen_base( L );


                注冊腳本需要的宿主程序函數(shù)到L里:

                lua_pushcfunction( L, sleep );
                lua_setglobal( L,
            "my_sleep" );


                載入腳本文件并執(zhí)行時(shí)稍微有點(diǎn)不同:

                luaL_loadfile( L, "test.lua" );
            lua_resume( L,
            0 ); /* 調(diào)用resume */


                在你的’阻塞‘函數(shù)里需要掛起coroutine:

                return lua_yield( L, 0 );


                注意,lua_yield函數(shù)非常特別,它必須作為return語句被調(diào)用,否則會調(diào)用失敗,具體原因我也不清楚。而在這里,
            它作為lua_CFunction的返回值,會不會引發(fā)錯誤?因?yàn)閘ua_CFunction約定返回值為該函數(shù)對于腳本而言的返回值個(gè)數(shù)。
            實(shí)際情況是,我看到的一些例子里都這樣安排lua_yield,所以i do what they do。

                在這個(gè)操作完成后(如玩家操作了那個(gè)對話框),宿主程序需要喚醒coroutine:

                lua_resume( L, 0 );

             

                大致步驟就這些。如果你要單獨(dú)創(chuàng)建新的lua_State,反而會搞得很麻煩,我開始就是那樣的做的,總是實(shí)現(xiàn)不了自己
            預(yù)想中的效果。

            相關(guān)下載:
                例子程序中,我給了一個(gè)sleep實(shí)現(xiàn)。腳本程序調(diào)用sleep時(shí)將被掛起,宿主程序不斷檢查當(dāng)前時(shí)間,當(dāng)時(shí)間到時(shí),resume
            掛起的coroutine。下載例子

             

            8.13補(bǔ)充

               可能有時(shí)候,我們提供給腳本的函數(shù)需要返回一些值給腳本,例如NPCDialog返回操作結(jié)果,我們只需要在宿主程序里lua_resume

            之前push返回值即可,當(dāng)然,需要設(shè)置lua_resume第二個(gè)參數(shù)為返回值個(gè)數(shù)。

            2.9.2010
                lua_yield( L, nResults )第二個(gè)參數(shù)指定返回給lua_resume的值個(gè)數(shù)。如下:

               lua_pushnumber( L, 3 );
               
            return lua_yield( L, 1 );
             ..
               
            int ret = lua_resume( L, 0 );
               
            if( ret == LUA_YIELD )
               
            {
                     lua_Number r 
            = luaL_checknumber( L, -1 );
               }

            posted @ 2008-08-12 16:02 Kevin Lynx 閱讀(12869) | 評論 (14)編輯 收藏

            為http server加入CGI--網(wǎng)頁外觀的應(yīng)用程序

            Implement CGI in your httpd

            author : Kevin Lynx

            Purpose

                為我們的http server加入CGI的功能,并不是要讓其成為真正響應(yīng)CGI query功能的web server。正如klhttpd的開發(fā)初衷一樣,
            更多的時(shí)候我們需要的是一個(gè)嵌入式(embeded)的web server。

                而加入CGI功能(也許它算不上真正的CGI),則是為了讓我們與client擁有更多的交互能力。我們也許需要為我們的應(yīng)用程序
            加入一個(gè)具有網(wǎng)頁外觀的界面,也許這才是我的目的。

            Brief

                CGI,common gateway interface,在我看來它就是一個(gè)公共約定。客戶端瀏覽器提交表單(form)操作結(jié)果給服務(wù)器,服務(wù)器
            解析這些操作,完成這些操作,然后創(chuàng)建返回信息(例如一個(gè)靜態(tài)網(wǎng)頁),返回給瀏覽器。
                因?yàn)闉g覽器遵循了CGI發(fā)送請求的標(biāo)準(zhǔn),那么我們所要做的就是解析收到的請求,處理我們自己的邏輯,返回信息給客戶端即可。
            Detail...
                如果你要獲取CGI的更多信息,你可以在網(wǎng)上查閱相關(guān)信息。如果你要獲取web server對CGI標(biāo)準(zhǔn)的處理方式,我無法提供給你
            這些信息。不過我愿意提供我的臆測:
                處理CGI請求通常都是由腳本去完成(當(dāng)然也可以用任何可執(zhí)行程序完成)。就我所獲取的資料來看,只要一門語言具備基本的
            輸入輸出功能,它就可以被用作CGI腳本。瀏覽器以key-value的形式提交query string,一個(gè)典型的以GET方式提交query string的
            URL類似于:
            http://host/cgi-bin/some-script.cgi?name=kevin+lynx&age=22。不必拘泥于我上句話中出現(xiàn)的一些讓你產(chǎn)生問號的
            名詞。我可以告訴你,這里舉出的URL(也許更準(zhǔn)確的說法是URI)中問號后面的字符串即為一個(gè)query string。
                我希望你看過我的上一篇草文<實(shí)現(xiàn)自己的http server>,你應(yīng)該知道一個(gè)典型的GET請求時(shí)怎樣的格式,應(yīng)該知道什么是initial
            line,應(yīng)該知道HTTP請求有很多method,例如GET、POST、HEAD等。
                一個(gè)CGI請求通常使用GET或POST作為其request method。對于一個(gè)GET類型的CGI請求,一個(gè)典型的request類似于:
                GET /cgi-bin/some-script.cgi?name=kevin+lynx&age=22 HTTP/1.1
                other headers...
                ...
                我上面舉例的那個(gè)URL則可能出現(xiàn)在你的瀏覽器的地址欄中,它對我們不重要。
                而對于POST類型的CGI請求呢?query string只是被放到了optional body里,如:
                POST /cgi-bin/some-script.cgi HTTP/1.1
                other heads...
                ...
                name=kevin+lynx&age=22
                不管我說到什么,我希望你不要陷入概念的泥潭,我希望你能明確我在brief里提到的what can we do and how to do。

            How about the web browser ?

                我總是想引領(lǐng)你走在實(shí)踐的路上(on the practice way)。作為一個(gè)程序員,你有理由自負(fù),但是你沒任何理由小覷任何一個(gè)
            編程問題。打開你的編譯器,現(xiàn)在就敲下main。
                so,我們需要知道如何利用我們的瀏覽器,讓我們更為順手地實(shí)踐我們的想法。
                你也許會寫:<html><head><title>HTML test</title></head><body>it's a html</body></html>這樣的HTML標(biāo)記語言。但是
            這沒有利用上CGI。check out the url in the reference section and learn how to write FORMs.
                這個(gè)時(shí)候你應(yīng)該明白:

               <html>
                  
            <head>
                    
            <title>CGI test</title>
                  
            </head>
                  
            <body>
                    
            <FORM ACTION="./cgi-bin/test.cgi">
                       
            <INPUT TYPE="text" NAME="name" SIZE=20 VALUE="Your name">
                     
            </FORM>
                  
            </body>
                
            </html>

                
                加入<FORM>來添加表單,加入<INPUT TYPE="text"加一個(gè)edit box控件。那么當(dāng)你在里面輸入名字按下回車鍵時(shí),瀏覽器將整理
            出一個(gè)CGI query string發(fā)給服務(wù)器。
                我希望你能在瀏覽器里看到以問號打頭的key-value形式的字符串。

            So here we go...

                在你自己已有的http server的代碼基礎(chǔ)上,讓瀏覽器來請求一個(gè)包含F(xiàn)ORM的網(wǎng)頁,例如以上我舉例的html。當(dāng)瀏覽器將請求發(fā)
            給服務(wù)器的時(shí)候,看看服務(wù)器這邊收到的request。即使你猜到了什么,我也建議你看看,這樣你將看到一個(gè)真正的CGI query是怎么
            樣的。
                so you know,CGI請求本質(zhì)上也就是一連串的key-value字符串。真正的http server會將這些字符串傳給CGI腳本。腳本只需要
            輸出處理結(jié)果信息到標(biāo)準(zhǔn)輸出,服務(wù)器就會收集這些輸出然后發(fā)給客戶端。
                你也許在揣摩,我們的httpd如何地exec一個(gè)外部程序或腳本,如何等待腳本執(zhí)行完(需不需要異步?),然后如何收集標(biāo)準(zhǔn)輸
            出上的信息,然后是否需要同步地將這些信息送往客戶端。
                這很沒必要。如果我們的http server真的只是被embeded到其他程序的。我們只需要將query的邏輯處理交給上層模塊即可。

            What's the next..

                現(xiàn)在你知道什么是CGI query string,你也許稍微思考下就知道接下來你該如何去做(if you smart enough)。記住我們的目標(biāo),
            只是解析CGI query string,然后處理相關(guān)邏輯,返回處理結(jié)果信息給客戶端。
                所以接下來,呃,接下來,everything is up to you:分析那些惱人的字符串,將其從&的間隔中整理出來,得到一個(gè)key-value
            的表,將這個(gè)表給你的上層模塊,上層模塊根據(jù)query scritp決定執(zhí)行什么邏輯,根據(jù)這個(gè)表獲取需要的參數(shù),處理,將結(jié)果格式
            化為HTML之類的東西,then response。
                就這么簡單。

            Example...

                同樣,我提供了源碼,從而證明我不是紙上談兵夸夸其談。我希望我公開源碼的習(xí)慣能受到你的贊同。

               下載帶CGI的http server.

            Reference:

            http://hoohoo.ncsa.uiuc.edu/cgi/ (獲取服務(wù)器需要做的)
            http://www.ietf.org/rfc/rfc3875 (rfc cgi)
            http://www.cmis.brighton.ac.uk/~mas/mas/courses/html/html2.html (學(xué)會寫帶FORM的HTML)

            posted @ 2008-08-05 22:01 Kevin Lynx 閱讀(4209) | 評論 (8)編輯 收藏

            實(shí)現(xiàn)自己的http server

            Write your own http server

            author : Kevin Lynx

            Why write your own?

                看這個(gè)問題的人證明你知道什么是http server,世界上有很多各種規(guī)模的http server,為什么要自己實(shí)現(xiàn)一個(gè)?其實(shí)沒什么
            理由。我自己問自己,感覺就是在自己娛樂自己,或者說只是練習(xí)下網(wǎng)絡(luò)編程,或者是因?yàn)槟橙瘴铱吹侥硞€(gè)庫宣稱自己附帶一個(gè)小
            型的http server時(shí),我不知道是什么東西,于是就想自己去實(shí)現(xiàn)一個(gè)。

            What's httpd ?

                httpd就是http daemon,這個(gè)是類unix系統(tǒng)上的名稱,也就是http server。httpd遵循HTTP協(xié)議,響應(yīng)HTTP客戶端的request,
            然后返回response。
                那么,什么是HTTP協(xié)議?最簡單的例子,就是你的瀏覽器與網(wǎng)頁服務(wù)器之間使用的應(yīng)用層協(xié)議。雖然官方文檔說HTTP協(xié)議可以
            建立在任何可靠傳輸?shù)膮f(xié)議之上,但是就我們所見到的,HTTP還是建立在TCP之上的。
                httpd最簡單的response是返回靜態(tài)的HTML頁面。在這里我們的目標(biāo)也只是一個(gè)響應(yīng)靜態(tài)網(wǎng)頁的httpd而已(也許你愿意加入CGI
            特性)。

            More details about HTTP protocol

                在這里有必要講解HTTP協(xié)議的更多細(xì)節(jié),因?yàn)槲覀兊膆ttpd就是要去解析這個(gè)協(xié)議。
                關(guān)于HTTP協(xié)議的詳細(xì)文檔,可以參看rfc2616。但事實(shí)上對于實(shí)現(xiàn)一個(gè)簡單的響應(yīng)靜態(tài)網(wǎng)頁的httpd來說,完全沒必要讀這么一
            分冗長的文檔。在這里我推薦<HTTP Made Really Easy>,以下內(nèi)容基本取自于本文檔。

            - HTTP協(xié)議結(jié)構(gòu)
              HTTP協(xié)議無論是請求報(bào)文(request message)還是回應(yīng)報(bào)文(response message)都分為四部分:
              * 報(bào)文頭 (initial line )
              * 0個(gè)或多個(gè)header line
              * 空行(作為header lines的結(jié)束)
              * 可選body
              HTTP協(xié)議是基于行的協(xié)議,每一行以\r\n作為分隔符。報(bào)文頭通常表明報(bào)文的類型(例如請求類型),報(bào)文頭只占一行;header line
              附帶一些特殊信息,每一個(gè)header line占一行,其格式為name:value,即以分號作為分隔;空行也就是一個(gè)\r\n;可選body通常
              包含數(shù)據(jù),例如服務(wù)器返回的某個(gè)靜態(tài)HTML文件的內(nèi)容。舉個(gè)例子,以下是一個(gè)很常見的請求報(bào)文,你可以截獲瀏覽器發(fā)送的數(shù)據(jù)
              包而獲得:

                1  GET /index.html HTTP/1.1
                2  Accept-Language: zh-cn
                3  Accept-Encoding: gzip, deflate
                4  User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; MAXTHON 2.0)
                5  Host: localhost
                6  Connection: Keep-Alive
                7
              我為每一行都添加了行號,第1行就是initial line,2-6行是header lines,7行是一個(gè)header line的結(jié)束符,沒有顯示出來。
              以下是一個(gè)回應(yīng)報(bào)文:
                1  HTTP/1.1 200 OK
                2  Server: klhttpd/0.1.0
                3  Content-Type: text/html
                4  Content-Length: 67
                5
                6  <head><head><title>index.html</title></head><body>index.html</body>
              第6行就是可選的body,這里是index.html這個(gè)文件的內(nèi)容。

            - HTTP request method
              因?yàn)槲覀冏龅氖路?wù)器端,所以我們重點(diǎn)對請求報(bào)文做說明。首先看initial line,該行包含幾個(gè)字段,每個(gè)字段用空格分開,例
              如以上的GET /index.html HTTP/1.1就可以分為三部分:GET、/index.html、HTTP/1.1。其中第一個(gè)字段GET就是所謂的request
              method。它表明請求類型,HTTP有很多method,例如:GET、POST、HEAD等。

              就我們的目標(biāo)而言,我們只需要實(shí)現(xiàn)對GET和HEAD做響應(yīng)即可。

              GET是最普遍的method,表示請求一個(gè)資源。什么是資源?諸如HTML網(wǎng)頁、圖片、聲音文件等都是資源。順便提一句,HTTP協(xié)議
              中為每一個(gè)資源設(shè)置一個(gè)唯一的標(biāo)識符,就是所謂的URI(更寬泛的URL)。
              HEAD與GET一樣,不過它不請求資源內(nèi)容,而是請求資源信息,例如文件長度等信息。

            - More detail
              繼續(xù)說說initial line后面的內(nèi)容:
              對應(yīng)于GET和HEAD兩個(gè)method,緊接著的字段就是資源名,其實(shí)從這里可以看出,也就是文件名(相對于你服務(wù)器的資源目錄),例
              如這里的/index.html;最后一個(gè)字段表明HTTP協(xié)議版本號。目前我們只需要支持HTTP1.1和1.0,沒有多大的技術(shù)差別。

              然后是header line。我們并不需要關(guān)注每一個(gè)header line。我只羅列有用的header line :
              - Host : 對于HTTP1.1而言,請求報(bào)文中必須包含此header,如果沒有包含,服務(wù)器需要返回bad request錯誤信息。
              - Date : 用于回應(yīng)報(bào)文,用于客戶端緩存數(shù)據(jù)用。
              - Content-Type : 用于回應(yīng)報(bào)文,表示回應(yīng)資源的文件類型,以MIME形式給出。什么是MIME?它們都有自己的格式,例如:
                text/html, image/jpg, image/gif等。
              - Content-Length : 用于回應(yīng)報(bào)文,表示回應(yīng)資源的文件長度。

            body域很簡單,你只需要將一個(gè)文件全部讀入內(nèi)存,然后附加到回應(yīng)報(bào)文段后發(fā)送即可,即使是二進(jìn)制數(shù)據(jù)。

            - 回應(yīng)報(bào)文
              之前提到的一個(gè)回應(yīng)報(bào)文例子很典型,我們以其為例講解。首先是initial line,第一個(gè)字段表明HTTP協(xié)議版本,可以直接以請求
              報(bào)文為準(zhǔn)(即請求報(bào)文版本是多少這里就是多少);第二個(gè)字段是一個(gè)status code,也就是回應(yīng)狀態(tài),相當(dāng)于請求結(jié)果,請求結(jié)果
              被HTTP官方事先定義,例如200表示成功、404表示資源不存在等;最后一個(gè)字段為status code的可讀字符串,你隨便給吧。

              回應(yīng)報(bào)文中最好跟上Content-Type、Content-Length等header。

            具體實(shí)現(xiàn)
                正式寫代碼之前我希望你能明白HTTP協(xié)議的這種請求/回應(yīng)模式,即客戶端發(fā)出一個(gè)請求,然后服務(wù)器端回應(yīng)該請求。然后繼續(xù)
            這個(gè)過程(HTTP1.1是長連接模式,而HTTP1.0是短連接,當(dāng)服務(wù)器端返回第一個(gè)請求時(shí),連接就斷開了)。
                這里,我們無論客戶端,例如瀏覽器,發(fā)出什么樣的請求,請求什么資源,我們都回應(yīng)相同的數(shù)據(jù):

                           

            /* 阻塞地接受一個(gè)客戶端連接 */
                    SOCKET con 
            = accept( s, 00 ); 
                    
            /* recv request */
                    
            char request[1024= 0 };
                    ret 
            = recv( con, request, sizeof( request ), 0 );
                    printf( request );
                    
            /* whatever we recv, we send 200 response */
                    
            {
                        
            char content[] = "<head><head><title>index.html</title></head><body>index.html</body>";
                        
            char response[512];
                        sprintf( response, 
            "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n%s", strlen( content ), content );
                        ret 
            = send( con, response, strlen( response ), 0 );
                    }

                    closesocket( con ); 

             

                程序以最簡單的阻塞模式運(yùn)行,我們可以將重點(diǎn)放在協(xié)議的分析上。運(yùn)行程序,在瀏覽器里輸入http://localhost:8080/index.html
            ,然后就可以看到瀏覽器正常顯示content中描述的HTML文件。假設(shè)程序在8080端口監(jiān)聽。

               現(xiàn)在你基本上明白了整個(gè)工作過程,我們可以把代碼寫得更全面一點(diǎn),例如根據(jù)GET的URI來載入對應(yīng)的文件然后回應(yīng)給客戶端。
            其實(shí)這個(gè)很簡單,只需要從initial line里解析出(很一般的字符串解析)URI字段,然后載入對應(yīng)的文件即可。例如以下函數(shù):

            void http_response( SOCKET con, const char *request )
            {
                
            /* get the method */
                
            char *token = strtok( request, " " );
                
            char *uri = strtok( 0" " );
                
            char file[64];
                sprintf( file, 
            ".%s", uri ); 

                
            {
                    
            /* load the file content */
                    FILE 
            *fp = fopen( file, "rb" );
                    
            if( fp == 0 )
                    
            {
                        
            /* response 404 status code */
                        
            char response[] = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
                        send( con, response, strlen( response ), 
            0 );
                    }

                    
            else
                    
            {
                        
            /* response the resource */
                        
            /* first, load the file */
                        
            int file_size ;
                        
            char *content;
                        
            char response[1024];
                        fseek( fp, 
            0, SEEK_END );
                        file_size 
            = ftell( fp );
                        fseek( fp, 
            0, SEEK_SET );
                        content 
            = (char*)malloc( file_size + 1 );
                        fread( content, file_size, 
            1, fp );
                        content[file_size] 
            = 0

                        sprintf( response, 
            "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n%s", file_size, content );
                        send( con, response, strlen( response ), 
            0 );
                        free( content );
                    }

                }

            }
             



            其他

                要將這個(gè)簡易的httpd做完善,我們還需要注意很多細(xì)節(jié)。包括:對不支持的method返回501錯誤;對于HTTP1.1要求有Host這個(gè)
            header;為了支持客戶端cache,需要添加Date header;支持HEAD請求等。

                相關(guān)下載中我提供了一個(gè)完整的httpd library,純C的代碼,在其上加上一層資源載入即可實(shí)現(xiàn)一個(gè)簡單的httpd。在這里我將
            對代碼做簡要的說明:
                evbuffer.h/buffer.c : 取自libevent的buffer,用于緩存數(shù)據(jù);
                klhttp-internal.h/klhttp-internal.c :主要用于處理/解析HTTP請求,以及創(chuàng)建回應(yīng)報(bào)文;
                klhttp-netbase.h/klhttp-netbase.c :對socket api的一個(gè)簡要封裝,使用select模型;
                klhttp.h/klhttp.c :庫的最上層,應(yīng)用層主要與該層交互,這一層主要集合internal和netbase。
                test_klhttp.c :一個(gè)測試?yán)印?

            相關(guān)下載:
                klhttpd
                文中相關(guān)代碼

            參考資料:

            http://www.w3.org/Protocols/rfc2616/rfc2616.html
            http://jmarshall.com/easy/http/
            http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

            posted @ 2008-07-30 16:14 Kevin Lynx 閱讀(29774) | 評論 (21)編輯 收藏

            自己實(shí)現(xiàn)memcached客戶端庫

            Kevin Lynx

            7.21.2008

            What's memcached ?

            memcached是一個(gè)以key-value的形式緩存數(shù)據(jù)的緩存系統(tǒng)。通過將數(shù)據(jù)緩存到內(nèi)存中,從而提高數(shù)據(jù)的獲取速度。
            memcached以key-value的形式來保存數(shù)據(jù),你可以為你每一段數(shù)據(jù)關(guān)聯(lián)一個(gè)key,然后以后可以通過這個(gè)key獲取
            這段數(shù)據(jù)。

            memcached是一個(gè)庫還是什么?memcached其實(shí)是一個(gè)單獨(dú)的網(wǎng)絡(luò)服務(wù)器程序。它的網(wǎng)絡(luò)底層基于libevent,你可以
            將其運(yùn)行在網(wǎng)絡(luò)中的一臺服務(wù)器上,通過網(wǎng)絡(luò),在遵循memcached的協(xié)議的基礎(chǔ)上與memcached服務(wù)器進(jìn)行通信。

            What do we want to wrap ?

            我們需要做什么?我們只需要遵循memcached的協(xié)議(參見該文檔),封裝網(wǎng)絡(luò)層的通信,讓上層可以通過調(diào)用諸如
            add/get之類的接口即可實(shí)現(xiàn)往memcached服務(wù)器緩存數(shù)據(jù),以及取數(shù)據(jù)。上層程序員根本不知道這些數(shù)據(jù)在網(wǎng)絡(luò)
            上存在過。

            這個(gè)東西,也就是memcached官方所謂的client apis。你可以使用現(xiàn)成的客戶端庫,但是你也可以將這種重造輪子
            的工作當(dāng)作一次網(wǎng)絡(luò)編程的練習(xí)。it's up to you.:D

            Where to start ?

            很遺憾,對于windows用戶而言,memcached官方?jīng)]有給出一個(gè)可以執(zhí)行或者可以直接F7即可得到可執(zhí)行文件的下載
            (如果你是vc用戶)。幸運(yùn)的是,已經(jīng)有人做了這個(gè)轉(zhuǎn)換工作。

            你可以從http://jehiah.cz/projects/memcached-win32/這里下載到memcached的windows版本,包括可執(zhí)行程序和
            源代碼。

            我們直接可以運(yùn)行memcached.exe來安裝/開啟memcached服務(wù)器,具體步驟在以上頁面有所提及:

            安裝:memcached.exe -d install,這會在windows服務(wù)里添加一個(gè)memcached服務(wù)
            運(yùn)行:memcached.exe 
            -d start,你也可以通過windows的服務(wù)管理運(yùn)行。

               
            然后,你可以在任務(wù)管理器里看到一個(gè)'memcached'的進(jìn)程,很占內(nèi)存,因?yàn)檫@是memcached。

            So, here we go ...

            通過以上步驟運(yùn)行的memcached,默認(rèn)在11211端口監(jiān)聽(是個(gè)TCP連接,可以通過netstat查看)。接下來,我們就可
            以connect到該端口上,然后send/recv數(shù)據(jù)了。發(fā)送/接收數(shù)據(jù)只要遵循memcached的協(xié)議格式,一切都很簡單。

            使用最簡單的阻塞socket連接memcached服務(wù)器:

                   SOCKET s = socket( AF_INET, SOCK_STREAM, 0 );
                    
            struct sockaddr_in addr;
                    memset( 
            &addr, 0sizeof( addr ) );
                    addr.sin_family 
            = AF_INET;
                    addr.sin_port 
            = htons( 11211 );
                    addr.sin_addr.s_addr 
            = inet_addr( "127.0.0.1" ); 

                    ret 
            = connect( s, (struct sockaddr*&addr, sizeof( addr ) );
                    
            if( ret == 0 )
                    
            {
                        printf( 
            "connect ok\n" );
                    }
             

                   
            About the protocol

            簡單地提一下memcached的協(xié)議。

            可以說,memcached的協(xié)議是基于行的協(xié)議,因?yàn)闊o論是客戶端請求還是服務(wù)器端應(yīng)答,都是以"\r\n"作為結(jié)束符。
            memcached的協(xié)議將數(shù)據(jù)(send/recv操作的數(shù)據(jù))分為兩種類型:命令和用戶數(shù)據(jù)。

            命令用于服務(wù)器和客戶端進(jìn)行交互;而用戶數(shù)據(jù),很顯然,就是用戶想要緩存的數(shù)據(jù)。

            關(guān)于用戶數(shù)據(jù),你只需要將其編碼成字節(jié)流(說白了,只要send函數(shù)允許即可),并附帶數(shù)據(jù)結(jié)束標(biāo)志"\r\n"發(fā)送即可。

            關(guān)于命令,memcached分為如下幾種命令:存儲數(shù)據(jù)、刪除數(shù)據(jù)、取出數(shù)據(jù)、其他一些獲取信息的命令。其實(shí)你換個(gè)角度
            想想,memcached主要就是將數(shù)據(jù)存儲到內(nèi)存里,所以命令也多不了哪去,基本就停留在add/get/del上。

            關(guān)于key,memcached用key來標(biāo)識數(shù)據(jù),每一個(gè)key都是一個(gè)不超過255個(gè)字符的字符串。

            到這里,你可以發(fā)現(xiàn)memcached對于數(shù)據(jù)的存儲方式(暴露給上層)多少有點(diǎn)像std::map,如果你愿意,你可以將客戶端
            API封裝成map形式。= =#

            具體實(shí)現(xiàn)

            接下來可以看看具體的實(shí)現(xiàn)了。

            首先看看存儲數(shù)據(jù)命令,存儲數(shù)據(jù)命令有:add/set/replace/append/prepend/cas。存儲命令的格式為:
            <command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
            具體字段的含義參看protocol.txt文件,這里我對set舉例,如下代碼,阻塞發(fā)送即可:

                    char cmd[256] ;
                    
            char data[] = "test data";
                    sprintf( cmd, 
            "set TestKey 0 0 %d\r\n", strlen( data ) );
                    ret 
            = send( s, cmd, strlen( cmd ), 0 ); 


            注意:noreply選項(xiàng)對于有些memcached版本并不被支持,例如我們使用的1.2.2版本。注意官方的changelog即可。

            當(dāng)你發(fā)送了存儲命令后,memcached會等待客戶端發(fā)送數(shù)據(jù)塊。所以我們繼續(xù)發(fā)送數(shù)據(jù)塊:

             

                    ret = send( s, data, strlen( data ), 0 );
                    ret 
            = send( s, "\r\n"20 ); // 數(shù)據(jù)結(jié)束符

             

            然后,正常的話,memcached服務(wù)器會返回應(yīng)答信息給客戶端。

             

                    char reply[256];
                    ret 
            = recv( s, reply, sizeof( reply ) - 10 );
                    reply[ret] 
            = 0;
                    printf( 
            "server reply : %s\n", reply ); 

             

            如果存儲成功,服務(wù)器會返回STORED字符串。memcached所有應(yīng)答信息都是以字符串的形式給出的。所以可以直接printf出來。

            關(guān)于其他的操作,我就不在這里列舉例子了。我提供了我封裝的memcached客戶端庫的完整代碼下載,使用的是阻塞socket,
            對應(yīng)著memcached的協(xié)議看,很容易看懂的。

            It's a story about a programmer...

            最近發(fā)覺自己有點(diǎn)極端,要么寫純C的代碼,要么寫滿是template的泛型代碼。

             

            相關(guān)代碼下載

            posted @ 2008-07-21 15:54 Kevin Lynx 閱讀(6374) | 評論 (5)編輯 收藏

            libevent 源碼分析:min_heap帶來的超時(shí)機(jī)制

            author : Kevin Lynx

            什么是min heap ?

                首先看什么是heap,heap是這樣一種數(shù)據(jù)結(jié)構(gòu):1.它首先是一棵完成二叉樹;2.父親節(jié)點(diǎn)始終大于(或其他邏輯關(guān)系
            )其孩子節(jié)點(diǎn)。根據(jù)父親節(jié)點(diǎn)與孩子節(jié)點(diǎn)的這種邏輯關(guān)系,我們將heap分類,如果父親節(jié)點(diǎn)小于孩子節(jié)點(diǎn),那么這個(gè)heap
            就是min heap。
                就我目前所看到的實(shí)現(xiàn)代碼來看,heap基本上都是用數(shù)組(或者其他的連續(xù)存儲空間)作為其存儲結(jié)構(gòu)的。這可以保證
            數(shù)組第一個(gè)元素就是heap的根節(jié)點(diǎn)。這是一個(gè)非常重要的特性,它可以保證我們在取heap的最小元素時(shí),其算法復(fù)雜度為
            O(1)。
                原諒我的教科書式說教,文章后我會提供我所查閱的比較有用的關(guān)于heap有用的資料。

            What Can It Do?
                libevent中的min_heap其實(shí)不算做真正的MIN heap。它的節(jié)點(diǎn)值其實(shí)是一個(gè)時(shí)間值。libevent總是保證時(shí)間最晚的節(jié)
            點(diǎn)為根節(jié)點(diǎn)。
                libevent用這個(gè)數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)IO事件的超時(shí)控制。當(dāng)某個(gè)事件(libevent中的struct event)被添加時(shí)(event_add),
            libevent將此事件按照其超時(shí)時(shí)間(由用戶設(shè)置)保存在min_heap里。然后libevent會定期地去檢查這個(gè)min_heap,從而實(shí)現(xiàn)
            了超時(shí)機(jī)制。

            實(shí)現(xiàn)

                min_heap相關(guān)源碼主要集中在min_heap.h以及超時(shí)相關(guān)的event.c中。
                首先看下min_heap的結(jié)構(gòu)體定義:

            typedef struct min_heap
            {
                
            struct event** p;
                unsigned n, a;
            }
             min_heap_t;


                p指向了一個(gè)動態(tài)分配的數(shù)組(隨便你怎么說,反正它是一個(gè)由realloc分配的連續(xù)內(nèi)存空間),數(shù)組元素為event*,這也是
            heap中的節(jié)點(diǎn)類型。這里libevent使用連續(xù)空間去保存heap,也就是保存一棵樹。因?yàn)閔eap是完成樹,所以可以保證其元素在
            數(shù)組中是連續(xù)的。n表示目前保存了多少個(gè)元素,a表示p指向的內(nèi)存的尺寸。
                struct event這個(gè)結(jié)構(gòu)體定義了很多東西,但是我們只關(guān)注兩個(gè)成員:min_heap_idx:表示該event保存在min_heap數(shù)組中
            的索引,初始為-1;ev_timeout:該event的超時(shí)時(shí)間,將被用于heap操作中的節(jié)點(diǎn)值比較。

                接下來看幾個(gè)與堆操作不大相關(guān)的函數(shù):
                min_heap_elem_greater:比較兩個(gè)event的超時(shí)值大小。
                min_heap_size:返回heap元素值數(shù)量。
                min_heap_reserve:調(diào)整內(nèi)存空間大小,也就是調(diào)整p指向的內(nèi)存區(qū)域大小。凡是涉及到內(nèi)存大小調(diào)整的,都有一個(gè)策略問
            題,這里采用的是初始大小為8,每次增大空間時(shí)以2倍的速度增加。
                看幾個(gè)核心的:真正涉及到heap數(shù)據(jù)結(jié)構(gòu)操作的函數(shù):往堆里插入元素、從堆里取出元素:
                相關(guān)函數(shù)為:min_heap_push、min_heap_pop、min_heap_erase、min_heap_shift_up_、min_heap_shift_down_。

            heap的核心操作:

            - 往堆里插入元素:
                往堆里插入元素相對而言比較簡單,如圖所示,每一次插入時(shí)都從樹的最右最下(也就是葉子節(jié)點(diǎn))開始。然后比較即將插入
            的節(jié)點(diǎn)值與該節(jié)點(diǎn)的父親節(jié)點(diǎn)的值,如果小于父親節(jié)點(diǎn)的話(不用在意這里的比較規(guī)則,上下文一致即可),那么交換兩個(gè)節(jié)點(diǎn),
            將新的父親節(jié)點(diǎn)與其新的父親節(jié)點(diǎn)繼續(xù)比較。重復(fù)這個(gè)過程,直到比較到根節(jié)點(diǎn)。

              heap_add
                libevent實(shí)現(xiàn)這個(gè)過程的函數(shù)主要是min_heap_shift_up_。每一次min_heap_push時(shí),首先檢查存儲空間是否足夠,然后直接
            調(diào)用min_heap_shift_up_插入。主要代碼如下:

            void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct event* e)
            {
                
            /* 獲取父節(jié)點(diǎn) */
                unsigned parent 
            = (hole_index - 1/ 2;
                
            /* 只要父節(jié)點(diǎn)還大于子節(jié)點(diǎn)就循環(huán) */
                
            while(hole_index && min_heap_elem_greater(s->p[parent], e))
                
            {
                    
            /* 交換位置 */
                    (s
            ->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
                    hole_index 
            = parent;
                    parent 
            = (hole_index - 1/ 2;
                }

                (s
            ->p[hole_index] = e)->min_heap_idx = hole_index;



            - 從堆里取元素:

                大部分時(shí)候,從堆里取元素只局限于取根節(jié)點(diǎn),因?yàn)檫@個(gè)節(jié)點(diǎn)是最有用的。對于數(shù)組存儲結(jié)構(gòu)而言,數(shù)組第一個(gè)元素即為根
            節(jié)點(diǎn)。取完元素后,我們還需要重新調(diào)整整棵樹以使其依然為一個(gè)heap。
                這需要保證兩點(diǎn):1.依然是完成樹;2.父親節(jié)點(diǎn)依然小于孩子節(jié)點(diǎn)。
                在具體實(shí)現(xiàn)heap的取元素操作時(shí),具體到代碼層次,方法都是有那么點(diǎn)微小差別的。libvent里的操作過程大致如圖所示(實(shí)際上libevent中父節(jié)點(diǎn)的時(shí)間值小于子節(jié)點(diǎn)的時(shí)間值,時(shí)間值的比較通過evutil_timercmp實(shí)現(xiàn)):

            heap_remove
                主要過程分為兩步:
                1.比較左右孩子節(jié)點(diǎn),選擇最大的節(jié)點(diǎn),移到父親節(jié)點(diǎn)上;按照這種方式處理被選擇的孩子節(jié)點(diǎn),直到?jīng)]有孩子節(jié)點(diǎn)為止。例如,
                當(dāng)移除了100這個(gè)節(jié)點(diǎn)后,我們在100的孩子節(jié)點(diǎn)19和36兩個(gè)節(jié)點(diǎn)里選擇較大節(jié)點(diǎn),即36,將36放置到100處;然后選擇原來的36的
            左右孩子25和1,選25放置于原來的36處;
                2.按照以上方式處理后,樹會出現(xiàn)一個(gè)空缺,例如原先的25節(jié)點(diǎn),因?yàn)楸灰苿拥皆鹊?6處,25處就空缺了。因此,為了保證完
            成性,就將最右最下的葉子節(jié)點(diǎn)(也就是連續(xù)存儲結(jié)構(gòu)中最后一個(gè)元素),例如這里的7,移動到空缺處,然后按照插入元素的方式處理
            新插入的節(jié)點(diǎn)7。
                完成整個(gè)過程。

                libevent完成這個(gè)過程的函數(shù)主要是min_heap_shift_down_:

            /* hole_index 為取出的元素的位置,e為最右最下的元素值 */
            void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct event* e)
            {
                
            /* 取得hole_index的右孩子節(jié)點(diǎn)索引 */
                unsigned min_child 
            = 2 * (hole_index + 1);
                
            while(min_child <= s->n)
                
            {
                    
            /* 有點(diǎn)惡心的一個(gè)表達(dá)式,目的就是取兩個(gè)孩子節(jié)點(diǎn)中較大的那個(gè)孩子索引 */
                    min_child 
            -= min_child == s->|| min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]);
                    
            /* 找到了位置,這里似乎是個(gè)優(yōu)化技巧,不知道具體原理 */
                    
            if(!(min_heap_elem_greater(e, s->p[min_child])))
                        
            break;
                    
            /* 換位置 */
                    (s
            ->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index;
                    
            /* 重復(fù)這個(gè)過程 */
                    hole_index 
            = min_child;
                    min_child 
            = 2 * (hole_index + 1);
                }

                
            /* 執(zhí)行第二步過程,將最右最下的節(jié)點(diǎn)插到空缺處 */
                min_heap_shift_up_(s, hole_index,  e);
            }
             


            STL中的heap

            值得一提的是,STL中提供了heap的相關(guān)操作算法,借助于模板的泛化特性,其適用范圍非常廣泛。相關(guān)函數(shù)為:
            make_heap, pop_heap, sort_heap, is_heap, sort 。其實(shí)現(xiàn)原理同以上算法差不多,相關(guān)代碼在algorithm里。SGI的
            STL在stl_heap.h里。

            參考資料:

            What is a heap?

            Heap_(data_structure)

            Heap Remove

            posted @ 2008-07-18 15:56 Kevin Lynx 閱讀(6643) | 評論 (6)編輯 收藏

            libevent 源碼分析:evbuffer緩沖(附帶libevent vs2005完整包下載)

            Author : Kevin Lynx

            前言

                可以說對于任何網(wǎng)絡(luò)庫(模塊)而言,一個(gè)緩沖模塊都是必不可少的。緩沖模塊主要用于緩沖從網(wǎng)絡(luò)接收到的數(shù)據(jù),以及
            用戶提交的數(shù)據(jù)(用于發(fā)送)。很多時(shí)候,我們還需要將網(wǎng)絡(luò)模塊層(非TCP層)的這些緩沖數(shù)據(jù)拷貝到用戶層,而這些內(nèi)存拷貝
            都會消耗時(shí)間。
                在這里,我簡要分析下libevent的相關(guān)代碼(event.h和buffer.c)。

            結(jié)構(gòu)

                關(guān)于libevent的緩沖模塊,主要就是圍繞evbuffer結(jié)構(gòu)體展開。先看下evbuffer的定義:

            /*event.h*/
            struct evbuffer {
                u_char 
            *buffer;
                u_char 
            *orig_buffer; 

                size_t misalign;
                size_t totallen;
                size_t off; 

                
            void (*cb)(struct evbuffer *, size_t, size_t, void *);
                
            void *cbarg;
            }
            ;


                libevent的緩沖是一個(gè)連續(xù)的內(nèi)存區(qū)域,其處理數(shù)據(jù)的方式(寫數(shù)據(jù)和讀數(shù)據(jù))更像一個(gè)隊(duì)列操作方式:從后寫入,從前
            讀出。evbuffer分別設(shè)置相關(guān)指針(一個(gè)指標(biāo))用于指示讀出位置和寫入位置。其大致結(jié)構(gòu)如圖:

            evbuffer_str
                orig_buffer指向由realloc分配的連續(xù)內(nèi)存區(qū)域,buffer指向有效數(shù)據(jù)的內(nèi)存區(qū)域,totallen表示orig_buffer指向的內(nèi)存
            區(qū)域的大小,misalign表示buffer相對于orig_buffer的偏移,off表示有效數(shù)據(jù)的長度。

            實(shí)際運(yùn)作

                這里我將結(jié)合具體的代碼分析libevent是如何操作上面那個(gè)隊(duì)列式的evbuffer的,先看一些輔助函數(shù):

            evbuffer_drain:
                該函數(shù)主要操作一些指標(biāo),當(dāng)每次從evbuffer里讀取數(shù)據(jù)時(shí),libevent便會將buffer指針后移,同時(shí)增大misalign,減小off,
            而該函數(shù)正是做這件事的。說白了,該函數(shù)就是用于調(diào)整緩沖隊(duì)列的前向指標(biāo)。

            evbuffer_expand:
                該函數(shù)用于擴(kuò)充evbuffer的容量。每次向evbuffer寫數(shù)據(jù)時(shí),都是將數(shù)據(jù)寫到buffer+off后,buffer到buffer+off之間已被
            使用,保存的是有效數(shù)據(jù),而orig_buffer和buffer之間則是因?yàn)樽x取數(shù)據(jù)移動指標(biāo)而形成的無效區(qū)域。
                evbuffer_expand的擴(kuò)充策略在于,首先判斷如果讓出orig_buffer和buffer之間的空閑區(qū)域是否可以容納添加的數(shù)據(jù),如果
            可以,則移動buffer和buffer+off之間的數(shù)據(jù)到orig_buffer和orig_buffer+off之間(有可能發(fā)生內(nèi)存重疊,所以這里移動調(diào)用的
            是memmove),然后把新的數(shù)據(jù)拷貝到orig_buffer+off之后;如果不可以容納,那么重新分配更大的空間(realloc),同樣會移動
            數(shù)據(jù)。
                擴(kuò)充內(nèi)存的策略為:確保新的內(nèi)存區(qū)域最小尺寸為256,且以乘以2的方式逐步擴(kuò)大(256、512、1024、...)。

                了解了以上兩個(gè)函數(shù),看其他函數(shù)就比較簡單了。可以看看具體的讀數(shù)據(jù)和寫數(shù)據(jù):

            evbuffer_add:
                該函數(shù)用于添加一段用戶數(shù)據(jù)到evbuffer中。很簡單,就是先判斷是否有足夠的空閑內(nèi)存,如果沒有則調(diào)用evbuffer_expand
            擴(kuò)充之,然后直接memcpy,更新off指標(biāo)。

            evbuffer_remove:
                該函數(shù)用于將evbuffer中的數(shù)據(jù)復(fù)制給用戶空間(讀數(shù)據(jù))。簡單地將數(shù)據(jù)memcpy,然后調(diào)用evbuffer_drain移動相關(guān)指標(biāo)。

            其他

                回過頭看看libevent的evbuffer其實(shí)是非常簡單的(跟我那個(gè)kl_net里的buffer一樣),不知道其他人有沒有更優(yōu)的緩沖管理
            方案。evbuffer還提供了兩個(gè)函數(shù):evbuffer_write和evbuffer_read,用于直接在套接字(其他文件描述符)上寫/讀數(shù)據(jù)。

                另外,關(guān)于libevent,因?yàn)楣俜教峁┑腣C工程文件有問題,很多人在windows下編譯不過。金慶曾提供過一種方法。其實(shí)主要
            就是修改event-config.h文件,修改編譯相關(guān)配置。這里我也提供一個(gè)解決步驟,順便提供完整包下載:

            1. vs2005打開libevent.dsw,轉(zhuǎn)換四個(gè)工程(event_test, libevent, signal_test, time_test)
            2. 刪除libevent項(xiàng)目中所有的文件,重新添加文件,文件列表如下:
               buffer.c
               evbuffer.c
               evdns.c
               evdns.h
               event.c
               event.h
               event_tagging.c
               event-config.h
               event-internal.h
               evhttp.h
               evrpc.h
               evrpc.c
               evrpc-internal.h
               evsignal.h
               evutil.c
               evutil.h
               http.c
               http-internal.h
               log.c
               log.h
               min_heap.h
               strlcpy.c
               strlcpy-internal.h
               tree.h
               win32.c
               config.h
               signal.c
            3. 替換event-config.h,使用libevent-iocp中的
            4. 項(xiàng)目設(shè)置里添加HAVE_CONFIG_H預(yù)處理宏
            5. 修改win32.c中win32_init函數(shù),加入WSAStartup函數(shù),類似于:
                   WSADATA wd;   
                    int err;
                    struct win32op *winop;
                    size_t size;
                    if( ( err = WSAStartup( MAKEWORD( 2, 2 ), &wd ) ) != 0 )
                        event_err( 1, "winsock startup failed : %d", err );
            6. 修改win32.c中win32_dealloc函數(shù),在函數(shù)末尾加上WSACleanup的調(diào)用:
                    WSACleanup();
            6. 至此libevent編譯成功;
            7. 幾個(gè)例子程序,只需要加入HAVE_CONFIG_H預(yù)處理宏,以及連接ws2_32.lib即可;
               (time_test需要修改time-test.c文件,即在包含event.h前包含windows.h)

            libevent-1.4.5-stable-vs2005.zip下載

             

            posted @ 2008-07-16 14:28 Kevin Lynx 閱讀(9547) | 評論 (14)編輯 收藏

            建立異步操作組件:隊(duì)列和線程

            6.25.2008

            Kevin Lynx

            引言

            在一個(gè)高效的系統(tǒng)中,我們經(jīng)常會將一些費(fèi)時(shí)的操作轉(zhuǎn)換為異步操作。例如往數(shù)據(jù)庫中寫日志。如果數(shù)據(jù)庫
            配置在網(wǎng)絡(luò)上,那么往數(shù)據(jù)庫中插入一些日志信息將非常慢(相對于程序其他部分)。

            如何轉(zhuǎn)換為異步?

            將類似于以上過程轉(zhuǎn)換為異步操作,一個(gè)典型的做法是:建立一個(gè)單獨(dú)的數(shù)據(jù)庫日志線程,一個(gè)線程安全的
            隊(duì)列。要寫日志時(shí),只需要往隊(duì)列里放入數(shù)據(jù),數(shù)據(jù)庫日志線程則從這個(gè)隊(duì)列里取數(shù)據(jù)然后完成寫操作。

            大致的過程類似于:

            typedef safe_list<std::string> SafeList;
            SafeList gLogList; 

            // database thread.read log string.
            unsigned int __stdcall DBThread( void *p )
            {
                
            whiletrue )
                
            {
                    
            // read gLogList and write log string into the database
                }
             

                _endthreadex( 
            0 );
                
            return 0;
            }
             

            // other threads. write log string.
            void wrtie_log( const std::string &log )
            {
                gLogList.push_back( log );
            }
             


            將他們包裝起來

            我們很有可能會在同一個(gè)系統(tǒng)中多次遇到類似需要轉(zhuǎn)換為異步操作的地方,如果每一次都手動去創(chuàng)建一個(gè)隊(duì)列和一
            個(gè)線程,那將會多么乏味啊!懶惰的程序員喜歡重用各種代碼。所以,我自己覺得很有必要將這一切封裝起來。我
            們只需要封裝這個(gè)隊(duì)列和創(chuàng)建線程的繁瑣細(xì)節(jié),讓應(yīng)用層全部專注于具體的邏輯處理:

            template <typename _NodeType>
            class async_operator
            {
            public:
                
            /// the list node type.
                typedef _NodeType node_type;
                
            /// the list.
                typedef multi_list<node_type, Mutex, Semaphore> list_type;
                
            /// operator signature
                typedef functor<void, TYPE_LIST1( list_type& )> operator_type;
                
            /// init function signature, called when the thread starts.
                typedef functor<void> init_type;
                
            /// called before the thread exits.
                typedef functor<void> release_type; 

            public:
                
            ///
                
            /// start the thread and execute the operation.
                
            /// @param op the callback function operate the list node.
                
            /// 

                void execute( operator_type &op, init_type &init = init_type(), release_type &release = release_type() ); 

                
            ///
                
            /// exit the thread.It will block until the thread exited.
                
            ///

                void exit(); 

                
            ///
                
            /// get the list so that you can pust list nodes.
                
            ///

                list_type &list(); 

            private:
                list_type _list;
                operator_type _operator;
                init_type _init;
                release_type _release;
                thread _thread;
            }



            我利用了已有的組件:線程安全的容器multi_list、包裝任意執(zhí)行體的functor、線程維護(hù)類thread。那么,現(xiàn)在,
            應(yīng)用層只需要定義隊(duì)列節(jié)點(diǎn)類型,寫應(yīng)用相關(guān)的回調(diào)函數(shù)(任意可被functor包裝的廣義函數(shù))。(見附件例子)

            之所以為這個(gè)組件加上init和release,是因?yàn)橛行〇|西(例如COM)需要在線程啟動時(shí)初始化,而在線程快結(jié)束時(shí)釋放,例如對于
            使用COM的應(yīng)用來說,就需要在線程初始化時(shí)CoInitialize,結(jié)束時(shí)CoUninitialize。

            閑說下其他東西

            在本文的附件代碼里,你可以獲取到functor、thread、multi_list這些東西,所以我有必要提一下。

            關(guān)于functor,你可以參看<實(shí)現(xiàn)functor - 增強(qiáng)型的函數(shù)指針>,基本上可以看成增強(qiáng)版的C回調(diào)函數(shù);至于multi_list,基本上
            是一個(gè)container adapter (套用下STL的概念),使用條件變量參與線程同步,據(jù)說效率要比簡單的互斥高點(diǎn);至于thread,我需要
            特別說下:

            thread最為重要的就是為其附加了一個(gè)windows的消息隊(duì)列(只要調(diào)用PeekMessage之類的函數(shù)該隊(duì)列就存在),本意是可以讓其他線
            程傳送數(shù)據(jù)到該線程,但是目前只用于線程退出,即其他線程可以在任何時(shí)候要求該線程安全地退出(該線程沒有阻塞的情況下,
            阻塞時(shí)獲取不到消息)。我不知道這個(gè)安全退出策略是否真的有必要存在,但是我討厭看到各種撇腳的退出方法(例如設(shè)置全局標(biāo)志
            變量,增加額外的--沒封裝前---event對象之類)。

            結(jié)束

            不知道其他人是如何做這種異步轉(zhuǎn)換操作的,在這里我只是起個(gè)拋磚引玉的作用,歡迎大家提出意見。

             

            例子下載

            posted @ 2008-06-25 15:47 Kevin Lynx 閱讀(5062) | 評論 (29)編輯 收藏

            IOCP與線程

            author : Kevin Lynx

             

            什么是完成包?

            完成包,即IO Completion Packet,是指異步IO操作完畢后OS提交給應(yīng)用層的通知包。IOCP維護(hù)了一個(gè)IO操作結(jié)果隊(duì)列,里面
            保存著各種完成包。應(yīng)用層調(diào)用GQCS(也就是GetQueueCompletionStatus)函數(shù)獲取這些完成包。

            最大并發(fā)線程數(shù)

            在一個(gè)典型的IOCP程序里,會有一些線程調(diào)用GQCS去獲取IO操作結(jié)果。最大并發(fā)線程數(shù)指定在同一時(shí)刻處理完成包的線程數(shù)目。
            該參數(shù)在調(diào)用CreateIoCompletionPort時(shí)由NumberOfConcurrentThreads指定。

            工作者線程

            工作者線程一般指的就是調(diào)用GQCS函數(shù)的線程。要注意的是,工作者線程數(shù)和最大并發(fā)線程數(shù)并不是同一回事(見下文)。工作者
            線程由應(yīng)用層顯示創(chuàng)建(_beginthreadex 之類)。工作者線程通常是一個(gè)循環(huán),會不斷地GQCS到完成包,然后處理完成包。

            調(diào)度過程

            工作者線程以是否阻塞分為兩種狀態(tài):運(yùn)行狀態(tài)和等待狀態(tài)。當(dāng)線程做一些阻塞操作時(shí)(線程同步,甚至GQCS空的完成隊(duì)列),線程
            處于等待狀態(tài);否則,線程處于運(yùn)行狀態(tài)。

            另一方面,OS會始終保持某一時(shí)刻處于運(yùn)行狀態(tài)的線程數(shù)小于最大并發(fā)線程數(shù)。每一個(gè)調(diào)用GQCS函數(shù)的線程OS實(shí)際上都會進(jìn)行記錄,
            當(dāng)完成隊(duì)列里有完成包時(shí),OS會首先檢查當(dāng)前處于運(yùn)行狀態(tài)的工作線程數(shù)是否小于最大并發(fā)線程數(shù),如果小于,OS會按照LIFO的順
            序讓某個(gè)工作者線程從GQCS返回(此工作者線程轉(zhuǎn)換為運(yùn)行狀態(tài))。如何決定這個(gè)LIFO?這是簡單地通過調(diào)用GQCS函數(shù)的順序決定的。

            從這里可以看出,這里涉及到線程喚醒和睡眠的操作。如果兩個(gè)線程被放置于同一個(gè)CPU上,就會有線程切換的開銷。因此,為了消
            除這個(gè)開銷,最大并發(fā)線程數(shù)被建議為設(shè)置成CPU數(shù)量。

            從以上調(diào)度過程還可以看出,如果某個(gè)處于運(yùn)行狀態(tài)的工作者線程在處理完成包時(shí)阻塞了(例如線程同步、其他IO操作),那么就有
            CPU資源處于空閑狀態(tài)。因此,我們也看到很多文檔里建議,工作者線程數(shù)為(CPU數(shù)*2+2)。

            在一個(gè)等待線程轉(zhuǎn)換到運(yùn)行狀態(tài)時(shí),有可能會出現(xiàn)短暫的時(shí)間運(yùn)行線程數(shù)超過最大并發(fā)線程數(shù),這個(gè)時(shí)候OS會迅速地讓這個(gè)新轉(zhuǎn)換
            的線程阻塞,從而減少這個(gè)數(shù)量。(關(guān)于這個(gè)觀點(diǎn),MSDN上只說:by not allowing any new active threads,卻沒說明not allowing
            what)

            調(diào)度原理

            這個(gè)知道了其實(shí)沒什么意義,都是內(nèi)核做的事,大致上都是操作線程control block,直接摘錄<Inside IO Completion Ports>:

            The list of threads hangs off the queue object. A thread's control block data structure has a pointer in it that
            references the queue object of a queue that it is associated with; if the pointer is NULL then the thread is not
            associated with a queue.

            So how does NT keep track of threads that become inactive because they block on something other than the completion
            port" The answer lies in the queue pointer in a thread's control block. The scheduler routines that are executed
            in response to a thread blocking (KeWaitForSingleObject, KeDelayExecutionThread, etc.) check the thread's queue
            pointer and if its not NULL they will call KiActivateWaiterQueue, a queue-related function. KiActivateWaiterQueue
            decrements the count of active threads associated with the queue, and if the result is less than the maximum and
            there is at least one completion packet in the queue then the thread at the front of the queue's thread list is
            woken and given the oldest packet. Conversely, whenever a thread that is associated with a queue wakes up after
            blocking the scheduler executes the function KiUnwaitThread, which increments the queue's active count.

            參考資料

            <Inside I/O Completion Ports>:
            http://technet.microsoft.com/en-us/sysinternals/bb963891.aspx
            <I/O Completion Ports>:
            http://msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx
            <INFO: Design Issues When Using IOCP in a Winsock Server>:
            http://support.microsoft.com/kb/192800/en-us/

            posted @ 2008-06-23 17:32 Kevin Lynx 閱讀(4848) | 評論 (3)編輯 收藏

            SGI STL的內(nèi)存池

            stl中各種容器都有一個(gè)可選的模板參數(shù):allocator,也就是一個(gè)負(fù)責(zé)內(nèi)存分配的組件。STL標(biāo)準(zhǔn)規(guī)定的allcator
            被定義在memory文件中。STL標(biāo)準(zhǔn)規(guī)定的allocator只是單純地封裝operator new,效率上有點(diǎn)過意不去。

            SGI實(shí)現(xiàn)的STL里,所有的容器都使用SGI自己定義的allocator。這個(gè)allocator實(shí)現(xiàn)了一個(gè)small object的內(nèi)存池。
            Loki里為了處理小對象的內(nèi)存分配,也實(shí)現(xiàn)了類似的內(nèi)存管理機(jī)制。

            該內(nèi)存池大致上,就是一大塊一大塊地從系統(tǒng)獲取內(nèi)存,然后將其分成很多小塊以鏈表的形式鏈接起來。其內(nèi)部
            有很多不同類型的鏈表,不同的鏈表維護(hù)不同大小的內(nèi)存塊。每一次客戶端要求分配內(nèi)存時(shí),allcator就根據(jù)請求
            的大小找到相應(yīng)的鏈表(最接近的尺寸),然后從鏈表里取出內(nèi)存。當(dāng)客戶端歸還內(nèi)存時(shí),allocator就將這塊內(nèi)存
            放回到對應(yīng)的鏈表里。

            我簡單地畫了幅圖表示整個(gè)結(jié)構(gòu):

            allocator

            allocator內(nèi)部維護(hù)一個(gè)鏈表數(shù)組,數(shù)組元素全部是鏈表頭指針。鏈表A每一個(gè)節(jié)點(diǎn)維護(hù)一個(gè)8bytes的內(nèi)存塊,鏈表
            B每一個(gè)節(jié)點(diǎn)維護(hù)一個(gè)16bytes的內(nèi)存塊。

            當(dāng)客戶端請求分配10bytes的內(nèi)存時(shí),allocator將10調(diào)整為最接近的16bytes(只能大于10bytes),然后發(fā)現(xiàn)16bytes
            這個(gè)鏈表(鏈表B)里有可用內(nèi)存塊,于是從B里取出一塊內(nèi)存返回。當(dāng)客戶端歸還時(shí),allocator找到對應(yīng)的鏈表,將
            內(nèi)存重新放回鏈表B即可。

            大致過程就這么簡單,也許有人要說用鏈表維護(hù)一塊內(nèi)存,鏈表本身就會浪費(fèi)一些內(nèi)存(在我很早前接觸內(nèi)存池時(shí),
            總會看到類似的論點(diǎn)= =|),其實(shí)通過一些簡單的技巧是完全可以避免的。例如,這里allocator維護(hù)了很多內(nèi)存塊,
            反正這些內(nèi)存本身就是閑置的,因此我們就可以直接在這些內(nèi)存里記錄鏈表的信息(下一個(gè)元素)。

            還是寫點(diǎn)代碼詳細(xì)說下這個(gè)小技巧:

               

            struct Obj
                
            {
                    Obj 
            *next;
                }


                
            void *mem = malloc( 100 );
                Obj 
            *header = (Obj*) mem;
                Obj 
            *cur_obj = header;
                Obj 
            *next_obj = cur_obj;
                
            forint i = 0; ; ++ i )
                
            {
                    cur_obj 
            = next_obj;
                    next_obj 
            = (Obj*)((char*)next_obj + 10 );
                    
            if( i == 9 )
                    
            {
                        cur_obj
            ->next = 0;
                        
            break;
                    }

                    
            else
                    
            {
                        cur_obj
            ->next = next_obj;
                    }

                }

                free( mem );

             

            這樣,通過header指針和next域,就可以逐塊(這里是10byts)地訪問mem所指向的內(nèi)存,而這些鏈表的節(jié)點(diǎn),都
            是直接保存在這塊內(nèi)存里的,所以完全沒有額外消耗。

            我用C模仿著SGI的這個(gè)allocator寫了個(gè)可配置的內(nèi)存池,在其上按照STL的標(biāo)準(zhǔn)包裝了一個(gè)allocator,可以直接
            用于VC自帶的STL里。
            測試代碼稍微測試了下,發(fā)現(xiàn)在不同的機(jī)器上有明顯的差距。

            posted @ 2008-06-12 21:26 Kevin Lynx 閱讀(8027) | 評論 (10)編輯 收藏

            Proactor和Reactor模式_繼續(xù)并發(fā)系統(tǒng)設(shè)計(jì)的掃盲

            6.6.2008

            Kevin Lynx

            Proactor和Reactor都是并發(fā)編程中的設(shè)計(jì)模式。在我看來,他們都是用于派發(fā)/分離IO操作事件的。這里所謂的
            IO事件也就是諸如read/write的IO操作。"派發(fā)/分離"就是將單獨(dú)的IO事件通知到上層模塊。兩個(gè)模式不同的地方
            在于,Proactor用于異步IO,而Reactor用于同步IO。

            摘抄一些關(guān)鍵的東西:

            "
            Two patterns that involve event demultiplexors are called Reactor and Proactor [1]. The Reactor patterns
            involve synchronous I/O, whereas the Proactor pattern involves asynchronous I/O.
            "

            關(guān)于兩個(gè)模式的大致模型,從以下文字基本可以明白:

            "
            An example will help you understand the difference between Reactor and Proactor. We will focus on the read
            operation here, as the write implementation is similar. Here's a read in Reactor:

            * An event handler declares interest in I/O events that indicate readiness for read on a particular socket ;
            * The event demultiplexor waits for events ;
            * An event comes in and wakes-up the demultiplexor, and the demultiplexor calls the appropriate handler;
            * The event handler performs the actual read operation, handles the data read, declares renewed interest in
              I/O events, and returns control to the dispatcher .

            By comparison, here is a read operation in Proactor (true async):

            * A handler initiates an asynchronous read operation (note: the OS must support asynchronous I/O). In this
              case, the handler does not care about I/O readiness events, but is instead registers interest in receiving
              completion events;
            * The event demultiplexor waits until the operation is completed ;
            * While the event demultiplexor waits, the OS executes the read operation in a parallel kernel thread, puts
              data into a user-defined buffer, and notifies the event demultiplexor that the read is complete ;
            * The event demultiplexor calls the appropriate handler;
            * The event handler handles the data from user defined buffer, starts a new asynchronous operation, and returns
              control to the event demultiplexor.

            "

            可以看出,兩個(gè)模式的相同點(diǎn),都是對某個(gè)IO事件的事件通知(即告訴某個(gè)模塊,這個(gè)IO操作可以進(jìn)行或已經(jīng)完成)。在結(jié)構(gòu)
            上,兩者也有相同點(diǎn):demultiplexor負(fù)責(zé)提交IO操作(異步)、查詢設(shè)備是否可操作(同步),然后當(dāng)條件滿足時(shí),就回調(diào)handler。
            不同點(diǎn)在于,異步情況下(Proactor),當(dāng)回調(diào)handler時(shí),表示IO操作已經(jīng)完成;同步情況下(Reactor),回調(diào)handler時(shí),表示
            IO設(shè)備可以進(jìn)行某個(gè)操作(can read or can write),handler這個(gè)時(shí)候開始提交操作。

            用select模型寫個(gè)簡單的reactor,大致為:

            ///
            class handler
            {
            public:
                
            virtual void onRead() = 0;
                
            virtual void onWrite() = 0;
                
            virtual void onAccept() = 0;
            }


            class dispatch
            {
            public:
                
            void poll()
                
            {
                    
            // add fd in the set.
                    
            //
                    
            // poll every fd
                    int c = select( 0&read_fd, &write_fd, 00 );
                    
            if( c > 0 )
                    
            {
                        
            for each fd in the read_fd_set
                        
            {    if fd can read
                                _handler
            ->onRead();
                            
            if fd can accept
                                _handler
            ->onAccept();
                        }
             

                        
            for each fd in the write_fd_set
                        
            {
                            
            if fd can write
                                _handler
            ->onWrite();
                        }

                    }

                }
             

                
            void setHandler( handler *_h )
                
            {
                    _handler 
            = _h;
                }
             

            private:
                handler 
            *_handler;
            }


            /// application
            class MyHandler : public handler
            {
            public:
                
            void onRead()
                
            {
                }
             

                
            void onWrite()
                
            {
                }
             

                
            void onAccept()
                
            {
                }

            }



            在網(wǎng)上找了份Proactor模式比較正式的文檔,其給出了一個(gè)總體的UML類圖,比較全面:

            proactor_uml

            根據(jù)這份圖我隨便寫了個(gè)例子代碼:

            class AsyIOProcessor
            {
            public:
                
            void do_read()
                
            {
                    
            //send read operation to OS
                    
            // read io finished.and dispatch notification
                    _proactor->dispatch_read();
                }
             

            private:
                Proactor 
            *_proactor;
            }


            class Proactor
            {
            public:
                
            void dispatch_read()
                
            {
                    _handlerMgr
            ->onRead();
                }
             

            private:
                HandlerManager 
            *_handlerMgr;
            }


            class HandlerManager
            {
            public:
                typedef std::list
            <Handler*> HandlerList; 

            public:
                
            void onRead()
                
            {
                    
            // notify all the handlers.
                    std::for_each( _handlers.begin(), _handlers.end(), onRead );
                }
             

            private:
                HandlerList 
            *_handlers;
            }


            class Handler
            {
            public:
                
            virtual void onRead() = 0;
            }


            // application level handler.
            class MyHandler : public Handler
            {
            public:
                
            void onRead() 
                
            {
                    
            // 
                }

            }



            Reactor通過某種變形,可以將其改裝為Proactor,在某些不支持異步IO的系統(tǒng)上,也可以隱藏底層的實(shí)現(xiàn),利于編寫跨平臺
            代碼。我們只需要在dispatch(也就是demultiplexor)中封裝同步IO操作的代碼,在上層,用戶提交自己的緩沖區(qū)到這一層,
            這一層檢查到設(shè)備可操作時(shí),不像原來立即回調(diào)handler,而是開始IO操作,然后將操作結(jié)果放到用戶緩沖區(qū)(讀),然后再
            回調(diào)handler。這樣,對于上層handler而言,就像是proactor一樣。詳細(xì)技法參見這篇文章

            其實(shí)就設(shè)計(jì)模式而言,我個(gè)人覺得某個(gè)模式其實(shí)是沒有完全固定的結(jié)構(gòu)的。不能說某個(gè)模式里就肯定會有某個(gè)類,類之間的
            關(guān)系就肯定是這樣。在實(shí)際寫程序過程中也很少去特別地實(shí)現(xiàn)某個(gè)模式,只能說模式會給你更多更好的架構(gòu)方案。

            最近在看spserver的代碼,看到別人提各種并發(fā)系統(tǒng)中的模式,有點(diǎn)眼紅,于是才來掃掃盲。知道什么是leader follower模式
            reactor, proactor,multiplexing,對于心中的那個(gè)網(wǎng)絡(luò)庫也越來越清晰。

            最近還干了些離譜的事,寫了傳說中的字節(jié)流編碼,用模板的方式實(shí)現(xiàn),不但保持了擴(kuò)展性,還少寫很多代碼;處于效率考慮,
            寫了個(gè)static array容器(其實(shí)就是template <typename _Tp, std::size_t size> class static_array { _Tp _con[size]),
            加了iterator,遵循STL標(biāo)準(zhǔn),可以結(jié)合進(jìn)STL的各個(gè)generic algorithm用,自我感覺不錯。基礎(chǔ)模塊搭建完畢,解析了公司
            服務(wù)器網(wǎng)絡(luò)模塊的消息,我是不是真的打算用自己的網(wǎng)絡(luò)模塊重寫我的驗(yàn)證服務(wù)器?在另一個(gè)給公司寫的工具里,因?yàn)閷?shí)在厭惡
            越來越多的重復(fù)代碼,索性寫了幾個(gè)宏,還真的做到了代碼的自動生成:D。

            對優(yōu)雅代碼的追求真的成了種癖好.  = =|

            posted @ 2008-06-06 13:25 Kevin Lynx 閱讀(29567) | 評論 (7)編輯 收藏

            僅列出標(biāo)題
            共12頁: First 4 5 6 7 8 9 10 11 12 
            久久精品免费全国观看国产| 久久国产视频99电影| 久久久久久曰本AV免费免费| 久久婷婷国产剧情内射白浆| 欧美午夜精品久久久久免费视| 精品久久久噜噜噜久久久| 久久综合九色综合久99| 欧美精品一区二区久久| 久久亚洲精品成人av无码网站 | 综合久久久久久中文字幕亚洲国产国产综合一区首 | 亚洲国产一成人久久精品| 丁香五月网久久综合| 亚洲欧美日韩精品久久亚洲区| 久久99精品国产麻豆| 一级A毛片免费观看久久精品| 国内精品伊人久久久久| 久久久国产精华液| 精品久久久久中文字| 精品久久久久久亚洲精品| 久久精品国产久精国产一老狼| 国产福利电影一区二区三区,免费久久久久久久精 | 久久丫忘忧草产品| 久久久久亚洲av毛片大| 97久久精品国产精品青草| 久久久久久精品免费看SSS| 久久久久久无码国产精品中文字幕| 99久久国产综合精品麻豆| 亚洲伊人久久大香线蕉综合图片| 亚洲&#228;v永久无码精品天堂久久 | 国产精品美女久久久久av爽 | 久久久久久久综合狠狠综合| 91精品国产综合久久香蕉| 久久精品国产半推半就| 久久国产精品77777| 国产精品99精品久久免费| 亚洲精品乱码久久久久久按摩| 久久久亚洲欧洲日产国码是AV| 日本国产精品久久| 久久涩综合| 久久人人爽人人人人爽AV| 久久久无码精品亚洲日韩京东传媒|