青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

twzheng's cppblog

『站在風口浪尖緊握住鼠標旋轉(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é)習翻譯文摘,見笑見笑。

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

本文翻譯并沒有通過原作者同意,僅用來在網(wǎng)絡(luò)上學(xué)習和交流,加之翻譯水平拙劣,所以請勿用于做商業(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的過程。當一個重疊IO被發(fā)起,一個Overlapped結(jié)構(gòu)體的指針就要作為參數(shù)傳遞給系統(tǒ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ù)量,標志位以及其他開發(fā)人員認為有用的信息。

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

當操作完成后,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é)習一些WinNTWin2000基本的Sockets體系結(jié)構(gòu)有益與對擴展性規(guī)則的理解。下圖表示當前版本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風格的接口,不接受應(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ū)動負責連接和緩沖管理,對應(yīng)用程序提供socket風格的編程接口。AFD.SYS則通過TDI和傳輸協(xié)議驅(qū)動層交流數(shù)據(jù)。

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

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

同樣,當從遠地客戶端接受數(shù)據(jù)的時候,如果應(yīng)用程序沒有提交receive請求,而且線上數(shù)據(jù)沒有超出SO_RCVBUF設(shè)置的緩沖大小,那么AFD.SYS就把網(wǎng)絡(luò)上的數(shù)據(jù)復(fù)制到自己的內(nèi)部緩沖保存。當應(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è)計目標。就是說,服務(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。這只是粗略的估計,不能作為一個準確的計算數(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)存的準確數(shù)量是不相同的,在未來的windows版本上也可能保持差異。如果你想延長你的程序的壽命,就不要打算在你的程序中精確的計算和控制你的非分頁內(nèi)存的數(shù)量。

雖然不能準確計算,但是程序在策略上要注意避免沖擊非分頁限制。當系統(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ù)雜的,因為事實上,當資源不足的情況發(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ā)性的流量)的話,當然比不常發(fā)生連接的程序需要更多的AcceptEx入口。聰明的策略就是根據(jù)流量改變AcceptEx調(diào)用的數(shù)量,而避免只使用一個確定的數(shù)目。

Win2000上,Winsock提供了一些幫助,用來判斷AcceptEx調(diào)用的數(shù)量是否跟不上需要。當創(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ù)接受之前并不會知道連接成功。當然客戶端也可以連接到服務(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的開銷是相當可觀的,每個AccepEx都需要創(chuàng)建一個新的socket,所以最好創(chuàng)建一個單獨的跟其他IO處理無關(guān)的線程來調(diào)用AcceptEx。當然,你還可以利用這個線程來進行其他的工作如創(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標志(必須同時指定TF_DISCONNECT標志)重用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和方法,以及可能遇到的資源瓶頸。這對你有用嗎?當然,首先取決于你的服務(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ù) ,那么準備一個接受緩沖區(qū)會有利于工作的進行。當然,不要忘記經(jīng)常的檢查正在連接的socketSO_CONNECT_TIME值,避免死連接。

本設(shè)計中重要的一項是決定需要顯形調(diào)用多少個AcceptEx。因為每個AcceptEx操作都需要一個接收緩沖區(qū),大量的頁面將被鎖定(還記得每個重疊操作都會消耗一些非分頁內(nèi)存,并且會將一些數(shù)據(jù)緩沖鎖定到內(nèi)存里嗎)。沒有公式和具體的準則指導(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 譚文政 閱讀(1464) 評論(0)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程vc++.net
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久综合狠狠| 欧美久久久久久久久久| 麻豆精品在线观看| 久久久精品久久久久| 欧美一级大片在线免费观看| 一区二区三区欧美日韩| 一区二区三区导航| 亚洲视频一区二区在线观看| 亚洲小视频在线观看| 亚洲线精品一区二区三区八戒| 久久精品国产96久久久香蕉| 亚洲综合色激情五月| 夜夜精品视频| 亚洲欧美中文另类| 久久香蕉国产线看观看av| 久久久久国产一区二区| 欧美www在线| 99热在线精品观看| 亚洲一区二区在线观看视频| 久久国产精品久久w女人spa| 免费观看亚洲视频大全| 欧美日韩精品一区二区三区| 国产欧美日韩亚洲精品| 精品成人国产| 亚洲性视频网站| 久久亚洲综合| 亚洲伦理在线观看| 久久男人资源视频| 欧美日韩国产成人精品| 国产亚洲综合精品| 一区二区三区毛片| 久久伊人亚洲| 亚洲影院高清在线| 欧美激情一区二区三级高清视频| 亚洲小视频在线观看| 亚洲欧美国产精品va在线观看| 午夜亚洲性色福利视频| 欧美在线观看一区| 欧美激情精品久久久久久久变态| 亚洲欧美日韩国产一区二区三区 | 亚洲欧美精品一区| 久久激情综合网| 欧美日韩亚洲高清| 亚洲人成久久| 久久亚洲色图| 久久成人精品无人区| 欧美日韩专区在线| 亚洲激情在线观看| 另类激情亚洲| 欧美在线观看网址综合| 国产精品久久久久毛片软件| 亚洲人成亚洲人成在线观看图片| 国模私拍一区二区三区| 亚洲欧美日韩一区二区三区在线| 亚洲作爱视频| 欧美国产综合一区二区| 欧美一区视频| 国产精品尤物| 欧美影院视频| 午夜国产精品影院在线观看| 国产精品都在这里| 亚洲欧美在线磁力| 99视频在线观看一区三区| 欧美精品在线视频观看| 在线视频亚洲欧美| 亚洲美女淫视频| 国产精品黄色| 性伦欧美刺激片在线观看| 一区二区三区久久网| 国产精品久久久久久久浪潮网站| 国产精品网站在线观看| 亚洲夜晚福利在线观看| 99国产精品99久久久久久| 欧美先锋影音| 午夜精品久久久久久久99热浪潮| 国产情侣久久| 久久久久亚洲综合| 久久国产加勒比精品无码| 激情自拍一区| 亚洲国产精品久久久久秋霞不卡| 亚洲精品影院| 欧美日韩国产三级| 亚洲午夜精品一区二区| 亚洲视频一区二区免费在线观看| 久久av免费一区| 国内精品久久久久久久影视蜜臀| 亚洲福利视频网站| 欧美激情免费在线| 欧美日韩伦理在线| 久久国产精彩视频| 久久久久久亚洲精品杨幂换脸| 欧美波霸影院| 99精品国产在热久久| 久久综合九色综合久99| 国产有码一区二区| 性色av一区二区三区在线观看 | 蜜臀va亚洲va欧美va天堂 | 欧美伊人久久久久久午夜久久久久 | 久久免费99精品久久久久久| 老色鬼久久亚洲一区二区| 久久综合狠狠| 在线一区二区三区四区| 黄色成人免费网站| 欧美精品在线网站| 久久人人九九| 亚洲韩国精品一区| 亚洲一卡久久| 日韩一级免费观看| 国产原创一区二区| 欧美日韩一区二区免费在线观看| 国产日产高清欧美一区二区三区| 日韩一二三区视频| 欧美亚洲综合另类| 久久精品123| 亚洲精品1区| 伊人久久亚洲影院| 亚洲视频欧美在线| 欧美一区二区黄| 在线中文字幕一区| 亚洲欧美激情一区二区| 久久一区国产| 99国产精品久久| 欧美有码视频| 欧美国产日韩视频| 欧美电影打屁股sp| 久久婷婷蜜乳一本欲蜜臀| 久久精品一区二区三区四区| 欧美电影专区| 国产午夜精品视频| 亚洲美女中出| av成人免费观看| 日韩亚洲欧美一区| 欧美一区2区视频在线观看| 欧美伊人久久久久久午夜久久久久| 国产美女搞久久| 在线观看91精品国产入口| 国产真实乱偷精品视频免| 狠狠操狠狠色综合网| 亚洲欧洲一区二区在线播放| 在线性视频日韩欧美| 欧美一级在线亚洲天堂| 亚洲欧美视频在线| 99人久久精品视频最新地址| 欧美先锋影音| 国产精品欧美一区喷水| 欧美日本不卡视频| 欧美精品首页| 国产日韩欧美一区| 亚洲免费激情| 麻豆91精品| 日韩天堂在线观看| 久久综合色婷婷| 亚洲国产精品99久久久久久久久| 亚洲视频网站在线观看| 欧美成人在线免费观看| 欧美一区亚洲一区| 欧美a级片网站| 狠狠色噜噜狠狠狠狠色吗综合| 亚洲三级影片| 制服丝袜激情欧洲亚洲| 欧美激情视频给我| 欧美一区二区在线免费观看| 在线亚洲观看| 国产有码一区二区| 久久久久在线观看| 欧美精品在线一区| 午夜亚洲一区| 久久久久久久一区二区| 亚洲欧美激情精品一区二区| 日韩性生活视频| 亚洲国产精品视频一区| 一区二区欧美视频| av不卡在线观看| 久久久久在线观看| 亚洲欧美三级在线| 亚洲午夜视频在线观看| 亚洲东热激情| 欧美在线视频一区二区三区| 一本色道久久88亚洲综合88| 亚洲欧美日韩成人高清在线一区| 欧美国产日韩一区二区| 亚洲激情社区| 亚洲一二三四区| 亚洲日本在线观看| 性做久久久久久久久| 亚洲福利在线看| 欧美在线一区二区| 亚洲视频一区二区免费在线观看| 一本色道久久99精品综合| 一区二区三区www| 黄色成人小视频| 久久一区二区三区四区五区| 久久婷婷麻豆| 久久riav二区三区| 国产精品久久9| 一本久久综合亚洲鲁鲁五月天| 亚洲视频999| 亚洲精品中文字幕有码专区| 久久亚洲影音av资源网| 久久精品亚洲精品|