完成端口中的單句柄數(shù)據(jù)結(jié)構(gòu)與單IO數(shù)據(jù)結(jié)構(gòu)的理解與設(shè)計
本文作者:sodme
本文出處:http://blog.csdn.net/sodme
聲明:本文可以不經(jīng)作者同意任意轉(zhuǎn)載、復(fù)制、傳播,但任何對本文的引用均須保留本文的作者、出處及本行聲明信息!謝謝!
完成端口模型,針對于WIN平臺的其它異步網(wǎng)絡(luò)模型而言,最大的好處,除了性能方面的卓越外,還在于完成端口在傳遞網(wǎng)絡(luò)事件的通知時,可以一并傳遞與此事件相關(guān)的應(yīng)用層數(shù)據(jù)。這個應(yīng)用層數(shù)據(jù),體現(xiàn)在兩個方面:一是單句柄數(shù)據(jù),二是單IO數(shù)據(jù)。
GetQueuedCompletionStatus函數(shù)的原型如下:
WINBASEAPI
BOOL
WINAPI
GetQueuedCompletionStatus(
IN HANDLE CompletionPort,
OUT LPDWORD lpNumberOfBytesTransferred,
OUT PULONG_PTR lpCompletionKey,
OUT LPOVERLAPPED *lpOverlapped,
IN DWORD dwMilliseconds
);
其中,我們把第三個參數(shù)lpCompletionKey稱為完成鍵,由它傳遞的數(shù)據(jù)稱為單句柄數(shù)據(jù)。我們把第四個參數(shù)lpOverlapped稱為重疊結(jié)構(gòu)體,由它傳遞的數(shù)據(jù)稱為單IO數(shù)據(jù)。
以字面的意思來理解,lpCompletionKey內(nèi)包容的東西應(yīng)該是與各個socket一一對應(yīng)的,而lpOverlapped是與每一次的wsarecv或wsasend操作一一對應(yīng)的。
在網(wǎng)絡(luò)模型的常見設(shè)計中,當(dāng)一個客戶端連接到服務(wù)器后,服務(wù)器會通過accept或AcceptEx創(chuàng)建一個socket,而應(yīng)用層為了保存與此socket相關(guān)的其它信息(比如:該socket所對應(yīng)的sockaddr_in結(jié)構(gòu)體數(shù)據(jù),該結(jié)構(gòu)體內(nèi)含客戶端IP等信息,以及為便于客戶端的邏輯包整理而準(zhǔn)備的數(shù)據(jù)整理緩沖區(qū)等),往往需要創(chuàng)建一個與該socket一一對應(yīng)的客戶端底層通信對象,這個對象可以負(fù)責(zé)保存僅在網(wǎng)絡(luò)層需要處理的數(shù)據(jù)成員和方法,然后我們需要將此客戶端底層通信對象放入一個類似于list或map的容器中,待到需要使用的時候,使用容器的查找算法根據(jù)socket值找到它所對應(yīng)的對象然后進行我們所需要的操作。
讓人非常高興的是,完成端口“體貼入微”,它已經(jīng)幫我們在每次的完成事件通知時,稍帶著把該socket所對應(yīng)的底層通信對象的指針?biāo)徒o了我們,這個指針就是lpCompletionKey。也就是說,當(dāng)我們從GetQueuedCompletionStatus函數(shù)取得一個數(shù)據(jù)接收完成的通知,需要將此次收到的數(shù)據(jù)放到該socket所對應(yīng)的通信對象整理緩沖區(qū)內(nèi)對數(shù)據(jù)進行整理時,我們已經(jīng)不需要去執(zhí)行l(wèi)ist或map等的查找算法,而是可以直接定位這個對象了,當(dāng)客戶端連接量很大時,頻繁查表還是很影響效率的。哇哦,太帥了,不是嗎?呵呵。
基于以上的認(rèn)識,我們的lpCompletionKey對象可以設(shè)計如下:
typedef struct PER_HANDLE_DATA
{
SOCKET socket; //本結(jié)構(gòu)體對應(yīng)的socket值
sockaddr_in addr; //用于存放客戶端IP等信息
char DataBuf[ 2*MAX_BUFFER_SIZE ]; //整理緩沖區(qū),用于存放每次整理時的數(shù)據(jù)
}
PER_HANDLE_DATA與socket的綁定,通過CreateIOCompletionPort完成,將該結(jié)構(gòu)體地址作為該函數(shù)的第三個參數(shù)傳入即可。而PER_HANDLE_DATA結(jié)構(gòu)體中addr成員,是在accept執(zhí)行成功后進行賦值的。DataBuf則可以在每次WSARecv操作完成,需要整理緩沖區(qū)數(shù)據(jù)時使用。
下面我們再來看看完成端口的收、發(fā)操作中所使用到的重疊結(jié)構(gòu)體OVERLAPPED。
關(guān)于重疊IO的知識,請自行GOOGLE相關(guān)資料。簡單地說,OVERLAPPED是應(yīng)用層與核心層交互共享的數(shù)據(jù)單元,如果要執(zhí)行一個重疊IO操作,必須帶有OVERLAPPED結(jié)構(gòu)。在完成端口中,它允許應(yīng)用層對OVERLAPPED結(jié)構(gòu)進行擴展和自定義,允許應(yīng)用層根據(jù)自己的需要在OVERLAPPED的基礎(chǔ)上形成新的擴展OVERLAPPED結(jié)構(gòu)。一般地,擴展的OVERLAPPED結(jié)構(gòu)中,要求放在第一個的數(shù)據(jù)成員是原OVERLAPPED結(jié)構(gòu)。我們可以形如以下方式定義自己的擴展OVERLAPPED結(jié)構(gòu):
typedef struct PER_IO_DATA
{
OVERLAPPED ovl;
WSABUF buf;
char RecvDataBuf[ MAX_BUFFER_SIZE ]; //接收緩沖區(qū)
char SendDataBuf[ MAX_BUFFER_SIZE ]; //發(fā)送緩沖區(qū)
OpType opType; //操作類型:發(fā)送、接收或關(guān)閉等
}
在執(zhí)行WSASend和WSARecv操作時,應(yīng)用層會將擴展OVERLAPPED結(jié)構(gòu)的地址傳給核心,核心完成相應(yīng)的操作后,仍然通過原有的這個結(jié)構(gòu)傳遞操作結(jié)果,比如“接收”操作完成后,RecvDataBuf里存放便是此次接收下來的數(shù)據(jù)。
根據(jù)各自應(yīng)用的不同,不同的完成端口設(shè)計者可能會設(shè)計出不同的PER_HANDLE_DATA
和PER_IO_DATA,我這里給出的設(shè)計也只是針對自己的應(yīng)用場合的,不一定就適合你。但我想,最主要的還是要搞明白PER_HANDLE_DATA和PER_IO_DATA兩種結(jié)構(gòu)體的含義、用途,以及調(diào)用流程。