早在兩年前我就已經(jīng)能很熟練的運(yùn)用完成端口這種技術(shù)了,只是一直沒(méi)有機(jī)會(huì)將它用在什么項(xiàng)目中,這段時(shí)間見(jiàn)到這種技術(shù)被過(guò)分炒作,過(guò)分的神秘化,就想寫(xiě)一篇解釋它如何工作的文章.想告訴大家它沒(méi)有傳說(shuō)中的那么高深難懂!有什么錯(cuò)誤的地方還請(qǐng)高人指正.轉(zhuǎn)載請(qǐng)注明出處及作者,謝謝!

以一個(gè)文件傳輸服務(wù)端為例,在我的機(jī)器上它只起兩個(gè)線(xiàn)程就可以為很多個(gè)個(gè)客戶(hù)端同時(shí)提供文件下載服務(wù),程序的性能會(huì)隨機(jī)器內(nèi)CPU個(gè)數(shù)的增加而線(xiàn)性增長(zhǎng),我盡可能做到使它清晰易懂,雖然程序很小卻用到了NT 5的一些新特性,重疊IO,完成端口以及線(xiàn)程池,基于這種模型的服務(wù)端程序應(yīng)該是NT系統(tǒng)上性能最好的了.

首先.做為完成端口的基礎(chǔ),我們應(yīng)該理解重疊IO,這需要你已經(jīng)理解了內(nèi)核對(duì)象及操作系統(tǒng)的一些概念概念,什么是信號(hào)/非信號(hào)態(tài),什么是等待函數(shù),什么是成功等待的副作用,什么是線(xiàn)程掛起等,如果這些概令還沒(méi)有理解,你應(yīng)該先看一下Windows 核心編程中的相關(guān)內(nèi)容.如果已經(jīng)理解這些,那么重疊IO對(duì)你來(lái)說(shuō)并不難.

你可以這樣認(rèn)為重疊IO,現(xiàn)在你已經(jīng)進(jìn)入一個(gè)服務(wù)器/客戶(hù)機(jī)環(huán)境,請(qǐng)不要混淆概念,這里的服務(wù)器是指操作系統(tǒng),而客戶(hù)機(jī)是指你的程序(它進(jìn)行IO操作),是當(dāng)你進(jìn)行IO操作(send,recv,writefile,readfile....)時(shí)你發(fā)送一個(gè)IO請(qǐng)求給服務(wù)器(操作系統(tǒng)),由服務(wù)器來(lái)完成你需要的操作,然后你什么事都沒(méi)有了,當(dāng)服務(wù)器完成IO請(qǐng)求時(shí)它會(huì)通知你,當(dāng)然在這期間你可以做任何事,一個(gè)常用的技巧是在發(fā)送重疊IO請(qǐng)求后,程序在一個(gè)循環(huán)中一邊調(diào)用PeekMessage,TranslateMessage和DispatchMessage更新界面,同時(shí)調(diào)用GetOverlappedResult等待服務(wù)器完成IO操作,更高效一點(diǎn)的做法是使用IO完成例程來(lái)處理服務(wù)器(操作系統(tǒng))返回的結(jié)果,但并不是每個(gè)支持重疊IO操作的函數(shù)都支持完成例程如TransmitFile函數(shù).

例1.一次重疊寫(xiě)操作過(guò)程(GetOverlappedResult方法):
1.填寫(xiě)一個(gè)OVERLAPPED結(jié)構(gòu)
2.進(jìn)行一次寫(xiě)操作,并指定重疊操作參數(shù)(上面的OVERLAPPED結(jié)構(gòu)變量的指針)
3.做其它事(如更新界面)
4.GetOverlappedResult取操作結(jié)果
5.如果IO請(qǐng)求沒(méi)有完成,并且沒(méi)有出錯(cuò)則回到期3
6.處理IO操作結(jié)果

例2.一次重疊寫(xiě)操作過(guò)程(完成例程方法):
1.填寫(xiě)一個(gè)OVERLAPPED結(jié)構(gòu)
2.進(jìn)行一次寫(xiě)操作,并指定重疊操作參數(shù)(上面的OVERLAPPED結(jié)構(gòu)變量的指針),并指定完成例程
3.做其它事(如更新界面)
4.當(dāng)完成例程被調(diào)用說(shuō)明IO操作已經(jīng)完成或出錯(cuò),現(xiàn)在可以對(duì)操作結(jié)果進(jìn)行處理了


如果你已經(jīng)理解上面的概念,就已經(jīng)很接近IO完成端口了,當(dāng)然這只是很常規(guī)的重疊操作它已經(jīng)非常高效,但如果再結(jié)合多線(xiàn)程對(duì)一個(gè)File或是Socket進(jìn)行重疊IO操作就會(huì)非常復(fù)雜,通常程序員很難把握這種復(fù)雜度.完成端口可以說(shuō)就是為了充分發(fā)揮多線(xiàn)程和重疊IO操作相結(jié)合的性能而設(shè)計(jì)的.很多人都說(shuō)它復(fù)雜,其實(shí)如果你自己實(shí)現(xiàn)一個(gè)多線(xiàn)程的對(duì)一個(gè)File或是Socket進(jìn)行重疊IO操作的程序(注意是多個(gè)線(xiàn)程對(duì)一個(gè)HANDLE或SOCKET進(jìn)行重疊IO操作,而不是啟一個(gè)線(xiàn)程對(duì)一個(gè)HANDLE進(jìn)行重疊IO操作)就會(huì)發(fā)現(xiàn)完成端口實(shí)際上簡(jiǎn)化了多線(xiàn)程里使用重疊IO的復(fù)雜度,并且性能更高,性能高在哪?下面進(jìn)行說(shuō)明.

我們可能寫(xiě)過(guò)這樣的服務(wù)端程序:

例3.主程序:
1.監(jiān)聽(tīng)一個(gè)端口
2.等待連接
3.當(dāng)有連接來(lái)時(shí)
4.啟一個(gè)線(xiàn)程對(duì)這個(gè)客戶(hù)端進(jìn)行處理
5.回到2

服務(wù)線(xiàn)程:
1.讀客戶(hù)端請(qǐng)求
2.如果客戶(hù)端不再有請(qǐng)求,執(zhí)行6
3.處理請(qǐng)求
4.返回操作結(jié)果
5.回到1
6.退出線(xiàn)程

這是一種最簡(jiǎn)單的網(wǎng)絡(luò)服務(wù)器模型,我們把它優(yōu)化一下

例4.主程序:
1.開(kāi)一個(gè)線(xiàn)程池,里面有機(jī)器能承受的最大線(xiàn)程數(shù)個(gè)線(xiàn)程,線(xiàn)程都處于掛起(suspend)狀態(tài)
1.監(jiān)聽(tīng)一個(gè)端口
2.等待連接
3.當(dāng)有連接來(lái)時(shí)
4.從線(xiàn)程池里Resume一個(gè)線(xiàn)程對(duì)這個(gè)客戶(hù)端進(jìn)行處理
5.回到2

服務(wù)線(xiàn)程與例3模型里的相同,只是當(dāng)線(xiàn)程處理完客戶(hù)端所有請(qǐng)求后,不是退出而是回到線(xiàn)程池,再次掛起讓出CPU時(shí)間,并等待為下一個(gè)客戶(hù)機(jī)服務(wù).當(dāng)然在此期間線(xiàn)程會(huì)因?yàn)镮O操作(服務(wù)線(xiàn)程的第1,5操作,也許還有其它阻塞操作)掛起自己,但不會(huì)回到線(xiàn)程池,也就是說(shuō)它一次只能為一個(gè)客戶(hù)端服務(wù).

這可能是你能想到的最高效的服務(wù)端模型了吧!它與第一個(gè)服務(wù)端模型相比少了很多個(gè)用戶(hù)態(tài)到內(nèi)核態(tài)的CONTEXT Switch,反映也更加快速,也許你可能覺(jué)得這很微不足道,這說(shuō)明你缺少對(duì)大規(guī)模高性能服務(wù)器程序(比如網(wǎng)游服務(wù)端)的認(rèn)識(shí),如果你的服務(wù)端程序要對(duì)幾千萬(wàn)個(gè)客戶(hù)端進(jìn)行服務(wù)呢?這也是微軟Windows NT開(kāi)發(fā)組在NT 5以上的系統(tǒng)中添加線(xiàn)程池的原因.

思考一下什么樣的模型可以讓一個(gè)線(xiàn)程為多個(gè)客戶(hù)端服務(wù)呢!那就要跳出每來(lái)一個(gè)連接啟線(xiàn)程為其服務(wù)的固定思維模式,我們把線(xiàn)程服務(wù)的最小單元分割為單獨(dú)的讀或?qū)懖僮?注意是讀或?qū)懖皇亲x和寫(xiě)),而不是一個(gè)客戶(hù)端從連接到斷開(kāi)期間的所有讀寫(xiě)操作.每個(gè)線(xiàn)程都使用重疊IO進(jìn)行讀寫(xiě)操作,投遞了讀寫(xiě)請(qǐng)求后線(xiàn)程回到線(xiàn)程池,等待為其它客戶(hù)機(jī)服務(wù),當(dāng)操作完成或出錯(cuò)時(shí)再回來(lái)處理操作結(jié)果,然后再回到線(xiàn)程池.

看看這樣的服務(wù)器模型:
例5.主程序:
1.開(kāi)一個(gè)線(xiàn)程池,里面有機(jī)器內(nèi)CPU個(gè)數(shù)兩倍的線(xiàn)程,線(xiàn)程都處于掛起(suspend)狀態(tài),它們?cè)诙嫉忍幚硪淮沃丿BIO操作的完成結(jié)果
1.監(jiān)聽(tīng)一個(gè)端口
2.等待連接
3.當(dāng)有連接來(lái)時(shí)
4.投遞一個(gè)重疊讀操作讀取命令
5.回到2

服務(wù)線(xiàn)程:
1.如果讀完成,則處理讀取的內(nèi)容(如HTTP GET命令),否則執(zhí)行3
2.投遞一個(gè)重疊寫(xiě)操作(如返回HTTP GET命令需要的網(wǎng)頁(yè))
3.如果是一個(gè)寫(xiě)操作完成,可以再投遞一個(gè)重疊讀操作,讀取客戶(hù)機(jī)的下一個(gè)請(qǐng)求,或者是關(guān)閉連接(如HTTP協(xié)議里每發(fā)完一個(gè)網(wǎng)頁(yè)就斷開(kāi))
4.取得下一個(gè)重疊IO操作結(jié)果,如果IO操作沒(méi)有完成或沒(méi)有IO操作則回到線(xiàn)程池

假設(shè)這是一個(gè)WEB服務(wù)器程序,可以看到工作者線(xiàn)程是以讀或?qū)憺樽钚〉墓ぷ鲉卧\(yùn)行的,在主程序里面進(jìn)行了一次重疊讀操作

當(dāng)讀操作完成時(shí)一個(gè)線(xiàn)程池中的一個(gè)工作者線(xiàn)程被激活取得了操作結(jié)果,處理GET或POST命令,然后發(fā)送一個(gè)網(wǎng)頁(yè)內(nèi)容,發(fā)送也是一個(gè)重疊操作,然后處理對(duì)其它客戶(hù)機(jī)的IO操作結(jié)果,如果沒(méi)有其它的東西需要處理時(shí)回到線(xiàn)程池等待.可以看到使用這種模型發(fā)送和接收可以是也可以不是一個(gè)線(xiàn)程.

當(dāng)發(fā)送操作完成時(shí),線(xiàn)程池中的一個(gè)工作者線(xiàn)程池激活,它關(guān)閉連接(HTTP協(xié)議),然后處理其它的IO操作結(jié)果,如果沒(méi)有其它的東西需要處理時(shí)回到線(xiàn)程池等待.

看看在這樣的模型中一個(gè)線(xiàn)程怎么為多個(gè)客戶(hù)端服務(wù),同樣是模擬一個(gè)WEB服務(wù)器例子:

假如現(xiàn)在系統(tǒng)中有兩個(gè)線(xiàn)程,ThreadA,ThreadB它們?cè)诙嫉忍幚硪淮沃丿BIO操作的完成結(jié)果

當(dāng)一個(gè)客戶(hù)機(jī)ClientA連接來(lái)時(shí)主程序投遞一個(gè)重疊讀操作,然后等待下一個(gè)客戶(hù)機(jī)連接,當(dāng)讀操作完成時(shí)ThreadA被激活,它收到一個(gè)HTTP GET命令,然后ThreadA使用重疊寫(xiě)操作發(fā)送一個(gè)網(wǎng)頁(yè)給ClientA,然后立即回到線(xiàn)程池等待處理下一個(gè)IO操作結(jié)果,這時(shí)發(fā)送操作還沒(méi)有完成,又有一個(gè)客戶(hù)機(jī)ClientB連接來(lái),主程序再投遞一個(gè)重疊讀操作,當(dāng)讀操作完成時(shí)ThreadA(當(dāng)然也可能是ThreadB)再次被激活,它重復(fù)同樣步驟,收到一個(gè)GET命令,使用重疊寫(xiě)操作發(fā)送一個(gè)網(wǎng)頁(yè)給ClientB,這次它沒(méi)有來(lái)得及回到線(xiàn)程池時(shí),又有一個(gè)連接ClientC連入,主程序再投遞一個(gè)重疊讀操作,讀操作完成時(shí)ThreadB被激活(因?yàn)門(mén)hreadA還沒(méi)有回到線(xiàn)程池)它收到一個(gè)HTTP GET命令,然后ThreadB使用重疊寫(xiě)操作發(fā)送一個(gè)網(wǎng)頁(yè)給ClientC,然后ThreadB回到線(xiàn)程池,這時(shí)ThreadA也回到了線(xiàn)程池.

可以想象現(xiàn)在有三個(gè)掛起的發(fā)送操作分別是ThreadA發(fā)送給ClientA和ClientB的網(wǎng)頁(yè),以及ThreadB發(fā)送給ClientC的網(wǎng)頁(yè),它們由操作系統(tǒng)內(nèi)核來(lái)處理.ThreadA和ThreadB現(xiàn)在已經(jīng)回到線(xiàn)程池,可以繼續(xù)為其它任何客戶(hù)端服務(wù).

當(dāng)對(duì)ClientA的重疊寫(xiě)操作已經(jīng)完成,ThreadA(也可以是ThreadB)又被激活它關(guān)閉與ClientA連接,但還沒(méi)有回到線(xiàn)程池,與此同時(shí)發(fā)送給ClientB的重疊寫(xiě)操作也完成,ThreadB被激活(因?yàn)門(mén)hreadA還沒(méi)有回到線(xiàn)程池)它關(guān)閉與ClientB的連接,然后回到線(xiàn)程池,這時(shí)ClientC的寫(xiě)操作也完成,ThreadB再次被激活(因?yàn)門(mén)hreadA還是沒(méi)有回到線(xiàn)程池),它再關(guān)閉與ClientC的連接,這時(shí)ThreadA回到線(xiàn)程池,ThreadB也回到線(xiàn)程池.這時(shí)對(duì)三個(gè)客戶(hù)端的服務(wù)全部完成.可以看到在整個(gè)服務(wù)過(guò)程中,"建立連接","讀數(shù)據(jù)","寫(xiě)數(shù)據(jù)"和"關(guān)閉連接"等操作是邏輯上連續(xù)而實(shí)際上分開(kāi)的.

到現(xiàn)在為止兩個(gè)線(xiàn)程處理了三次讀操作和三次寫(xiě)操作,在這些讀寫(xiě)操作過(guò)程中所出現(xiàn)的狀態(tài)機(jī)(state machine)是比較復(fù)雜的,我們模擬的是經(jīng)過(guò)我簡(jiǎn)化過(guò)的,實(shí)際上的狀態(tài)要比這個(gè)還要復(fù)雜很多,然而這樣的服務(wù)端模型在客戶(hù)端請(qǐng)求越多時(shí)與前兩個(gè)模型相比的性能越高.而使用完成端口我們可以很容易實(shí)現(xiàn)這樣的服務(wù)器模型.

微軟的IIS WEB服務(wù)器就是使用這樣的服務(wù)端模型,很多人說(shuō)什么阿帕奇服務(wù)器比IIS的性能好什么什么的我表示懷疑,除非阿帕奇服務(wù)器可以將線(xiàn)程分割成,為更小的單元服務(wù),我覺(jué)得不太可能!這種完成端口模型已經(jīng)將單個(gè)讀或?qū)懖僮髯鳛樽钚〉姆?wù)單元,我覺(jué)得在相同機(jī)器配置的情況下IIS的性能要遠(yuǎn)遠(yuǎn)高于其它WEB服務(wù)器,這也是從實(shí)現(xiàn)機(jī)理上來(lái)分析的,如果出現(xiàn)性能上的差別可能是在不同的操作系統(tǒng)上,也許Linux的內(nèi)核比Windows的要好,有人真的研究過(guò)嗎?還是大家一起在炒作啊.

對(duì)于狀態(tài)機(jī)概念,在很多方面都用到,TCPIP中有,編譯原理中有,OpengGL中有等等,我的離散數(shù)學(xué)不好(我是會(huì)計(jì)專(zhuān)業(yè)不學(xué)這個(gè)),不過(guò)還是搞懂了些,我想如果你多花些時(shí)間看,還是可以搞懂的.最后是一個(gè)簡(jiǎn)單的文件傳輸服務(wù)器程序代碼,只用了兩個(gè)線(xiàn)程(我的機(jī)器里只有一塊CPU)就可以服務(wù)多個(gè)客戶(hù)端.我調(diào)試時(shí)用它同時(shí)為6個(gè)nc客戶(hù)端提供文件下載服務(wù)都沒(méi)有問(wèn)題,當(dāng)然更多也不會(huì)有問(wèn)題,只是略為使用了一下NT 5的線(xiàn)程池和完成端口技術(shù)就可以有這樣高的性能,更不用說(shuō)IIS的性能咯!

希望大家不要陷在這個(gè)程序的框架中,Ctrl+C,Ctrl+V沒(méi)有什么意義,要理解它的實(shí)質(zhì).程序使用Visual C++ 6.0 SP5+2003 Platform SDK編譯通過(guò),在Windows XP Professional下調(diào)試運(yùn)行通過(guò).程序運(yùn)行的最低要求是Windows 2000操作系統(tǒng).

/********************************************************************
  created:   2005/12/24
  created:   24:12:2005   20:25
  modified:   2005/12/24
  filename:   d:\vcwork\iocomp\iocomp.cpp
  file path:   d:\vcwork\iocomp
  file base:   iocomp
  file ext:   cpp
  author:     kruglinski(kruglinski_at_gmail_dot_com)
 
  purpose:   利用完成端口技術(shù)實(shí)現(xiàn)的高性能文件下載服務(wù)程序
*********************************************************************/

#define _WIN32_WINNT   0x0500

#include <cstdlib>
#include <clocale>
#include <ctime>
#include <iostream>//一使用輸入輸出流程序頓時(shí)增大70K
#include <vector>
#include <algorithm>
#include <winsock2.h>
#include <mswsock.h>

using namespace std;

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"mswsock.lib")

const int MAX_BUFFER_SIZE=1024;
const int PRE_SEND_SIZE=1024;
const int QUIT_TIME_OUT=3000;
const int PRE_DOT_TIMER=QUIT_TIME_OUT/80;

typedef enum{IoTransFile,IoSend,IoRecv,IoQuit} IO_TYPE;

typedef struct
{
  SOCKET hSocket;
  SOCKADDR_IN ClientAddr;
}PRE_SOCKET_DATA,*PPRE_SOCKET_DATA;

typedef struct
{
  OVERLAPPED   oa;
  WSABUF     DataBuf;
  char     Buffer[MAX_BUFFER_SIZE];
  IO_TYPE     IoType;
}PRE_IO_DATA,*PPRE_IO_DATA;

typedef vector<PPRE_SOCKET_DATA>   SocketDataVector;
typedef vector<PPRE_IO_DATA>     IoDataVector;

SocketDataVector   gSockDataVec;
IoDataVector     gIoDataVec;

CRITICAL_SECTION   csProtection;

char* TimeNow(void)
{
  time_t t=time(NULL);
  tm *localtm=localtime(&t);
  static char timemsg[512]={0};
 
  strftime(timemsg,512,"%Z: %B %d %X,%Y",localtm);
  return timemsg;
}

BOOL TransFile(PPRE_IO_DATA pIoData,PPRE_SOCKET_DATA pSocketData,DWORD dwNameLen)
{
  //這一句是為nc做的,你可以修改它
  pIoData->Buffer[dwNameLen-1]='\0';
 
  HANDLE hFile=CreateFile(pIoData->Buffer,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
  BOOL bRet=FALSE;

  if(hFile!=INVALID_HANDLE_VALUE)
  {
    cout<<"Transmit File "<<pIoData->Buffer<<" to client"<<endl;
    pIoData->IoType=IoTransFile;
    memset(&pIoData->oa,0,sizeof(OVERLAPPED));
    *reinterpret_cast<HANDLE*>(pIoData->Buffer)=hFile;
    TransmitFile(pSocketData->hSocket,hFile,GetFileSize(hFile,NULL),PRE_SEND_SIZE,reinterpret_cast<LPOVERLAPPED>(pIoData),NULL,TF_USE_SYSTEM_THREAD);
    bRet=WSAGetLastError()==WSA_IO_PENDING;
  }
  else
    cout<<"Transmit File "<<"Error:"<<GetLastError()<<endl;

  return bRet;
}

DWORD WINAPI ThreadProc(LPVOID IocpHandle)
{
  DWORD dwRecv=0;
  DWORD dwFlags=0;
 
  HANDLE hIocp=reinterpret_cast<HANDLE>(IocpHandle);
  DWORD dwTransCount=0;
  PPRE_IO_DATA pPreIoData=NULL;
  PPRE_SOCKET_DATA pPreHandleData=NULL;

  while(TRUE)
  {
    if(GetQueuedCompletionStatus(hIocp,&dwTransCount,
        reinterpret_cast<LPDWORD>(&pPreHandleData),
        reinterpret_cast<LPOVERLAPPED*>(&pPreIoData),INFINITE))
    {
        if(0==dwTransCount&&IoQuit!=pPreIoData->IoType)
        {
          cout<<"Client:"
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<" is closed"<<endl;

          closesocket(pPreHandleData->hSocket);

          EnterCriticalSection(&csProtection);
            IoDataVector::iterator itrIoDelete=find(gIoDataVec.begin(),gIoDataVec.end(),pPreIoData);
            gIoDataVec.erase(itrIoDelete);
            SocketDataVector::iterator itrSockDelete=find(gSockDataVec.begin(),gSockDataVec.end(),pPreHandleData);
            gSockDataVec.erase(itrSockDelete);
          LeaveCriticalSection(&csProtection);

          delete *itrIoDelete;
          delete *itrSockDelete;
         
          continue;
        }
       
        switch(pPreIoData->IoType){
        case IoTransFile:
          cout<<"Client:"
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<" Transmit finished"<<endl;
          CloseHandle(*reinterpret_cast<HANDLE*>(pPreIoData->Buffer));
          goto LRERECV;
         
        case IoSend:
          cout<<"Client:"
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<" Send finished"<<endl;

LRERECV:
          pPreIoData->IoType=IoRecv;
          pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;
          memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));

          WSARecv(pPreHandleData->hSocket,&pPreIoData->DataBuf,1,
            &dwRecv,&dwFlags,
            reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NULL);

          break;

        case IoRecv:
          cout<<"Client:"
            <<inet_ntoa(pPreHandleData->ClientAddr.sin_addr)
            <<":"<<ntohs(pPreHandleData->ClientAddr.sin_port)
            <<" recv finished"<<endl;
          pPreIoData->IoType=IoSend;
         
          if(!TransFile(pPreIoData,pPreHandleData,dwTransCount))
          {
            memset(&pPreIoData->oa,0,sizeof(OVERLAPPED));
            strcpy(pPreIoData->DataBuf.buf,"File transmit error!\r\n");
            pPreIoData->DataBuf.len=strlen(pPreIoData->DataBuf.buf);
           
            WSASend(pPreHandleData->hSocket,&pPreIoData->DataBuf,1,
                &dwRecv,dwFlags,
                reinterpret_cast<LPWSAOVERLAPPED>(pPreIoData),NULL);
          }
          break;
         
        case IoQuit:
          goto LQUIT;
         
        default:
          ;
        }
    }  
  }
 
LQUIT:
  return 0;
}

HANDLE hIocp=NULL;
SOCKET hListen=NULL;

BOOL WINAPI ShutdownHandler(DWORD dwCtrlType)
{
  PRE_SOCKET_DATA PreSockData={0};
  PRE_IO_DATA PreIoData={0};

  PreIoData.IoType=IoQuit;

  if(hIocp)
  {
    PostQueuedCompletionStatus(hIocp,1,
        reinterpret_cast<ULONG_PTR>(&PreSockData),
        reinterpret_cast<LPOVERLAPPED>(&PreIoData));

    cout<<"Shutdown at "<<TimeNow()<<endl<<"wait for a moment please"<<endl;
   
    //讓出CPU時(shí)間,讓線(xiàn)程退出
    for(int t=0;t<80;t+=1)
    {
        Sleep(PRE_DOT_TIMER);
        cout<<".";
    }
   
    CloseHandle(hIocp);
  }
 
  int i=0;

  for(;i<gSockDataVec.size();i++)
  {
    PPRE_SOCKET_DATA pSockData=gSockDataVec[i];
    closesocket(pSockData->hSocket);
    delete pSockData;
  }

  for(i=0;i<gIoDataVec.size();i++)
  {
    PPRE_IO_DATA pIoData=gIoDataVec[i];
    delete pIoData;
  }

  DeleteCriticalSection(&csProtection);
  if(hListen)
    closesocket(hListen);

  WSACleanup();
  exit(0);
  return TRUE;
}

LONG WINAPI MyExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  ShutdownHandler(0);
  return EXCEPTION_EXECUTE_HANDLER;
}

u_short DefPort=8182;

int main(int argc,char **argv)
{
  if(argc==2)
    DefPort=atoi(argv[1]);

  InitializeCriticalSection(&csProtection);
  SetUnhandledExceptionFilter(MyExceptionFilter);
  SetConsoleCtrlHandler(ShutdownHandler,TRUE);

  hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

  WSADATA data={0};
  WSAStartup(0x0202,&data);

  hListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(INVALID_SOCKET==hListen)
  {
    ShutdownHandler(0);
  }
 
  SOCKADDR_IN addr={0};
  addr.sin_family=AF_INET;
  addr.sin_port=htons(DefPort);
 
  if(bind(hListen,reinterpret_cast<PSOCKADDR>(&addr),
    sizeof(addr))==SOCKET_ERROR)
  {
    ShutdownHandler(0);
  }
 
  if(listen(hListen,256)==SOCKET_ERROR)
    ShutdownHandler(0);

  SYSTEM_INFO si={0};
  GetSystemInfo(&si);
  si.dwNumberOfProcessors<<=1;

  for(int i=0;i<si.dwNumberOfProcessors;i++)
  {
   
    QueueUserWorkItem(ThreadProc,hIocp,WT_EXECUTELONGFUNCTION);
  }
 
  cout<<"Startup at "<<TimeNow()<<endl
    <<"work on port "<<DefPort<<endl
    <<"press CTRL+C to shutdown"<<endl<<endl<<endl;

  while(TRUE)
  {
    int namelen=sizeof(addr);
    memset(&addr,0,sizeof(addr));
    SOCKET hAccept=accept(hListen,reinterpret_cast<PSOCKADDR>(&addr),&namelen);

    if(hAccept!=INVALID_SOCKET)
    {
        cout<<"accept a client:"<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<endl;

        PPRE_SOCKET_DATA pPreHandleData=new PRE_SOCKET_DATA;
        pPreHandleData->hSocket=hAccept;
        memcpy(&pPreHandleData->ClientAddr,&addr,sizeof(addr));
       
        CreateIoCompletionPort(reinterpret_cast<HANDLE>(hAccept),
          hIocp,reinterpret_cast<DWORD>(pPreHandleData),0);
       
        PPRE_IO_DATA pPreIoData=new(nothrow) PRE_IO_DATA;

        if(pPreIoData)
        {
          EnterCriticalSection(&csProtection);
            gSockDataVec.push_back(pPreHandleData);
            gIoDataVec.push_back(pPreIoData);
          LeaveCriticalSection(&csProtection);

          memset(pPreIoData,0,sizeof(PRE_IO_DATA));
          pPreIoData->IoType=IoRecv;
          pPreIoData->DataBuf.len=MAX_BUFFER_SIZE;
          pPreIoData->DataBuf.buf=pPreIoData->Buffer;
          DWORD dwRecv=0;
          DWORD dwFlags=0;
          WSARecv(hAccept,&pPreIoData->DataBuf,1,
            &dwRecv,&dwFlags,
            reinterpret_cast<WSAOVERLAPPED*>(pPreIoData),NULL);
        }
        else
        {
          delete pPreHandleData;
          closesocket(hAccept);
        }
    }
  }
 
  return 0;
}

參考資料:
《MSDN 2001》
《Windows 網(wǎng)絡(luò)編程》
《Windows 核心編程》
《TCP/IP詳解》