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