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

            twzheng's cppblog

            『站在風(fēng)口浪尖緊握住鼠標(biāo)旋轉(zhuǎn)!』 http://www.cnblogs.com/twzheng

              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
              136 隨筆 :: 78 文章 :: 353 評論 :: 0 Trackbacks
            Windows Sockets 2.0:使用完成端口高性能,可擴展性Winsock服務(wù)程序
            [摘自]http://blog.csdn.net/vcbear/

            翻譯說明:

            完成端口基本上公認為一種在windows服務(wù)平臺上比較成熟和高效的IO方法,理解和編寫程序都不是很困難。目前我正在進行這方面的實踐,代碼還沒有完全調(diào)試和評價,只有這一篇拙劣的學(xué)習(xí)翻譯文摘,見笑見笑。

            翻譯這個文章,是因為我近期在學(xué)習(xí)一些socket服務(wù)程序的編寫中發(fā)現(xiàn)(注意,只是在學(xué)習(xí),我本人在這個領(lǐng)域經(jīng)驗并不充足到可以撰文騙錢的地步:P),如果不是逼著自己把這個文章從頭翻譯一遍,我懷疑我是否能認真領(lǐng)會本文的內(nèi)容 :PPP. 把這個文章貼出來,不是為了賺人氣,而是因為水平確實有限,雖然整體上大差不差的翻譯出來了,但是細節(jié)和用詞上可能還是有很多問題。是希望大家能指出其中的翻譯錯誤和理解謬誤,互相交流和幫助。非常感謝。

            本文翻譯并沒有通過原作者同意,僅用來在網(wǎng)絡(luò)上學(xué)習(xí)和交流,加之翻譯水平拙劣,所以請勿用于做商業(yè)用途。

            vcbear

            2001.8

             

            Windows Sockets 2.0:使用完成端口高性能,可擴展性Winsock服務(wù)程序

            原作者:Anthony Jones Amol Deshpande

            原文在http://msdn.microsoft.com/msdnmag/issues/1000/winsock/winsock.asp

            • APIs和擴展性

               

            • 完成端口(Completion Ports

               

            • 典型的Worker Thread 結(jié)構(gòu)

               

            • Windows NT Windows 2000 Sockets體系結(jié)構(gòu)

               

            • 緩沖區(qū)由誰來管理

               

            • 資源約束

               

            • 關(guān)于接受連接

               

            • TransmitFile TransmitPackets函數(shù)

               

            • 來實現(xiàn)一個服務(wù)方案

               

             

            本文作者假定你已經(jīng)熟悉Winsock API,TCP/IP ,Win32 API

            摘要:編寫一般的網(wǎng)絡(luò)應(yīng)用程序的難點在于程序的“可擴展性”。利用完成端口進行重疊I/O的技術(shù)在WindowsNTWIndows2000上提供了真正的可擴展性。完成端口和Windows Socket2.0結(jié)合可以開發(fā)出支持大量連接的網(wǎng)絡(luò)服務(wù)程序。

            本文從討論服務(wù)端的實現(xiàn)開始,然后討論如何處理有系統(tǒng)資源約束和高要求的環(huán)境,以及在可擴展的服務(wù)程序開發(fā)的過程中會遇到的一般問題。

            --------------------------------------------------------------------------------

            正文:

            開發(fā)網(wǎng)絡(luò)程序從來都不是一件容易的事情,盡管只需要遵守很少的一些規(guī)則創(chuàng)建socket,發(fā)起連接,接受連接,發(fā)送和接受數(shù)據(jù)。真正的困難在于:讓你的程序可以適應(yīng)從單單一個連接到幾千個連接。本文主要關(guān)注C/S結(jié)構(gòu)的服務(wù)器端程序,因為一般來說,開發(fā)一個大容量,具可擴展性的winsock程序一般就是指服務(wù)程序。我們將討論基于WindowsNT4.0Windows 2000的代碼,而不包括Windows3.x(什么時候的東西了),因為Winsock2的這一屬性只在Windows NT4和最新版本上有效。

            APIs和擴展性

            win32重疊I/O(Overlapped I/O)機制允許發(fā)起一個操作,然后在操作完成之后接受到信息。對于那種需要很長時間才能完成的操作來說,重疊IO機制尤其有用,因為發(fā)起重疊操作的線程在重疊請求發(fā)出后就可以自由的做別的事情了。

            WinNTWin2000上,提供的真正的可擴展的I/O模型就是使用完成端口Completion Port)的重疊I/O.

            其實類似于WSAAsyncSelectselect函數(shù)的機制更容易兼容Unix,但是難以實現(xiàn)我們想要的“擴展性”。而且windows的完成端口機制在操作系統(tǒng)內(nèi)部已經(jīng)作了優(yōu)化,提供了更高的效率。所以,我們選擇完成端口開始我們的服務(wù)器程序的開發(fā)。

            完成端口(Completion Ports

            其實可以把完成端口看成系統(tǒng)維護的一個隊列,操作系統(tǒng)把重疊IO操作完成的事件通知放到該隊列里,由于是暴露 “操作完成”的事件通知,所以命名為“完成端口”(COmpletion Ports)。一個socket被創(chuàng)建后,可以在任何時刻和一個完成端口聯(lián)系起來。

            一般來說,一個應(yīng)用程序可以創(chuàng)建多個工作線程來處理完成端口上的通知事件。工作線程的數(shù)量依賴于程序的具體需要。但是在理想的情況下,應(yīng)該對應(yīng)一個CPU創(chuàng)建一個線程。因為在完成端口理想模型中,每個線程都可以從系統(tǒng)獲得一個“原子”性的時間片,輪番運行并檢查完成端口,線程的切換是額外的開銷。在實際開發(fā)的時候,還要考慮這些線程是否牽涉到其他堵塞操作的情況。如果某線程進行堵塞操作,系統(tǒng)則將其掛起,讓別的線程獲得運行時間。因此,如果有這樣的情況,可以多創(chuàng)建幾個線程來盡量利用時間。

            應(yīng)用完成端口分兩步走:

            1創(chuàng)建完成端口句柄:

            HANDLE hIocp;

            hIocp = CreateIoCompletionPort(

            INVALID_HANDLE_VALUE,

            NULL,

            (ULONG_PTR)0,

            0);

            if (hIocp == NULL) {

            // Error

            }

            注意在第一個參數(shù)(FileHandle)傳入INVALID_FILE_HANDLE,第二個參數(shù)(ExistingCompletionPort)傳入NULL,系統(tǒng)將創(chuàng)建一個新的完成端口句柄,沒有任何IO句柄與其關(guān)聯(lián)。

            2.完成端口創(chuàng)建成功后,socket和完成端口之間建立關(guān)聯(lián)。再次調(diào)用CreateIoCmpletionPort函數(shù),這一次在第一個參數(shù)FileHandle傳入創(chuàng)建的socket句柄,參數(shù)ExistingCompletionPort為已經(jīng)創(chuàng)建的完成端口句柄。

            以下代碼創(chuàng)建了一個socket并把它和完成端口聯(lián)系起來。

            SOCKET s;

            s = socket(AF_INET, SOCK_STREAM, 0);

            if (s == INVALID_SOCKET) {

            // Error

            if (CreateIoCompletionPort((HANDLE)s,

            hIocp,

            (ULONG_PTR)0,

            0) == NULL)

            {

            // Error

            }

            ???

            }

            到此為止socket已經(jīng)成功和完成端口相關(guān)聯(lián)。在此socket上進行的重疊IO操作結(jié)果均使用完成端口發(fā)出通知。注意:CreateIoCompletionPort函數(shù)的第三個參數(shù)允許開發(fā)人員傳入一個類型為ULONG_PTR的數(shù)據(jù)成員,我們把它稱為完成鍵(Completion key),此數(shù)據(jù)成員可以設(shè)計為指向包含socket信息的一個結(jié)構(gòu)體的一個指針,用來把相關(guān)的環(huán)境信息和socket聯(lián)系起來,每次完成通知來到的同時,該環(huán)境信息也隨著通知一起返回給開發(fā)人員。

            完成端口創(chuàng)建以及與socket關(guān)聯(lián)之后,就要創(chuàng)建一個或多個工作線程來處理完成通知,每個線程都可以循環(huán)的調(diào)用GetQueuedCompletionStatus函數(shù),檢查完成端口上的通知事件。

            在舉例說明一個典型的工作線程的之前,我們先討論一下重疊IO的過程。當(dāng)一個重疊IO被發(fā)起,一個Overlapped結(jié)構(gòu)體的指針就要作為參數(shù)傳遞給系統(tǒng)。當(dāng)操作完成,GetQueueCompletionStatus可以返回指向同一個Overlapp結(jié)構(gòu)的指針。為了辨認和定位這個已完成的操作,開發(fā)人員最好定義自己的OVERLAPPED結(jié)構(gòu),以包含一些自己定義的關(guān)于操作本身的額外信息。比如:

            typedef struct _OVERLAPPEDPLUS {

            OVERLAPPED ol;

            SOCKET s, sclient;

            int OpCode;

            WSABUF wbuf;

            DWORD dwBytes, dwFlags;

            // other useful information

            } OVERLAPPEDPLUS;

            此結(jié)構(gòu)的第一個成員為默認的OVERLAPPED結(jié)構(gòu),第二,三個為本地服務(wù)socket和與該操作相關(guān)的客戶socekt,4個成員為操作類型,對于socket,現(xiàn)在定義的有

            #define OP_READ 0

            #define OP_WRITE 1

            #define OP_ACCEPT 2

            3種。然后還有應(yīng)用程序的socket緩沖區(qū),操作數(shù)據(jù)量,標(biāo)志位以及其他開發(fā)人員認為有用的信息。

            當(dāng)進行重疊IO操作,把OVERLAPPEDPLUS結(jié)構(gòu)作為重疊IO的參數(shù)lpOverlapp傳遞(如WSASend,WASRecv,等函數(shù),有一個lpOverlapped參數(shù),要求傳入一個OVERLAPP結(jié)構(gòu)的指針)

            當(dāng)操作完成后,GetQueuedCompletionStatus函數(shù)返回一個LPOVERLAPPED 類型的指針,這個指針其實是指向開發(fā)人員定義的擴展OVERLAPPEDPLUS結(jié)構(gòu),包含著開發(fā)人員早先傳入的全部信息。

            注意 OVERLAPPED成員不一定要求是OVERLAPPEDPLUS擴展結(jié)構(gòu)的一個成員,在獲得OVERLAPPED指針之后,可以用CONTAINING_RECORD宏獲得相應(yīng)的擴展結(jié)構(gòu)的指針。

             

             

            典型的Worker Thread 結(jié)構(gòu)

            DWORD WINAPI WorkerThread(LPVOID lpParam)

            {

            ULONG_PTR *PerHandleKey;

            OVERLAPPED *Overlap;

            OVERLAPPEDPLUS *OverlapPlus,

            *newolp;

            DWORD dwBytesXfered;

            while (1)

            {

            ret = GetQueuedCompletionStatus(

            hIocp,

            &dwBytesXfered,

            (PULONG_PTR)&PerHandleKey,

            &Overlap,

            INFINITE);

            if (ret == 0)

            {

            // Operation failed

            continue;

            }

            OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);

            switch (OverlapPlus->OpCode)

            {

            case OP_ACCEPT:

            // Client socket is contained in OverlapPlus.sclient

            // Add client to completion port

            CreateIoCompletionPort(

            (HANDLE)OverlapPlus->sclient,

            hIocp,

            (ULONG_PTR)0,

            0);

            // Need a new OVERLAPPEDPLUS structure

            // for the newly accepted socket. Perhaps

            // keep a look aside list of free structures.

            newolp = AllocateOverlappedPlus();

            if (!newolp)

            {

            // Error

            }

            newolp->s = OverlapPlus->sclient;

            newolp->OpCode = OP_READ;

            // This function prepares the data to be sent

            PrepareSendBuffer(&newolp->wbuf);

            ret = WSASend(

            newolp->s,

            &newolp->wbuf,

            1,

            &newolp->dwBytes,

            0,

            &newolp.ol,

            NULL);

            if (ret == SOCKET_ERROR)

            {

            if (WSAGetLastError() != WSA_IO_PENDING)

            {

            // Error

            }

            }

            // Put structure in look aside list for later use

            FreeOverlappedPlus(OverlapPlus);

            // Signal accept thread to issue another AcceptEx

            SetEvent(hAcceptThread);

            break;

            case OP_READ:

            // Process the data read

            // ???

            // Repost the read if necessary, reusing the same

            // receive buffer as before

            memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED));

            ret = WSARecv(

            OverlapPlus->s,

            &OverlapPlus->wbuf,

            1,

            &OverlapPlus->dwBytes,

            &OverlapPlus->dwFlags,

            &OverlapPlus->ol,

            NULL);

            if (ret == SOCKET_ERROR)

            {

            if (WSAGetLastError() != WSA_IO_PENDING)

            {

            // Error

            }

            }

            break;

            case OP_WRITE:

            // Process the data sent, etc.

            break;

            } // switch

            } // while

            } // WorkerThread

             

            --------------------------------------------------------------------------------

            查看以上代碼,注意如果Overlapped操作立刻失敗(比如,返回SOCKET_ERROR或其他非WSA_IO_PENDING的錯誤),則沒有任何完成通知時間會被放到完成端口隊列里。反之,則一定有相應(yīng)的通知時間被放到完成端口隊列。

            更完善的關(guān)于Winsock的完成端口機制,可以參考MSDNMicrosoft PlatFormSDK,那里有完成端口的例子。訪問http://msdn.microsoft.com/library/techart/msdn_servrapp.htm.可以獲得更多信息。

             

             

             

             

            Windows NT Windows 2000 Sockets體系結(jié)構(gòu)

            學(xué)習(xí)一些WinNTWin2000基本的Sockets體系結(jié)構(gòu)有益與對擴展性規(guī)則的理解。下圖表示當(dāng)前版本Win2000Winsock實現(xiàn)。應(yīng)用程序不應(yīng)該依賴于這里描述的一些底層細節(jié)(指drivers ,Dlls之類的),因為這些可能會在未來版本的操作系統(tǒng)中被改變。

             

            Figure 3 Socket Architecture
            Socket 體系結(jié)構(gòu)

            Winsock2.0規(guī)范支持多種協(xié)議以及相關(guān)的支持服務(wù)。這些用戶模式服務(wù)支持可以基于其他現(xiàn)存服務(wù)提供者來擴展他們自己的功能。比如,一個代理層服務(wù)支持(LSP)可以把自己安裝在現(xiàn)存的TCP/IP服務(wù)頂層。這樣,代理服務(wù)就可以截取和重定向一個對底層功能的調(diào)用。

            與其他操作系統(tǒng)不同的是,WinNTWin2000的傳輸協(xié)議層并不直接給應(yīng)用程序提供socket風(fēng)格的接口,不接受應(yīng)用程序的直接訪問。而是實現(xiàn)了更多的通用API,稱為傳輸驅(qū)動接口(Transport Driver Interface,TDI).這些APIWinNT的子系統(tǒng)從各種各樣的網(wǎng)絡(luò)編程接口中分離出來。然后,通過Winsock內(nèi)核模式驅(qū)動提供了sockets方法(在AFD.SYS里實現(xiàn))。這個驅(qū)動負責(zé)連接和緩沖管理,對應(yīng)用程序提供socket風(fēng)格的編程接口。AFD.SYS則通過TDI和傳輸協(xié)議驅(qū)動層交流數(shù)據(jù)。

            緩沖區(qū)由誰來管理

            如上所說,對于使用socket接口和傳輸協(xié)議層交流的應(yīng)用程序來說,AFD.SYS負責(zé)緩沖區(qū)的管理。也就是說,當(dāng)一個程序調(diào)用sendWSASend函數(shù)發(fā)送數(shù)據(jù)的時候,數(shù)據(jù)被復(fù)制到AFD.SYS的內(nèi)部緩沖里(大小根據(jù)SO_SNDBUF設(shè)置),然后sendWSASend立刻返回。之后數(shù)據(jù)由AFD.SYS負責(zé)發(fā)送到網(wǎng)絡(luò)上,與應(yīng)用程序無關(guān)。當(dāng)然,如果應(yīng)用程序希望發(fā)送比SO_SNDBUF設(shè)置的緩沖區(qū)還大的數(shù)據(jù),WSASend函數(shù)將會被堵塞,直到所有數(shù)據(jù)均被發(fā)送完畢為止。

            同樣,當(dāng)從遠地客戶端接受數(shù)據(jù)的時候,如果應(yīng)用程序沒有提交receive請求,而且線上數(shù)據(jù)沒有超出SO_RCVBUF設(shè)置的緩沖大小,那么AFD.SYS就把網(wǎng)絡(luò)上的數(shù)據(jù)復(fù)制到自己的內(nèi)部緩沖保存。當(dāng)應(yīng)用程序調(diào)用recvWSARecv函數(shù)的時候,數(shù)據(jù)即從AFD.SYS的緩沖復(fù)制到應(yīng)用程序提供的緩沖區(qū)里。

            在大多數(shù)情況下,這個體系工作的很好。尤其是應(yīng)用程序使用一般的發(fā)送接受例程不牽涉使用Overlapped的時候。開發(fā)人員可以通過使用setsockopt API函數(shù)把SO_SNDBUFSO_RCVBUF這兩個設(shè)置的值改為0關(guān)閉AFD.SYS的內(nèi)部緩沖。但是,這樣做會帶來一些后果:

            比如,應(yīng)用程序把SO_SNDBUF設(shè)為0,關(guān)閉了發(fā)送緩沖(指AFD.SYS里的緩沖),并發(fā)出一個同步堵塞式的發(fā)送操作,應(yīng)用程序提供的數(shù)據(jù)緩沖區(qū)就會被內(nèi)核鎖定,send函數(shù)不會返回,直到連接的另一端收到整個緩沖區(qū)的數(shù)據(jù)為止。這貌似一種挺不錯的方法,用來判斷是否你的數(shù)據(jù)已經(jīng)被對方全部收取。但實際上,這是很糟糕的。問題在于:網(wǎng)絡(luò)層即使收到遠端TCP的確認,也不能保證數(shù)據(jù)會被安全交到客戶端應(yīng)用程序那里,因為客戶端可能發(fā)生“資源不足”等情況,而導(dǎo)致應(yīng)用程序無法從AFD.SYS的內(nèi)部緩沖復(fù)制得到數(shù)據(jù)。而更重大的問題是:由于堵塞,程序在一個線程里只能進行一次send操作,非常的沒有效率。

            如果關(guān)閉接受緩沖(設(shè)置SO_RCVBUF的值為0),也不能真正的提高效率。接受緩沖為0迫使接受的數(shù)據(jù)在比winsock內(nèi)核層更底層的地方被緩沖,同樣在調(diào)用recv的時候進行才進行緩沖復(fù)制,這樣你關(guān)閉AFD緩沖的根本意圖(避免緩沖復(fù)制)就落空了。關(guān)閉接收緩沖是沒有必要的,只要應(yīng)用程序經(jīng)常有意識的在一個連接上調(diào)用重疊WSARecvs操作,這樣就避免了AFD老是要緩沖大量的到來數(shù)據(jù)。

            到這里,我們應(yīng)該清楚關(guān)閉緩沖的方法對絕大多數(shù)應(yīng)用程序來說沒有太多好處的了。

            然而,一個高性能的服務(wù)程序可以關(guān)閉發(fā)送緩沖,而不影響性能。這樣的程序必須確保它在同時執(zhí)行多個Overlapped發(fā)送,而不是等待一個Overlapped發(fā)送結(jié)束之后,才執(zhí)行另一個。這樣如果一個數(shù)據(jù)緩沖區(qū)數(shù)據(jù)已經(jīng)被提交,那么傳輸層就可以立刻使用該數(shù)據(jù)緩沖區(qū)。如果程序“串行”的執(zhí)行Overlapped發(fā)送,就會浪費一個發(fā)送提交之后另一個發(fā)送執(zhí)行之前那段時間。

             

             

            資源約束

            魯棒性是每一個服務(wù)程序的一個主要設(shè)計目標(biāo)。就是說,服務(wù)程序應(yīng)該可以對付任何的突發(fā)問題,比如,客戶端請求的高峰,可用內(nèi)存的暫時貧缺,以及其他可靠性問題。為了平和的解決這些問題,開發(fā)人員必須了解典型的WindowsNTWindows2000平臺上的資源約束。

            最基本的問題是網(wǎng)絡(luò)帶寬。使用UDP協(xié)議進行發(fā)送的服務(wù)程序?qū)Υ艘筝^高,因為這樣的服務(wù)程序要求盡量少的丟包率。即使是使用TCP連接,服務(wù)器也必須注意不要濫用網(wǎng)絡(luò)資源。否則,TCP連接中將會出現(xiàn)大量重發(fā)和連接取消事件。具體的帶寬控制是跟具體程序相關(guān)的,超出了本文的討論范圍。

            程序所使用的虛擬內(nèi)存也必須小心。應(yīng)該保守的執(zhí)行內(nèi)存申請和釋放,或許可以使用旁視列表(一個記錄申請并使用過的“空閑”內(nèi)存的緩沖區(qū))來重用已經(jīng)申請但是被程序使用過,空閑了的內(nèi)存,這樣可以使服務(wù)程序避免過多的反復(fù)申請內(nèi)存,并且保證系統(tǒng)中一直有盡可能多的空余內(nèi)存。(應(yīng)用程序還可以使用SetWorkingSetSize這個Win32API函數(shù)來向系統(tǒng)請求增加該程序可用的物理內(nèi)存。)

            有兩個winsock程序不會直接面對的資源約束。第一個是頁面鎖定限制。無論應(yīng)用程序發(fā)起send還是receive操作,也不管AFD.SYS的緩沖是否被禁止,數(shù)據(jù)所在的緩沖都會被鎖定在物理內(nèi)存里。因為內(nèi)核驅(qū)動要訪問該內(nèi)存的數(shù)據(jù),在訪問期間該內(nèi)存區(qū)域都不能被解鎖。在大部分情況下,這不會產(chǎn)生任何問題。但是操作系統(tǒng)必須確認還有可用的可分頁內(nèi)存來提供給其他程序。這樣做的目的是防止一個有錯誤操作的程序請求鎖定所有的物理RAM,而導(dǎo)致系統(tǒng)崩潰。這意味著,應(yīng)用程序必須有意識的避免導(dǎo)致過多頁面鎖定,使該數(shù)量達到或超過系統(tǒng)限制。

            WinNTWin2000中,系統(tǒng)允許的總共的內(nèi)存鎖定的限制大概是物理內(nèi)存的1/8。這只是粗略的估計,不能作為一個準(zhǔn)確的計算數(shù)據(jù)。只是需要知道,有時重疊IO操作會發(fā)生ERROR_INSUFFICIENT_RESOURCE失敗,這是因為可能同時有太多的send/receives操作在進行中。程序應(yīng)該注意避免這種情況。

            另一個的資源限制情況是,程序運行時,系統(tǒng)達到非分頁內(nèi)存池的限制。WinNTWin2000的驅(qū)動從指定的非分頁內(nèi)存池中申請內(nèi)存。這個區(qū)域里分配的內(nèi)存不會被扇出,因為它包含了多個不同的內(nèi)核對象可能需要訪問的數(shù)據(jù),而有些內(nèi)核對象是不能訪問已經(jīng)扇出的內(nèi)存的。一旦系統(tǒng)創(chuàng)建了一個socket (或打開一個文件),一定數(shù)目的非分頁內(nèi)存就被分配了。另外,綁定(binding)和連接socket也會導(dǎo)致額外的非分頁內(nèi)存池的分配。更進一步的說,一個I/O請求,比如sendreceive,也是分配了很少的一點非分頁內(nèi)存池的(為了跟蹤I/O操作的進行,包含必須信息的一個很小的結(jié)構(gòu)體被分配了)。積少成多,最后還是可能導(dǎo)致問題。因此操作系統(tǒng)限制了非分頁內(nèi)存的數(shù)量。在winNTwin2000平臺上,每個連接分配的非分頁內(nèi)存的準(zhǔn)確數(shù)量是不相同的,在未來的windows版本上也可能保持差異。如果你想延長你的程序的壽命,就不要打算在你的程序中精確的計算和控制你的非分頁內(nèi)存的數(shù)量。

            雖然不能準(zhǔn)確計算,但是程序在策略上要注意避免沖擊非分頁限制。當(dāng)系統(tǒng)的非分頁池內(nèi)存枯竭,一個跟你的程序完全無關(guān)的的驅(qū)動都有可能出問題,因為它無法正常的申請到非分頁內(nèi)存。最壞的情況下,會導(dǎo)致整個系統(tǒng)崩潰。比如那些第三方設(shè)備或系統(tǒng)本身的驅(qū)動。切記:在同一臺計算機上,可能還有其他的服務(wù)程序在運行,同樣在消耗非分頁內(nèi)存。開發(fā)人員應(yīng)該用最保守的策略估算資源,并基于此策略開發(fā)程序。

            資源約束的解決方案是很復(fù)雜的,因為事實上,當(dāng)資源不足的情況發(fā)生時,可能不會有特定的錯誤代碼返回到程序。程序在調(diào)用函數(shù)時可能可以得到類似WSAENOBUFS

            ERROR_INSUFFICIENT_RESOURCES的這種一般的返回代碼。如何處理這些錯誤呢,首先,合理的增加程序的工作環(huán)境設(shè)置(Working set,如果想獲得更多信息,請參考MSDNJohn Robbins關(guān)于 Bugslayer的一章)。如果仍然不能解決問題,那么你可能遇上了非分頁內(nèi)存池限制。那么最好是立刻關(guān)閉部分連接,并期待情況恢復(fù)正常。

             

             

            關(guān)于接受連接

            服務(wù)程序最常做的一個事情是接受客戶端的連接。AcceptEx函數(shù)是Winsock API中唯一可以使用重疊IO方式接受Socket連接的函數(shù)。AccpetEx要求一個傳入一個socket 作為它的參數(shù)。普通的同步accept函數(shù),新的SOCKET是作為返回值得到的。AcceptEx函數(shù)作為一個重疊操作,接收Socket應(yīng)該提前被創(chuàng)建(但不需要綁定和或連接),并傳入此API

            AcceptEx原形,加粗的即為需要傳入的socket

            BOOL AcceptEx(

            SOCKET sListenSocket,

            SOCKET sAcceptSocket,

            PVOID lpOutputBuffer,

            DWORD dwReceiveDataLength,

            DWORD dwLocalAddressLength,

            DWORD dwRemoteAddressLength,

            LPDWORD lpdwBytesReceived,

            LPOVERLAPPED lpOverlapped

            );

            使用AcceptEx的例程可能是這個樣子的:

            do {

            -Wait for a previous AcceptEx to complete //等待前一個AcceptEx完成

            -Create a new socket and associate it with the completion port //創(chuàng)建一個新的Socket并將其關(guān)聯(lián)

            //到完成端口

            -Allocate context structure etc. //初始化相關(guān)的環(huán)境信息結(jié)構(gòu)

            -Post an AcceptEx request. //進入AcceptEx請求。

            }while(TRUE);

             

            一個服務(wù)器一直具備足夠的AcceptEx調(diào)用,這樣就可以立刻響應(yīng)客戶機的連接。AcceptEx操作的數(shù)量取決于服務(wù)器的策略。如果要滿足高連接率(比如大量的短暫連接或爆發(fā)性的流量)的話,當(dāng)然比不常發(fā)生連接的程序需要更多的AcceptEx入口。聰明的策略就是根據(jù)流量改變AcceptEx調(diào)用的數(shù)量,而避免只使用一個確定的數(shù)目。

            Win2000上,Winsock提供了一些幫助,用來判斷AcceptEx調(diào)用的數(shù)量是否跟不上需要。當(dāng)創(chuàng)建一個監(jiān)聽Socket之后,使用WSAEventSelect函數(shù)把它和一個FD_ACCEPT事件關(guān)聯(lián),如果沒有accept未決的調(diào)用正在進行,一旦有請求到來,該事件(FD_ACCEPT)就會發(fā)生。因此此事件可以用來告訴開發(fā)人員:還需要進行更多的AcceptEx操作,或者由此探測到一個有異常行為的遠端實體。注意:此機制在NT上是無效的。

            使用AcceptEx的顯著好處是:在一次連接中就可以獲取客戶端的數(shù)據(jù),見AcceptExlpOutputBuffer參數(shù)。這意味著如果客戶端連接并立刻發(fā)送數(shù)據(jù)的話,AcceptEx將在客戶端連接成功和數(shù)據(jù)發(fā)送之后才完成。這個功能同時導(dǎo)致的問題是:AcceptEx必須等待數(shù)據(jù)接受完成才能返回。因為一個帶Output緩沖的AcceptEx函數(shù)并非一個“原子”操作,而是兩步的過程:接受連接和等待數(shù)據(jù)。所以程序在數(shù)據(jù)接受之前并不會知道連接成功。當(dāng)然客戶端也可以連接到服務(wù)器而不馬上發(fā)送數(shù)據(jù),如果這樣的連接過多,服務(wù)器將開始拒絕合法的連接,因為沒有可用的未決的Accept操作入口。這也是一種常用的方法,通過拒絕訪問,防止惡意攻擊和海量連接。

            在正在接受連接的線程中,可以檢查AcceptEx調(diào)用傳入的socket,調(diào)用getsockopt檢查其SO_CONNECT_TIME,該值返回的是socket連接的時間,沒有連接的時候返回-1

            根據(jù)WSAEventSelect機制所帶來的特性,我們可以很容易的判斷是否應(yīng)該檢查傳到AcceptEx函數(shù)的socket句柄的連接時間。如果在一定時間里,AcceptEx沒有從某個連接中收到數(shù)據(jù),AcceptEx可以通過關(guān)閉該socket來斷開連接。在不緊急的情況下,程序不應(yīng)該關(guān)閉一個AcceptEx里處于未連接狀態(tài)的socket ,因為系統(tǒng)考慮到性能問題,關(guān)聯(lián)在AcceptEx上的內(nèi)核態(tài)數(shù)據(jù)結(jié)構(gòu)不會被釋放,直到一個新的連接到來或監(jiān)聽socket本身都關(guān)閉了。

            乍看起來,一個發(fā)出AcceptEx請求的線程同時也可以是一個關(guān)聯(lián)在完成端口上,并且處理其他完成IO事件的工作線程。然而,最好不要設(shè)計這樣一個線程。在winsocket2的層次結(jié)構(gòu)上有一個副作用,那就是一個socket/WSASocket API的開銷是相當(dāng)可觀的,每個AccepEx都需要創(chuàng)建一個新的socket,所以最好創(chuàng)建一個單獨的跟其他IO處理無關(guān)的線程來調(diào)用AcceptEx。當(dāng)然,你還可以利用這個線程來進行其他的工作如創(chuàng)建事件log

            關(guān)于AcceptEx要注意的最后一個事情是:Winsock2的其他供應(yīng)商不一定會實現(xiàn)AcceptEx函數(shù)。同樣情況也包括的其他Microsoft的特定APIsTransmitFile,GetAcceptExSockAddrs以及其他Microsoft將在以后版本的windows里。在運行WinNTWin2000的系統(tǒng)上,這些APIsMicrosoft提供的DLL(mswsock.dll)里實現(xiàn),可以通過鏈接mswsock.lib或者通過WSAioctlSIO_GET_EXTENSION_FUNCTION_POINTER操作動態(tài)調(diào)用這些擴展APIs.

             

            未獲取函數(shù)指針就調(diào)用函數(shù)(如直接連接mswsock..lib并直接調(diào)用AcceptEx)的消耗是很大的,因為AcceptEx 實際上是存在于Winsock2結(jié)構(gòu)體系之外的。每次應(yīng)用程序常試在服務(wù)提供層上(mswsock之上)調(diào)用AcceptEx時,都要先通過WSAIoctl獲取該函數(shù)指針。如果要避免這個很影響性能的操作,應(yīng)用程序最好是直接從服務(wù)提供層通過WSAIoctl先獲取這些APIs的指針。

            TransmitFile TransmitPackets函數(shù)

            Winsock提供了兩個專為文件和內(nèi)存數(shù)據(jù)傳輸而優(yōu)化過的函數(shù)。TransmitFile APIWinNTWin2000均有效,而TransmitPackets作為一個新的擴展函數(shù),將在未來版本的windows里實現(xiàn)。

            TransmitFile可以把文件的內(nèi)容通過socket傳輸。一般情況下,如果應(yīng)用程序通過socket傳輸文件,首先要用CreateFile打開文件,并循環(huán)調(diào)用ReadFileWSASend函數(shù),讀取一段數(shù)據(jù)然后發(fā)送,直到整個文件發(fā)送完畢。這樣的效率很低,因為ReadFileWSASend調(diào)用都需要系統(tǒng)在用戶態(tài)和核心態(tài)之間進行轉(zhuǎn)換。TransmitFile則只需要知道需要傳輸?shù)奈募浔鸵獋鬏數(shù)淖止?jié)數(shù),只有CreateFile打開文件獲得句柄這個向核心態(tài)躍遷的這一個額外開銷。如果你的程序需要通過socket發(fā)送大量文件,建議使用此函數(shù)。

            函數(shù)的原形如下:

            BOOL TransmitFile(

            SOCKET hSocket,

            HANDLE hFile,

            DWORD nNumberOfBytesToWrite,

            DWORD nNumberOfBytesPerSend,

            LPOVERLAPPED lpOverlapped,

            LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,

            DWORD dwFlags

            );

            TransmitPackets APITransmitFile API更進一步,允許調(diào)用者一次指定多個文件句柄和內(nèi)存緩沖區(qū),并進行傳輸。原形如下:

            BOOL TransmitPackets(

            SOCKET hSocket,

            LPTRANSMIT_PACKET_ELEMENT lpPacketArray,

            DWORD nElementCount,

            DWORD nSendSize,

            LPOVERLAPPED lpOverlapped,

            DWORD dwFlags

            );

            lpPacketArray包含結(jié)構(gòu)體的數(shù)組。每個入口點指定一個需要被傳輸?shù)奈募浔騼?nèi)存緩沖,該結(jié)構(gòu)的成員如下:

            typedef struct _TRANSMIT_PACKETS_ELEMENT {

            DWORD dwElFlags;

            DWORD cLength;

            union {

            struct {

            LARGE_INTEGER nFileOffset;

            HANDLE hFile;

            };

            PVOID pBuffer;

            };

            } TRANSMIT_FILE_BUFFERS;

            各成員的名字都是自解釋的。dwEIFlags成員指明結(jié)構(gòu)體里的元素是一個文件句柄(TF_ELEMENT_FILE)還是一個內(nèi)存緩沖(TF_ELEMENT_MEMORY)cLength成員表示要傳輸?shù)淖止?jié)數(shù)(對于文件句柄,0則表示文件內(nèi)所有的數(shù)據(jù))。一個未命名的聯(lián)合體(union)包含內(nèi)存緩沖指針或文件句柄(以及指定的偏移量)。

            使用這兩個函數(shù)的其他好處是,你可以通過指定TF_REUSE_SOCKET標(biāo)志(必須同時指定TF_DISCONNECT標(biāo)志)重用socket句柄。一旦API函數(shù)完成數(shù)據(jù)傳輸,連接就會在傳輸點的層次上斷開,然后該socket就可以被AcceptEx重新使用。這樣就可以減少反復(fù)創(chuàng)建socket和的次數(shù),優(yōu)化了效率。

            使用這兩個函數(shù)要注意的是,在WinNT Workstation版本或win2000 Professional版本上,并不能實現(xiàn)優(yōu)化性能。必須在winNT,win2000 Server ,Win2000 Advanced Server Win2000 Data Center版本上才能實現(xiàn)。

             

            來實現(xiàn)一個服務(wù)方案

            在前幾章里,我們介紹了一些有益于改善性能提高擴展性的APIs和方法,以及可能遇到的資源瓶頸。這對你有用嗎?當(dāng)然,首先取決于你的服務(wù)端和客戶端的設(shè)計。在設(shè)計時,你對服務(wù)端和客戶端的控制越得力,你就能更好的避免瓶頸

            讓我們來看一種簡單的用例,在這個用例里我們設(shè)計一個服務(wù)器,這個服務(wù)器處理客戶端的連接,然后客戶端發(fā)送一次數(shù)據(jù),并期待服務(wù)端的回應(yīng),然后客戶端斷開連接。

            我們的設(shè)計是:服務(wù)器創(chuàng)建一個監(jiān)聽socket,并和一個完成端口相關(guān)聯(lián),然后創(chuàng)建和CPU同等數(shù)量的工作線程,以及一個專門用來進行AcceptEx調(diào)用的線程。既然我們知道客戶端一旦連接馬上就會發(fā)送數(shù)據(jù) ,那么準(zhǔn)備一個接受緩沖區(qū)會有利于工作的進行。當(dāng)然,不要忘記經(jīng)常的檢查正在連接的socketSO_CONNECT_TIME值,避免死連接。

            本設(shè)計中重要的一項是決定需要顯形調(diào)用多少個AcceptEx。因為每個AcceptEx操作都需要一個接收緩沖區(qū),大量的頁面將被鎖定(還記得每個重疊操作都會消耗一些非分頁內(nèi)存,并且會將一些數(shù)據(jù)緩沖鎖定到內(nèi)存里嗎)。沒有公式和具體的準(zhǔn)則指導(dǎo)如何確定究竟允許多少個AcceptEx操作。最好的方案就是使這個數(shù)目成為可調(diào)的,通過性能測試,尋找一個在典型的環(huán)境下最好的值

            現(xiàn)在已經(jīng)確定服務(wù)器是如何處理連接的了,下一步就是發(fā)送數(shù)據(jù)。影響發(fā)送數(shù)據(jù)的重要因素就是你期望服務(wù)器能夠并發(fā)的處理連接數(shù)。一般來說,服務(wù)器應(yīng)該限制并發(fā)的連接數(shù)量,以及顯式的send調(diào)用。越多的連接數(shù)意味著越多的非分頁內(nèi)存的使用,并發(fā)的send調(diào)用也應(yīng)該被限制,避免沖擊系統(tǒng)的可分頁內(nèi)存鎖定極限。連接數(shù)和并發(fā)的send調(diào)用限制也都應(yīng)該是程序可調(diào)節(jié)的。

            在本例的情況里,不必要去取消每個socket的接收緩沖,因為接收事件僅僅在AcceptEx調(diào)用中發(fā)生。保證每個socket都有一個接收緩沖不會造成什么危害。一旦客戶端/服務(wù)器在最初的一次請求(由AcceptEx完成)之后進行交互,發(fā)送更多的數(shù)據(jù),那么取消接收緩沖更是一個很不好的做法。除非你能保證這些數(shù)據(jù)都是在每個連接的重疊IO接收里完成的

             

            結(jié)束語:

            重復(fù):開發(fā)一個可擴展的Winsock服務(wù)器并非十分困難的。僅僅是開始一個監(jiān)聽socket,接收連接,并且進行重疊發(fā)送和接收的IO操作。最大的挑戰(zhàn)就是管理系統(tǒng)資源,限制重疊Io的數(shù)量,避免內(nèi)存危機。遵循這幾個原則,就能幫助你開發(fā)高性能,可擴展的服務(wù)程序。

            posted on 2007-05-30 17:24 譚文政 閱讀(1449) 評論(0)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程vc++.net
            久久se精品一区二区影院| 一本久道久久综合狠狠躁AV | 亚洲中文字幕无码久久综合网 | 久久99精品免费一区二区| 亚洲国产成人乱码精品女人久久久不卡| 亚洲精品综合久久| 91精品国产91久久久久福利| 久久亚洲精品无码观看不卡| 国产精品无码久久综合| 欧美无乱码久久久免费午夜一区二区三区中文字幕 | 国产精品美女久久久久AV福利| 久久久久波多野结衣高潮| 国产精品一区二区久久精品无码| 欧美噜噜久久久XXX| 伊人情人综合成人久久网小说| 亚洲国产二区三区久久| 欧美丰满熟妇BBB久久久| 亚洲精品乱码久久久久久蜜桃 | 欧美日韩成人精品久久久免费看 | 狠狠精品干练久久久无码中文字幕| 噜噜噜色噜噜噜久久| 国产精品九九久久免费视频 | 国产91久久综合| 久久99精品久久久久久hb无码| 伊人 久久 精品| 久久天天躁狠狠躁夜夜2020老熟妇| 国产成人精品久久免费动漫| 久久久www免费人成精品| 精品无码久久久久久国产| 亚洲欧美精品伊人久久| 久久国产一区二区| 欧美精品一区二区精品久久| 国内精品九九久久久精品| 久久久久人妻一区精品色| 99久久精品免费看国产一区二区三区 | 久久亚洲国产成人影院网站| 久久99国产一区二区三区| 91亚洲国产成人久久精品网址| 久久亚洲精品视频| 国产精品免费久久| 久久久久国产|