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

            兔子的技術(shù)博客

            兔子

               :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
              202 Posts :: 0 Stories :: 43 Comments :: 0 Trackbacks

            留言簿(10)

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            轉(zhuǎn)自:http://www.vckbase.com/document/viewdoc/?id=1866
            英文原文:http://www.codeproject.com/KB/IP/iocp_server_client.aspx


            源代碼下載

            源碼使用了高級(jí)的完成端口(IOCP)技術(shù),該技術(shù)可以有效地服務(wù)于多客戶端。本文提出了一些IOCP編程中出現(xiàn)的實(shí)際問(wèn)題的解決方法,并提供了一個(gè)簡(jiǎn)單的echo版本的可以傳輸文件的客戶端/服務(wù)器程序。

            程序截圖:


            1.1 環(huán)境要求
            本文讀者需要熟悉C++、TCP/IP、Socket編程、MFC,和多線程。
            源碼使用Winsock 2.0和IOCP技術(shù),要求:
            Windows NT/2000或以上:要求Windows NT3.5或以后版本
            Windows 95/98/ME:不支持
            Visual C++.NET,或完整更新過(guò)的Visual C++ 6.0

            1.2 摘要
            當(dāng)你開發(fā)不同類型的軟件,你遲早必須處理C/S的開發(fā)。對(duì)一個(gè)程序員來(lái)說(shuō),寫一個(gè)通用的C/S編碼是一項(xiàng)困難的工作。本文檔提供了一份簡(jiǎn)單但是功能強(qiáng)大的C/S源碼,可以擴(kuò)展到任何類型的C/S應(yīng)用程序中。這份源碼使用了高級(jí)的IOCP技術(shù),該技術(shù)可以高效的服務(wù)于多客戶端。IOCP提供了解決“每個(gè)客戶端占用一個(gè)線程”的瓶頸問(wèn)題的辦法,只使用幾個(gè)處理線程,異步輸入/輸出來(lái)發(fā)送/接收。IOCP技術(shù)被廣泛應(yīng)用在各種類型的高效服務(wù)端,例如Apache等。這份源碼也提供了一系列的在處理通信和C/S軟件中經(jīng)常使用的功能,如文件接收/傳送功能和邏輯線程池管理。本文重點(diǎn)在于出現(xiàn)在IOCP程序API中實(shí)用的解決方案,以及關(guān)于源碼的全面的文檔。另外,一份簡(jiǎn)單的echo版的可處理多連接和文件傳輸?shù)腃/S程序也在這里提供。

            2.1 引言
            本文提出了一個(gè)類,可以用在客戶端和服務(wù)端。這個(gè)類使用IOCP(Input Output Completion Ports)和異步(非阻塞)機(jī)制。…
            通過(guò)這些簡(jiǎn)單的源碼,你可以:
            · 服務(wù)或連接多客戶端和服務(wù)端
            · 異步發(fā)送或接收文件
            · 創(chuàng)建并管理一個(gè)邏輯工作者線程池,用以處理繁重的客戶端/服務(wù)器請(qǐng)求或計(jì)算

            找到一份全面但簡(jiǎn)單的解決客戶端/服務(wù)器通信的源碼是件困難的事情。在網(wǎng)絡(luò)上找到的源碼要么太復(fù)雜(超過(guò)20個(gè)類),要命沒有提供足夠的效率。本源碼的設(shè)計(jì)盡可能簡(jiǎn)單,并提供了充足的文檔。在這篇文章中,我們簡(jiǎn)潔的呈現(xiàn)出了Winsock API 2.0支持的IOCP技術(shù),說(shuō)明了在編寫過(guò)程中出現(xiàn)的棘手問(wèn)題,并提出了每一個(gè)問(wèn)題的解決方案。

            2.2 異步完成端口介紹

            如果一個(gè)服務(wù)器應(yīng)用程序不能同時(shí)支持多個(gè)客戶端,那是毫無(wú)意義的,為此,通常使用異步I/O請(qǐng)求和多線程。根據(jù)定義,一個(gè)異步I/O請(qǐng)求會(huì)立即返回,而留下I/O請(qǐng)求處于等待狀態(tài)。有時(shí),I/O異步請(qǐng)求的結(jié)果必須與主線程同步。這可以通過(guò)幾種不同方式解決。同步可以通過(guò)下面的方式實(shí)現(xiàn):

            > 使用事件 – 當(dāng)異步請(qǐng)求結(jié)束時(shí)會(huì)馬上觸發(fā)一個(gè)信號(hào)。這種方式的缺點(diǎn)是線程必須檢查并等待事件被觸發(fā)
            > 使用GetOverlappedResult函數(shù) – 這種方式與上一種方式有相同的缺點(diǎn)。
            > 使用Asynchronous Procedure Calls(或APC) – 這種方式有幾個(gè)缺點(diǎn)。首先,APC總是在請(qǐng)求線程的上下文中被請(qǐng)求;第二,為了執(zhí)行APC,請(qǐng)求線程必須在可變等候狀態(tài)下掛起。
            > 使用IOCP – 這種方式的缺點(diǎn)是必須解決很多實(shí)際的棘手的編程問(wèn)題。編寫IOCP可能有點(diǎn)麻煩。

            2.2.1 為什么使用IOCP?
            通過(guò)使用IOCP,我們可以解決“每個(gè)客戶端占用一個(gè)線程”的問(wèn)題。通常普遍認(rèn)為如果軟件不能運(yùn)行在真正的多處理器機(jī)器上,執(zhí)行能力會(huì)嚴(yán)重降低。線程是系統(tǒng)資源,而這些資源既不是無(wú)限的,也不是低價(jià)的。

            IOCP提供了一種方式來(lái)使用幾個(gè)線程“公平的”處理多客戶端的輸入/輸出。線程被掛起,不占用CPU周期直到有事可做。

            2.3 什么是IOCP?
            我們已經(jīng)看到IOCP只是一個(gè)線程同步對(duì)象,類似于信號(hào)燈,因此IOCP并不是一個(gè)復(fù)雜的概念。一個(gè)IOCP對(duì)象與幾個(gè)支持待定異步I/O請(qǐng)求的I/O對(duì)象綁定。一個(gè)可以訪問(wèn)IOCP的線程可以被掛起,直到一個(gè)待定的異步I/O請(qǐng)求結(jié)束。

            3 IOCP是怎樣工作的?
            要使用IOCP,你必須處理三件事情,綁定一個(gè)socket到完成端口,創(chuàng)建異步I/O請(qǐng)求,并與線程同步。為從異步I/O請(qǐng)求獲得結(jié)果,如那個(gè)客戶端發(fā)出的請(qǐng)求,你必須傳遞兩個(gè)參數(shù):CompletionKey參數(shù)和OVERLAPPED結(jié)構(gòu)。

            3.1 關(guān)鍵參數(shù)
            第一個(gè)參數(shù):CompletionKey,是一個(gè)DWORD類型的變量。你可以傳遞任何你想傳遞的唯一值,這個(gè)值將總是同該對(duì)象綁定。正常情況下會(huì)傳遞一個(gè)指向結(jié)構(gòu)或類的指針,該結(jié)構(gòu)或類包含了一些客戶端的指定對(duì)象。在源碼中,傳遞的是一個(gè)指向ClientContext的指針。

            3.2 OVERLAPPED參數(shù)

            這個(gè)參數(shù)通常用來(lái)傳遞異步I/O請(qǐng)求使用的內(nèi)存緩沖。很重要的一點(diǎn)是:該數(shù)據(jù)將會(huì)被鎖定并不允許從物理內(nèi)存中換出頁(yè)面(page out)。

            3.3 綁定一個(gè)socket到完成端口
            一旦創(chuàng)建完成一個(gè)完成端口,可以通過(guò)調(diào)用CreateIoCompletionPort函數(shù)來(lái)綁定socket到完成端口。形式如下:
            BOOL IOCPS::AssociateSocketWithCompletionPort(SOCKET socket,  HANDLE hCompletionPort, DWORD dwCompletionKey)
            {
            HANDLE h = CreateIoCompletionPort((HANDLE) socket,  hCompletionPort, dwCompletionKey, m_nIOWorkers);
            return h == hCompletionPort;
            }
            3.4 響應(yīng)異步I/O請(qǐng)求
            響應(yīng)具體的異步請(qǐng)求,調(diào)用函數(shù)WSASend和WSARecv。他們也需要一個(gè)參數(shù):WSABUF,這個(gè)參數(shù)包含了一個(gè)指向緩沖的指針。一個(gè)重要的規(guī)則是:通常當(dāng)服務(wù)器/客戶端響應(yīng)一個(gè)I/O操作,不是直接響應(yīng),而是提交給完成端口,由I/O工作者線程來(lái)執(zhí)行。這么做的原因是:我們希望公平的分割CPU周期。通過(guò)發(fā)送狀態(tài)給完成端口來(lái)發(fā)出I/O請(qǐng)求,如下:
            BOOL bSuccess = PostQueuedCompletionStatus(m_hCompletionPort,
            pOverlapBuff->GetUsed(),
            (DWORD) pContext,
            &pOverlapBuff->m_ol);
            3.5 與線程同步

            與I/O工作者線程同步是通過(guò)調(diào)用GetQueuedCompletionStatus函數(shù)來(lái)實(shí)現(xiàn)的(如下)。這個(gè)函數(shù)也提供了CompletionKey參數(shù)和OVERLAPPED參數(shù),如下:
            BOOL GetQueuedCompletionStatus(   HANDLE CompletionPort, // handle to completion port
            LPDWORD lpNumberOfBytes, // bytes transferred
            PULONG_PTR lpCompletionKey, // file completion key
            LPOVERLAPPED *lpOverlapped, // buffer
            DWORD dwMilliseconds // optional timeout value
            );
            
            3.6 四個(gè)棘手的IOCP編碼問(wèn)題和解決方法

            使用IOCP時(shí)會(huì)出現(xiàn)一些問(wèn)題,其中有一些不是很直觀的。在使用IOCP的多線程編程中,一個(gè)線程函數(shù)的控制流程不是筆直的,因?yàn)樵诰€程和通訊直接沒有關(guān)系。在這一章節(jié)中,我們將描述四個(gè)不同的問(wèn)題,可能在使用IOCP開發(fā)客戶端/服務(wù)器應(yīng)用程序時(shí)會(huì)出現(xiàn),分別是:

            The WSAENOBUFS error problem.(WSAENOBUFS錯(cuò)誤問(wèn)題)
            The package reordering problem.(包重構(gòu)問(wèn)題)
            The access violation problem.(訪問(wèn)非法問(wèn)題)



            3.6.1 WSAENOBUFS問(wèn)題

            這個(gè)問(wèn)題通常很難靠直覺發(fā)現(xiàn),因?yàn)楫?dāng)你第一次看見的時(shí)候你或許認(rèn)為是一個(gè)內(nèi)存泄露錯(cuò)誤。假定已經(jīng)開發(fā)完成了你的完成端口服務(wù)器并且運(yùn)行的一切良好,但是當(dāng)你對(duì)其進(jìn)行壓力測(cè)試的時(shí)候突然發(fā)現(xiàn)服務(wù)器被中止而不處理任何請(qǐng)求了,如果你運(yùn)氣好的話你會(huì)很快發(fā)現(xiàn)是因?yàn)閃SAENOBUFS 錯(cuò)誤而影響了這一切。

            每當(dāng)我們重疊提交一個(gè)send或receive操作的時(shí)候,其中指定的發(fā)送或接收緩沖區(qū)就被鎖定了。當(dāng)內(nèi)存緩沖區(qū)被鎖定后,將不能從物理內(nèi)存進(jìn)行分頁(yè)。操作系統(tǒng)有一個(gè)鎖定最大數(shù)的限制,一旦超過(guò)這個(gè)鎖定的限制,那么就會(huì)產(chǎn)生WSAENOBUFS 錯(cuò)誤了。

            如果一個(gè)服務(wù)器提交了非常多的重疊的receive在每一個(gè)連接上,那么限制會(huì)隨著連接數(shù)的增長(zhǎng)而變化。如果一個(gè)服務(wù)器能夠預(yù)先估計(jì)可能會(huì)產(chǎn)生的最大并發(fā)連接數(shù),服務(wù)器可以投遞一個(gè)使用零緩沖區(qū)的receive在每一個(gè)連接上。因?yàn)楫?dāng)你提交操作沒有緩沖區(qū)時(shí),那么也不會(huì)存在內(nèi)存被鎖定了。使用這種辦法后,當(dāng)你的receive操作事件完成返回時(shí),該socket底層緩沖區(qū)的數(shù)據(jù)會(huì)原封不動(dòng)的還在其中而沒有被讀取到receive操作的緩沖區(qū)來(lái)。此時(shí),服務(wù)器可以簡(jiǎn)單的調(diào)用非阻塞式的recv將存在socket緩沖區(qū)中的數(shù)據(jù)全部讀出來(lái),一直到recv返回 WSAEWOULDBLOCK 為止。 這種設(shè)計(jì)非常適合那些可以犧牲數(shù)據(jù)吞吐量而換取巨大 并發(fā)連接數(shù)的服務(wù)器。當(dāng)然,你也需要意識(shí)到如何讓客戶端的行為盡量避免對(duì)服務(wù)器造成影響。在上一個(gè)例子中,當(dāng)一個(gè)零緩沖區(qū)的receive操作被返回后使 用一個(gè)非阻塞的recv去讀取socket緩沖區(qū)中的數(shù)據(jù),如果服務(wù)器此時(shí)可預(yù)計(jì)到將會(huì)有爆發(fā)的數(shù)據(jù)流,那么可以考慮此時(shí)投遞一個(gè)或者多個(gè)receive 來(lái)取代非阻塞的recv來(lái)進(jìn)行數(shù)據(jù)接收。(這比你使用1個(gè)缺省的8K緩沖區(qū)來(lái)接收要好的多。)

            源碼中提供了一個(gè)簡(jiǎn)單實(shí)用的解決WSAENOBUF錯(cuò)誤的辦法。我們執(zhí)行了一個(gè)零字節(jié)緩沖的異步WSARead(...)(參見 OnZeroByteRead(..))。當(dāng)這個(gè)請(qǐng)求完成,我們知道在TCP/IP棧中有數(shù)據(jù),然后我們通過(guò)執(zhí)行幾個(gè)有MAXIMUMPACKAGESIZE緩沖的異步WSARead(...)去讀,解決了WSAENOBUFS問(wèn)題。但是這種解決方法降低了服務(wù)器的吞吐量。

            總結(jié):

            解決方法一:

            投遞使用空緩沖區(qū)的 receive操作,當(dāng)操作返回后,使用非阻塞的recv來(lái)進(jìn)行真實(shí)數(shù)據(jù)的讀取。因此在完成端口的每一個(gè)連接中需要使用一個(gè)循環(huán)的操作來(lái)不斷的來(lái)提交空緩沖區(qū)的receive操作。

            解決方法二:

            在投遞幾個(gè)普通含有緩沖區(qū)的receive操作后,進(jìn)接著開始循環(huán)投遞一個(gè)空緩沖區(qū)的receive操作。這樣保證它們按照投遞順序依次返回,這樣我們就總能對(duì)被鎖定的內(nèi)存進(jìn)行解鎖。

            3.6.2 包重構(gòu)問(wèn)題
            ... ... 盡管使用IO完成端口的待發(fā)操作將總是按照他們發(fā)送的順序來(lái)完成,線程調(diào)度安排可能使綁定到完成端口的實(shí)際工作不按指定的順序來(lái)處理。例如,如果你有兩個(gè)I/O工作者線程,你可能接收到“字節(jié)塊2,字節(jié)塊1,字節(jié)塊3”。這就意味著:當(dāng)你通過(guò)向I/O完成端口提交請(qǐng)求數(shù)據(jù)發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)實(shí)際上用重新排序過(guò)的順序發(fā)送了。

            這可以通過(guò)只使用一個(gè)工作者線程來(lái)解決,并只提交一個(gè)I/O請(qǐng)求,等待它完成。但是如果這么做,我們就失去了IOCP的長(zhǎng)處。

            解決這個(gè)問(wèn)題的一個(gè)簡(jiǎn)單實(shí)用辦法是給我們的緩沖類添加一個(gè)順序數(shù)字,如果緩沖順序數(shù)字是正確的,則處理緩沖中的數(shù)據(jù)。這意味著:有不正確的數(shù)字的緩沖將被存下來(lái)以后再用,并且因?yàn)閳?zhí)行原因,我們保存緩存到一個(gè)HASH MAP對(duì)象中(如m_SendBufferMap 和 m_ReadBufferMap)。

            獲取這種解決方法的更多信息,請(qǐng)查閱源碼,仔細(xì)查看IOCPS類中如下的函數(shù):

            GetNextSendBuffer (..) and GetNextReadBuffer(..), to get the ordered send or receive buffer.
            IncreaseReadSequenceNumber(..) and IncreaseSendSequenceNumber(..), to increase the sequence numbers.

            3.6.3 異步等待讀 和 字節(jié)塊包處理問(wèn)題

            最通用的服務(wù)端協(xié)議是一個(gè)基于協(xié)議的包,首先X個(gè)字節(jié)代表包頭,包頭包含了詳細(xì)的完整的包的長(zhǎng)度。服務(wù)端可以讀包頭,計(jì)算出需要多少數(shù)據(jù),繼續(xù)讀取直到讀完一個(gè)完整的包。當(dāng)服務(wù)端同時(shí)只處理一個(gè)異步請(qǐng)求時(shí)工作的很好。但是,如果我們想發(fā)揮IOCP服務(wù)端的全部潛能,我們應(yīng)該啟用幾個(gè)等待的異步讀事件,等待數(shù)據(jù)到達(dá)。這意味著幾個(gè)異步讀操作是不按順序完成的,通過(guò)等待的讀事件返回的字節(jié)塊流將不會(huì)按順序處理。而且,一個(gè)字節(jié)塊流可以包含一個(gè)或幾個(gè)包,也可能包含部分包,如下圖所示:


            這個(gè)圖形顯示了部分包(綠色)和完整包(黃色)是怎樣在不同字節(jié)塊流中異步到達(dá)的。
            這意味著我們必須處理字節(jié)流來(lái)成功的讀取一個(gè)完整的包。而且,我們必須處理部分包(圖表中綠色的部分)。這就使得字節(jié)流的處理更加困難。這個(gè)問(wèn)題的完整解決方法在IOCPS類的ProcessPackage(…)函數(shù)中。

            3.6.4 訪問(wèn)非法問(wèn)題
            這是一個(gè)較小的問(wèn)題,代碼設(shè)計(jì)導(dǎo)致的問(wèn)題更勝于IOCP的特定問(wèn)題。假設(shè)一個(gè)客戶端連接已經(jīng)關(guān)閉并且一個(gè)I/O請(qǐng)求返回一個(gè)錯(cuò)誤標(biāo)志,然后我們知道客戶端已經(jīng)關(guān)閉。在參數(shù)CompletionKey中,我們傳遞了一個(gè)指向結(jié)構(gòu)ClientContext的指針,該結(jié)構(gòu)中包含了客戶端的特定數(shù)據(jù)。如果我們釋放這個(gè)ClientContext結(jié)構(gòu)占用的內(nèi)存,并且同一個(gè)客戶端處理的一些其它I/O請(qǐng)求返回了錯(cuò)誤代碼,我們通過(guò)轉(zhuǎn)換參數(shù)CompletionKey為一個(gè)指向ClientContext結(jié)構(gòu)的指針并試圖訪問(wèn)或刪除它,會(huì)發(fā)生什么呢?一個(gè)非法訪問(wèn)出現(xiàn)了!

            這個(gè)問(wèn)題的解決方法是添加一個(gè)數(shù)字到結(jié)構(gòu)中,包含等待的I/O請(qǐng)求的數(shù)量(m_nNumberOfPendingIO),然后當(dāng)我們知道沒有等待的I/O請(qǐng)求時(shí)刪除這個(gè)結(jié)構(gòu)。這個(gè)功能通過(guò)函數(shù)EnterIoLoop(…) 和ReleaseClientContext(…)來(lái)實(shí)現(xiàn)。

            3.7 源碼略讀
            源碼的目標(biāo)是提供一系列簡(jiǎn)單的類來(lái)處理所有IOCP編碼中的問(wèn)題。源碼也提供了一系列通信和C/S軟件中經(jīng)常使用的函數(shù),如文件接收/傳送函數(shù),邏輯線程池處理,等等。


            上圖功能性的圖解說(shuō)明了IOCP類源碼。

            我們有幾個(gè)IO工作者線程通過(guò)完成端口來(lái)處理異步IO請(qǐng)求,這些工作者線程調(diào)用一些虛函數(shù),這些虛函數(shù)可以把需要大量計(jì)算的請(qǐng)求放到一個(gè)工作隊(duì)列中。邏輯工作者通過(guò)類中提供的這些函數(shù)從隊(duì)列中取出任務(wù)、處理并發(fā)回結(jié)果。GUI經(jīng)常與主類通信,通過(guò)Windows消息(因?yàn)镸FC不是線程安全的)、通過(guò)調(diào)用函數(shù)或通過(guò)使用共享的變量。

            圖三

            上圖顯示了類結(jié)構(gòu)縱覽。

            圖3中的類說(shuō)明如下:

            > CIOCPBuffer:管理異步請(qǐng)求的緩存的類。
            > IOCPS:處理所有通信的主類。
            > JobItem:保存邏輯工作者線程要處理的任務(wù)的結(jié)構(gòu)。
            > ClientContex:保存客戶端特定信息的結(jié)構(gòu)(如狀態(tài)、數(shù)據(jù),等等)。

            3.7.1 緩沖設(shè)計(jì) - CIOCPBuffer類
            使用異步I/O調(diào)用時(shí),我們必須提供私有的緩沖區(qū)供I/O操作使用。
            當(dāng)我們將帳號(hào)信息放入分配的緩沖供使用時(shí)有許多情況需要考慮:

            .分配和釋放內(nèi)存代價(jià)高,因此我們應(yīng)重復(fù)使用以及分配的緩沖(內(nèi)存),
            因此我們將緩沖保存在列表結(jié)構(gòu)中,如下所示:

            // Free Buffer List..
            CCriticalSection m_FreeBufferListLock;
            CPtrList m_FreeBufferList;
            // OccupiedBuffer List.. (Buffers that is currently used)
            CCriticalSection m_BufferListLock;
            CPtrList m_BufferList;
            // Now we use the function AllocateBuffer(..)
            // to allocate memory or reuse a buffer.
            .有時(shí),當(dāng)異步I/O調(diào)用完成后,緩沖里可能不是完整的包,因此我們需要分割緩沖去取得完整的信息。在CIOCPS類中提供了SplitBuffer函數(shù)。
            同樣,有時(shí)候我們需要在緩沖間拷貝信息,IOCPS類提供了AddAndFlush函數(shù)。

            . 眾所周知,我們也需要添加序號(hào)和狀態(tài)(IOType 變量, IOZeroReadCompleted, 等等)到我們的緩沖中。

            . 我們也需要有將數(shù)據(jù)轉(zhuǎn)換到字節(jié)流或?qū)⒆止?jié)流轉(zhuǎn)換到數(shù)據(jù)的方法,CIOCPBuffer也提供了這些函數(shù)。


            以上所有問(wèn)題都在CIOCPBuffer中解決。

            3.8 如何使用源代碼
            從IOCP繼承你自己的類(如圖3),實(shí)現(xiàn)IOCPS類中的虛函數(shù)(例如,threadpool),
            在任何類型的服務(wù)端或客戶端中實(shí)現(xiàn)使用少量的線程有效地管理大量的連接。

            3.8.1 啟動(dòng)和關(guān)閉服務(wù)端/客戶端


            調(diào)用下面的函數(shù)啟動(dòng)服務(wù)端
            BOOL Start(int nPort=999,int iMaxNumConnections=1201,
            int iMaxIOWorkers=1,int nOfWorkers=1,
            int iMaxNumberOfFreeBuffer=0,
            int iMaxNumberOfFreeContext=0,
            BOOL bOrderedSend=TRUE,
            BOOL bOrderedRead=TRUE,
            int iNumberOfPendlingReads=4);

            nPort
            服務(wù)端偵聽的端口. ( -1 客戶端模式.)

            iMaxNumConnections
            允許最大的連接數(shù). (使用較大的數(shù).)

            iMaxIOWorkers
            I/O工作線程數(shù)

            nOfWorkers
            邏輯工作者數(shù)量Number of logical workers. (可以在運(yùn)行時(shí)改變.)

            iMaxNumberOfFreeBuffer
            重復(fù)使用的緩沖最大數(shù). (-1 不使用, 0= 不限)

            iMaxNumberOfFreeContext
            重復(fù)使用的客戶端信息對(duì)象數(shù) (-1 for 不使用, 0= 不限)

            bOrderedRead
            順序讀取. (我們已經(jīng)在 3.6.2. 處討論過(guò))

            bOrderedSend
            順序?qū)懭? (我們已經(jīng)在 3.6.2. 處討論過(guò))

            iNumberOfPendlingReads
            等待讀取數(shù)據(jù)時(shí)未決的異步讀取循環(huán)數(shù)

            連接到遠(yuǎn)程服務(wù)器(客戶端模式nPort=-1),調(diào)用函數(shù):
            CodeConnect(const CString &strIPAddr, int nPort)

            .strIPAddr
            遠(yuǎn)程服務(wù)器的IP地址

            .nPort
            端口

            調(diào)用ShutDown()關(guān)閉連接

            例如:
            if(!m_iocp.Start(-1,1210,2,1,0,0))
            AfxMessageBox("Error could not start the Client");
            ….
            m_iocp.ShutDown();
            

            4.1 源代碼描述
            更多關(guān)于源代碼的信息請(qǐng)參考代碼里的注釋。

            4.1.1 虛函數(shù)
            NotifyNewConnection
            新的連接已接受

            NotifyNewClientContext
            空的ClientContext結(jié)構(gòu)被分配

            NotifyDisconnectedClient
            客戶端連接斷開

            ProcessJob
            邏輯工作者需要處理一個(gè)工作

            NotifyReceivedPackage
            新的包到達(dá)

            NotifyFileCompleted
            文件傳送完成。

            4.1.2 重要變量
            所有變量共享使用時(shí)必須加鎖避免存取違例,所有需要加鎖的變量,名稱為XXX則鎖變量名稱為XXXLock。

            m_ContextMapLock;
            保存所有客戶端數(shù)據(jù)(socket,客戶端數(shù)據(jù),等等)

            ContextMap m_ContextMap;
            m_NumberOfActiveConnections
            保存已連接的連接數(shù)

            4.1.3 重要函數(shù)
            GetNumberOfConnections()
            返回連接數(shù)

            CString GetHostAdress(ClientContext* p)
            提供客戶端上下文,返回主機(jī)地址

            BOOL ASendToAll(CIOCPBuffer *pBuff);
            發(fā)送緩沖上下文到所有連接的客戶端

            DisconnectClient(CString sID)
            根據(jù)客戶端唯一編號(hào),斷開指定的客戶端

            CString GetHostIP()
            返回本地IP

            JobItem* GetJob()
            將JobItem從隊(duì)列中移出, 如果沒有job,返回 NULL

            BOOL AddJob(JobItem *pJob)
            添加Job到隊(duì)列

            BOOL SetWorkers(int nThreads)
            設(shè)置可以任何時(shí)候調(diào)用的邏輯工作者數(shù)量

            DisconnectAll();
            斷開所有客戶端

            ARead(…)
            異步讀取

            ASend(…)
            異步發(fā)送,發(fā)送數(shù)據(jù)到客戶端

            ClientContext* FindClient(CString strClient)
            根據(jù)字符串ID尋找客戶(非線程安全)

            DisconnectClient(ClientContext* pContext, BOOL bGraceful=FALSE);
            端口客戶

            DisconnectAll()
            端口所有客戶

            StartSendFile(ClientContext *pContext)
            根據(jù)ClientContext結(jié)構(gòu)發(fā)送文件(使用經(jīng)優(yōu)化的transmitfile(..) 函數(shù))

            PrepareReceiveFile(..)
            接收文件準(zhǔn)備。調(diào)用該函數(shù)時(shí),所有進(jìn)入的字節(jié)流已被寫入到文件。

            PrepareSendFile(..)
            打開文件并發(fā)送包含文件信息的數(shù)據(jù)包。函數(shù)禁用ASend(..)函數(shù),直到文件傳送關(guān)閉或中斷。

            DisableSendFile(..)
            禁止發(fā)送文件模式

            DisableRecevideFile(..)
            禁止文件接收模式

            5.1 文件傳輸
            文件傳輸使用Winsock 2.0 中的TransmitFile函數(shù)。TransmitFile函數(shù)通過(guò)連接的socket句柄傳送文件數(shù)據(jù)。函數(shù)使用操作系統(tǒng)的高速緩沖管理器(cache manager)接收文件數(shù)據(jù),通過(guò)sockets提供高性能的文件數(shù)據(jù)傳輸。異步文件傳輸要點(diǎn):
            在TransmitFile函數(shù)返回前,所有其他發(fā)送或?qū)懭氲皆搒ocket的操作都將無(wú)法執(zhí)行,因?yàn)檫@將使文件數(shù)據(jù)混亂。
            因此,在PrepareSendFile()函數(shù)調(diào)用之后,所有ASend都被禁止。
            因?yàn)椴僮飨到y(tǒng)連續(xù)讀取文件數(shù)據(jù),你可以使用FILE_FLAG_SEQUENTIAL_SCAN參數(shù)來(lái)優(yōu)化高速緩沖性能。
            發(fā)送文件時(shí)我們使用了內(nèi)核異步操作(TF_USE_KERNEL_APC)。TF_USE_KERNEL_APC的使用可以更好地提升性能。有可能, 無(wú)論如何,TransmitFile在線程中的大量使用,這種情形可能會(huì)阻止APCs的調(diào)用.

            文件傳輸按如下順序執(zhí)行:服務(wù)器調(diào)用PrepareSendFile(..)函數(shù)初始化文件傳輸。客戶端接收文件信息時(shí),調(diào)用PrepareReceiveFile(..)作接收前的準(zhǔn)備,并發(fā)送一個(gè)包到服務(wù)器告知開始文件傳送。當(dāng)包到達(dá)服務(wù)器端,服務(wù)器端調(diào)用StartSendFile(..)采用高性能的TransmitFile函數(shù)發(fā)送指定文件。

            6 源代碼示例
            提供的源代碼演示代碼是一個(gè)echo客戶端/服務(wù)器端程序,并提供了對(duì)文件傳輸?shù)闹С郑▓D4)。在代碼中,MyIOCP類從IOCP繼承,處理客戶端/服務(wù)器端的通訊,所涉及的虛函數(shù)可以參見4.1.1處。
            客戶端或服務(wù)器端最重要的部分是虛函數(shù)NotifyReceivedPackage,定義如下:

            void MyIOCP::NotifyReceivedPackage(CIOCPBuffer *pOverlapBuff,
            int nSize,ClientContext *pContext)
            {
            BYTE PackageType=pOverlapBuff->GetPackageType();
            switch (PackageType)
            {
            case Job_SendText2Client :
            Packagetext(pOverlapBuff,nSize,pContext);
            break;
            case Job_SendFileInfo :
            PackageFileTransfer(pOverlapBuff,nSize,pContext);
            break;
            case Job_StartFileTransfer:
            PackageStartFileTransfer(pOverlapBuff,nSize,pContext);
            break;
            case Job_AbortFileTransfer:
            DisableSendFile(pContext);
            break;
            };
            }
            

            函數(shù)接收進(jìn)入的信息并執(zhí)行遠(yuǎn)程連接發(fā)送的請(qǐng)求。這種情況,只是簡(jiǎn)單的echo或文件傳輸?shù)那樾巍7?wù)器端和客戶端源代碼分成兩個(gè)工程,IOCP和IOCPClient。

            6.1 編譯問(wèn)題
            使用VC++6.0或VC.NET編譯,你可能在編譯CFile時(shí)得到一些奇怪的錯(cuò)誤,如:


            “if (pContext->m_File.m_hFile !=
            INVALID_HANDLE_VALUE) <-error C2446: '!=' : no conversion "
            "from 'void *' to 'unsigned int'”

            這個(gè)問(wèn)題可以通過(guò)更新頭文件(*.h)或VC++ 6.0的版本或改變類型轉(zhuǎn)換錯(cuò)誤來(lái)解決,在修正了錯(cuò)誤后,服務(wù)器端/客戶端源代碼可以在不需MFC的情況下使用。

            7 特別的考慮和經(jīng)驗(yàn)總結(jié)
            當(dāng)你在其他類型的程序中使用本代碼,有一些可以避免的編程陷阱和多線程問(wèn)題。
            非確定錯(cuò)誤指的是那些隨機(jī)出現(xiàn)的錯(cuò)誤,很難通過(guò)執(zhí)行相同順序的任務(wù)來(lái)重現(xiàn)這些錯(cuò)誤。
            這是最壞的錯(cuò)誤類型,通常,錯(cuò)誤出現(xiàn)在內(nèi)部源代碼的設(shè)計(jì)中。當(dāng)服務(wù)器端有多個(gè)IO工作線程在運(yùn)行,
            為客戶端提供連接,如果程序員沒有考慮多線程環(huán)境,可能會(huì)發(fā)生存取違例等不確定錯(cuò)誤。

            經(jīng)驗(yàn) #1:
            在未對(duì)上下文加鎖時(shí),不要讀寫客戶上下文(例如:ClientContext)。
            通知函數(shù)(例如:Notify*(ClientContext *pContext))已經(jīng)是線程安全,處理成員變量ClientContext可以不需要解鎖、解鎖。

            //不要這樣使用
            // …
            If(pContext->m_bSomeData)
            pContext->m_iSomeData=0;
            // …
            
            // 應(yīng)該這樣使用
            //….
            pContext->m_ContextLock.Lock();
            If(pContext->m_bSomeData)
            pContext->m_iSomeData=0;
            pContext->m_ContextLock.Unlock();
            //…

            大家都知道的,當(dāng)你鎖定上下文,其他線程或GUI都將等待它。

            經(jīng)驗(yàn)#2:
            避免使用復(fù)雜的和其他類型的"上下文鎖",應(yīng)為容易造成死鎖(例如:A在等待B,B在等待C,C在等待A,A死鎖)

            pContext-> m_ContextLock.Lock();
            //… code code ..
            pContext2-> m_ContextLock.Lock();
            // code code..
            pContext2-> m_ContextLock.Unlock();
            // code code..
            pContext-> m_ContextLock.Unlock();

            以上代碼可能導(dǎo)致死鎖

            經(jīng)驗(yàn) #3:
            不要在通知函數(shù)(例如:Notify*(ClientContext *pContext))以外處理客戶上下文,如果你需要這樣做,你
            要放入
            m_ContextMapLock.Lock();

            m_ContextMapLock.Unlock();
            參考如下代碼:

            ClientContext* pContext=NULL ;
            m_ContextMapLock.Lock();
            pContext = FindClient(ClientID);
            // safe to access pContext, if it is not NULL
            // and are Locked (Rule of thumbs#1:)
            //code .. code..
            m_ContextMapLock.Unlock();
            // Here pContext can suddenly disappear because of disconnect.
            // do not access pContext members here.
            8 將來(lái)的工作
            將來(lái),代碼將提供以下功能:
            添加支持AcceptEx(..)接受新連接,處理短連接和DOS攻擊。
            源代碼兼容Win32,STL,WTL等環(huán)境。
            posted on 2009-10-26 17:56 會(huì)飛的兔子 閱讀(2060) 評(píng)論(1)  編輯 收藏 引用 所屬分類: 系統(tǒng)API,底層技術(shù)

            Feedback

            # re: 一個(gè)簡(jiǎn)單的完成端口類 2010-03-20 15:27 osbin
            為什么我覺得這個(gè)類有問(wèn)題呢?
            首先關(guān)于接收數(shù)據(jù)時(shí),投遞長(zhǎng)度為0的緩沖區(qū)。在這個(gè)程序中,它并不是說(shuō)先發(fā)送一個(gè)緩沖區(qū)長(zhǎng)度為0的去接收數(shù)據(jù),然后接收到完成通知時(shí)再調(diào)用緩沖區(qū)長(zhǎng)度不為0的去接收。而是在一個(gè)“循環(huán)”中,不斷地提交緩沖長(zhǎng)度為0的,中間也穿插的緩沖區(qū)長(zhǎng)度不為0的,等待接收數(shù)據(jù),這樣難道不會(huì)產(chǎn)生內(nèi)存被鎖定嗎?

            其次在OnReadCompleted方法中,如果接收數(shù)據(jù)按順序進(jìn)行接收,比如我投遞的順序是1、2、3、4、5,但是完成通知的時(shí)候,剛好反過(guò)來(lái)順序是5、4、3、2、1,那么
            if(m_bReadInOrder)
            pOverlapBuff = GetNextReadBuffer(pContext, pOverlapBuff);

            while(pOverlapBuff)
            {
            //TRACE("R> %i\r\n",pOverlapBuff->GetSequenceNumber());

            // Mark that we are Using the buffer..
            pOverlapBuff->Use(dwIoSize);
            #ifdef TRANSFERFILEFUNCTIONALITY
            if(!pContext->m_bFileReceivedMode)
            #endif
            ProcessPackage(pContext, dwIoSize, pOverlapBuff);
            #ifdef TRANSFERFILEFUNCTIONALITY
            else
            AddToFile(pContext,dwIoSize,pOverlapBuff);
            #endif
            IncreaseReadSequenceNumber(pContext);
            pOverlapBuff = NULL ;
            if(m_bReadInOrder) pOverlapBuff=GetNextReadBuffer(pContext);
            }
            ARead(pContext); //pOverlapBuff為NULL由于序號(hào)是5、4、3、2 勢(shì)必又調(diào)用這個(gè)方法4次。  回復(fù)  更多評(píng)論
              

            国产精品女同一区二区久久| 久久久久久久久久久免费精品| 偷窥少妇久久久久久久久| 一级做a爰片久久毛片免费陪 | 狠狠色丁香久久婷婷综合图片| 久久久久亚洲AV综合波多野结衣 | 亚洲国产精品久久久久久| 99久久国产亚洲高清观看2024| 免费一级欧美大片久久网| 亚洲午夜久久久久妓女影院 | 77777亚洲午夜久久多喷| 久久人人爽爽爽人久久久| 99久久精品免费观看国产| 伊人久久无码中文字幕| 久久国产精品77777| 久久久久国产视频电影| 久久综合九色综合网站| 久久久久亚洲AV成人网| 99久久做夜夜爱天天做精品| 女人香蕉久久**毛片精品| 日产久久强奸免费的看| 日本精品久久久中文字幕| 久久久久亚洲精品男人的天堂| 国产高潮国产高潮久久久| 亚洲婷婷国产精品电影人久久| 久久精品国产亚洲麻豆| 亚洲狠狠婷婷综合久久蜜芽| 日韩精品无码久久一区二区三| 996久久国产精品线观看| AV无码久久久久不卡蜜桃| 精品视频久久久久| 国产欧美久久久精品| 亚洲va久久久噜噜噜久久男同 | 亚洲AV成人无码久久精品老人| 久久国产香蕉视频| 欧美精品一区二区精品久久| 日产精品久久久久久久| 一本一本久久aa综合精品 | 久久国产精品99久久久久久老狼| 国产精品久久久久久五月尺| 91久久国产视频|