网络程序设计IOCP与可伸缩网络程序.ppt
18:36:20,1,第四章,IOCP与可伸缩网络程序,18:36:20,2,回顾前4种IO模型,摘自互联网场景:在风景秀丽的海边别墅,住着一位老人,他有一个在外地工作的女儿,不能经常回来,老人和她通过信件联系,邮递员把信件投递到他们的信箱里。老人:应用程序信箱:套接字,18:36:20,3,select模型:老人非常想看到女儿的信,每隔10分钟就下楼检查信箱,看是否有女儿的信。select模型周而复始地去检查套接字.如果有数据或者可以发送数据.接收/发送.while(1)int ret=select();.,18:36:20,4,WSAAsyncSelect模型 微软公司改进信箱,采用一种新式信箱,这种信箱非常先进,一旦信箱里有新的信件,微软就会发短信通知老人:喂,大爷,你有新的信件了!于是,老人下楼取信处理信件 WSAAsyncSelect()函数安装新式信箱,在套接字上注册网络事件并关联到窗口。使用WSAAsyncSelect模型时,Windows会把网络事件以消息的形式通知应用程序。,18:36:20,5,WSAEventSelect模型 微软的新式信箱非常畅销,购买微软信箱的人以百万计数以至于盖茨每天24小时发短信通知客户有信件到达,于是微软又改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出“新信件到达”声,提醒老人下楼去收信。WSAEventSelect()函数安装这个新式信箱,将套接字和事件对象相关联。WSAEventSelect模型在套接字上注册网络事件,网络事件发生时,应用程序通过事件对象状态的变化感知网络事件的发生。,18:36:20,6,Overlapped I/O 事件通知模型 微软调查发现:老人不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进信箱,采用了更为先进的技术,只要用户告诉老人家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老人很高兴,因为他不必再亲自下楼收发信件了!提供门牌号码相当于提供缓冲区并投递IO操作。Overlapped模型是让应用程序使用重叠数据结构,一次投递一个或多个Winsock I/O请求。这些提交的请求完成后,应用程序会收到完成通知。从socket上接收数据,只需要告诉系统要接收数据,而数据的接收由系统完成,应用程序需要做的只是为系统提供一个缓冲区,18:36:20,7,IOCP模型 微软信箱似乎很完美,老人很满意。但是在一些大公司情况却完全不同,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!问题在于:在一个套接字上投递大量的IO操作,处理IO的结果需要等待的时间变长如何解决?增加信箱,18:36:20,8,方案一,while(TRUE)NewConnection=accept(ListeningSocket,(SOCKADDR*),18:36:20,9,Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别是处理很多同时的客户请求,意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的没有被挂起和等待发生什么事,Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文Context,线程就没有得到很多CPU时间来做它们的工作。这种并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程,创建线程比起创建进程开销要小,但也远不是没有开销的。-摘自nonocast的理解I/O Completion Port,18:36:20,10,方案二,不妨设想一下:如果事先开好N个线程,让它们在那hold阻塞,然后可以将所有用户的请求都投递到一个消息队列中去,然后N个线程逐一从消息队列中去取出消息并加以处理,就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。理论上很不错,你想我等泛泛之辈都能想出来的问题,Microsoft又怎会没有考虑到呢?-摘自nonocast的理解I/O Completion Port也就是采用IO完成端口,18:36:20,11,关于IOCP的几个评述,I/O完成端口可能是Win32提供的最复杂的内核对象。Advanced Windows 3rd Jeffrey Richter IOCP是实现高容量网络服务器的最佳方法。Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports Microsoft Corporation 完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。Windows网络编程 2nd Anthony Jones&Jim Ohlund I/O completion ports特别重要,是唯一适用于高负载服务器的技术。Completion ports利用一些线程,帮助平衡由I/O请求所引起的负载。Win32多线程程序设计 Jim Beveridge&Robert Wiener,18:36:20,12,IOCP,Windows NT3.5中首次引入了一个称为I/O完成端口的内核对象;微软在Winsock2中引入了IOCP这一概念。IOCP全称I/O Completion Port,中文译为I/O完成端口。IOCP就是一个消息队列;IOCP是应用程序和操作系统沟通的一个接口。套接字与完成端口关联后就可投递Winsock IO操作。当IO操作完成后,完成端口会收到IO系统的通知包并将这些通知加入到一个队列中,然后应用程序可以查询完成端口取得通知包。如何使用完成端口:创建与查询,18:36:20,13,完成端口I/O模型,I/O完成端口是应用程序使用线程池处理异步I/O请求的一种机制。处理多个并发异步I/O请求时,使用I/O完成端口比在I/O请求时创建线程更快更有效。完成端口可接受多种对象句柄。从本质上说,完成端口模型要求创建一个Win32完成端口对象,通过指定数量的线程,对重叠I/O请求完成情况进行管理,以便为已经完成的重叠I/O请求提供服务。,18:36:20,14,创建完成端口HANDLE CreateIoCompletionPort(HANDLE FileHandle,/handle to file HANDLE ExistingCompletionPort,/handle to I/O completion port DWORD CompletionKey,/completion key DWORD NumberOfConcurrentThreads/number of threads to execute concurrently);函数功能:用于创建一个完成端口对象。将一个句柄同完成端口关联到一起。,FileHandle:一个已打开的文件句柄,该句柄创建时必须使用FILE_FLAG_OVERLAPPED 标志。该参数若为INVALID_HANDLE_VALUE,则创建一个没有任何文件相关联的完成端口,此时ExistingCompletionPort必须为NULL,CompletionKey被忽略。ExistingCompletionPort:指向完成端口的句柄。该参数若指定了一个已存在的完成端口,则将该完成端口与由FileHandle指定的文件相关联,函数返回值就为该完成端口;若没有指定,为NULL,则创建一个完成端口并实施关联,函数返回新创建的完成端口。CompletionKey:完成键,指定句柄唯一数据,可存放用户定义的任意类型的数据,通常是一个指针。NumberOfConcurrentThreads:系统允许的并发最大线程数,若为0,则线程数和CPU数量相同。,18:36:20,15,18:36:20,16,功能1、创建完成端口对象 HANDLE hCompletion=:CreateCompletionPort(INVALID_HANDLE_VALUE,0,0,0);在创建完成端口时,只需填写NumberOfConcurrentThreads参数,告诉系统完成端口上同时允许运行的线程最大数。在默认情况下,所开线程数和CPU数量相同,经验公式为:线程数=CPU数*2+2 作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对该完成端口进行引用。功能2、将一个句柄同完成端口关联起来 CreateIoCompletionPort(s,hCompletion,(DWORD)pPerHandle,0);,18:36:20,17,I/O服务线程与完成端口在关联套接字之前,首先必须创建一个或多个工作线程,以便在I/O请求投递给完成端口对象后,为完成端口提供服务。工作线程数目原则和创建完成端口时指定的NumberOfConcurrentThreads相同,根据程序总体设计可以适当增加。,18:36:20,18,完成端口和重叠I/O将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送与接收请求。I/O操作完成时,I/O系统向完成端口对象发送一个完成通知包。I/O完成端口以先进先出方式为通知包排队。应用程序使用GetQueuedCompletionStatus函数获得通知包,18:36:20,19,BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,/handle to completion port LPDWORD lpNumberOfBytes,/bytes transferred LPDWORD lpCompletionKey,/file completion key LPOVERLAPPED*lpOverlapped,/buffer DWORD dwMilliseconds/optional timeout value);GetQueuedCompletionStatus()使调用线程挂起,直到指定的端口的I/O完成队列中出现了一项完成通知包或直到超时。,CompletionPort:指定线程要在查询的完成端口。很多服务应用程序只使用一个I/O完成端口,所有的I/O请求完成以后的通知都将发给该端口。lpNumberOfBytes:接收实际传输的字节数。lpCompletionKey:关联套接字到完成端口时的句柄唯一数据。lpOverlapped:投递IO操作时使用的重叠结构。dwMilliseconds:等待时间。同I/O完成端口相关联的3个数据结构是使线程得到完成I/O项中的信息:传输的字节数,句柄唯一数据和OVERLAPPED结构的地址。这些信息通过传递给GetQueuedCompletionSatatus的lpdwNumberOfBytesTransferred,lpdwCompletionKey和lpOverlapped参数返回给线程的。,18:36:20,20,函数返回值:非0:表示取得IO成功的通知包lpdwNumberOfBytesTransferred,lpdwCompletionKey和lpOverlapped三个参数指定的存储单元值被更新0:当lpOverlapped=NULL且函数没有取得完成通知包,此时lpdwNumberOfBytesTransferred,lpdwCompletionKey指定存储单元值不被更新;当lpOverlapped!=NULL且取得IO失败的通知包,此时lpdwNumberOfBytesTransferred,lpdwCompletionKey和lpOverlapped三个参数指定的存储单元值被更新,18:36:20,21,18:36:20,22,句柄唯一数据和I/O唯一数据一个工作线程从GetQueuedCompletionStatus调用接收到I/O完成通知后,在lpCompletionKey和lpOverlapped参数中,包含一些必要的套接字信息和I/O信息,利用这些信息可通过完成端口,继续在一个套接字上的I/O处理。通过这些参数,可获得两方面重要的与套接字相关的数据:句柄唯一数据以及I/O唯一数据。,18:36:20,23,lpCompletionKey参数包含了“句柄唯一数据”,通常情况下,应用程序会将与I/O请求有关的套接字句柄保存在这里。lpOverlapped参数则包含了一个OVERLAPPED结构,在它后面跟随“I/O唯一数据”。I/O唯一数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。如果一个函数要求用到一个OVERLAPPED结构,就必须将这样的一个结构传递进去,以满足它的要求。解决方法是定义一个结构,将OVERLAPPED结构作为新结构的第一个元素使用。,18:36:20,24,typedef struct _PER_IO_DATA/per-I/O数据OVERLAPPED ol;/重叠结构char bufBUFFER_SIZE;/数据缓冲区int nOperationType;/操作类型#define OP_READ 1#define OP_WRITE 2#define OP_ACCEPT 3 PER_IO_DATA,*PPER_IO_DATA;,typedef struct _PER_HANDLE_DATA/per-handle数据SOCKET s;/对应的套接字句柄sockaddr_in addr;/客户方地址 PER_HANDLE_DATA,*PPER_HANDLE_DATA;,关闭IOCP,要避免在进行重叠I/O操作的同时,强行释放一个OVERLAPPED结构,最好的办法是针对每个套接字句柄,调用closesocket函数,任何尚未进行的重叠I/O操作都会完成,只不过是失败方式完成。一旦所有套接字句柄都已关闭,便需在完成端口上 终止所有工作线程的运行,则需要使用PostQueuedCompletionStatus()函数,向每个工作线程都发送一个指示每个线程都“立即结束并退出”完成通知包。,18:36:20,25,PostQueuedCompletionStatus函数原型:BOOL PostQueuedCompletionStatus(HANDLE CompletionPort,DWORD dwNumberOfBytesTransferred,ULONG_PTR dwCompletionKey,LPOVERLAPPED lpOverlapped);后面3个参数指定的值会在调用GetQueuedCompletionStatus()函数时获得。,18:36:20,26,18:36:20,27,完成端口大概的处理流程,1:创建一个完成端口。2:创建一个线程A。3:A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果。4:主线程循环里调用accept等待客户端连接。5:主线程里accept返回新连接后,把这个新的套接字句柄用CreateIoCompletionPort()关联到完成端口,然后发出一个异步的WSASend或WSARecv调用,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收操作由WINDOWS系统去做。,18:36:20,28,6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。7:WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。8:A线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。9:在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。,18:36:20,29,18:36:20,30,举例:主线程编程步骤,1、创建一个完成端口。2、判断系统处理器数目。3、根据步骤2 得到的处理器数量,创建工作线程,在完成端口上为已完成的I/O请求提供服务。4、准备好监听套接字,在端口*上监听进入的连接请求。5、使用accep t函数,接受进入的连接请求。6、创建一个数据结构,用于容纳“句柄唯一数据”,同时在结构中存入接受的套接字句柄。,18:36:20,31,7、调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey)参数,将句柄唯一数据结构传递给CreateIoCompletionPort。8、开始在已接受的连接上投递I/O操作。9、重复步骤5)8),直至服务器中止。,18:36:20,32,InitWinsock()/加载Winsock库/步骤一,创建一个完成端口 CompletionPort=CreateIoCompletionPort(INVALI_HANDLE_VALUE,0,0,0);/步骤二判断系统处理器数目 GetSystemInfo(,18:36:20,33,/步骤四:创建监听套接字Listen=WSASocket(AF_INET,S0CK_STREAM,0,NULL,WSA_FLAG_OVERLAPPED);InternetAddr.sin_famlly=AF_INET;InternetAddr.sin_addr.s_addr=inet_addr(“127.0.0.1”);InternetAddr.sln_port=htons(6666);bind(Listen,(PSOCKADDR),18:36:20,34,/步骤六 创建一个数据结构,用于容纳“句柄唯一数据”,同时在结构中存入接受的套接字句柄。PerHandleData=(LPPER_HANDLE_DATA)GlobalAlloc(GPTR,sizeof(PER_HANDLE_DATA);printf(Socket number%d connectedn,Accept);PerHandleData-Socket=Accept;/步骤七,接入套接字和完成端口关联 CreateIoCompletionPort(HANDLE)Accept,CompletionPort,(DWORD)PerHandleData,0);/步骤八 开始进行I/O操作,用重叠I/O投递WSASend()和/WSARecv()WSARecv();,18:36:20,35,工作线程编程步骤,见代码,18:36:20,36,DWORD WINAPI ServerThread(LPVOID lpParam)/得到完成端口对象句柄HANDLE hCompletion=(HANDLE)lpParam;DWORD dwTrans;PPER_HANDLE_DATA pPerHandle;/此处略去空间分配PPER_IO_DATA pPerIO;/此处略去空间分配while(TRUE)/在关联到此完成端口的所有套接字上等待I/O完成BOOL bOK=GetQueuedCompletionStatus(hCompletion,18:36:20,37,/套接字被对方关闭if(dwTrans=0,18:36:20,38,switch(pPerIO-nOperationTypecase OP_READ:/完成一个接收请求pPerIO-bufdwTrans=0;printf(pPerIO-buf);/继续投递接收I/O请求WSABUF buf;buf.buf=pPerIO-buf;buf.len=BUFFER_SIZE;pPerIO-nOperationType=OP_READ;DWORD nFlags=0;WSARecv(pPerHandle-s,18:36:20,39,Microsoft扩展函数,GetAcceptExSockaddrs()GetAcceptExSockaddrs函数解析从AcceptEx函数取得的数据,将本地和远程地址传递到sockaddr结构。函数定义:void GetAcceptExSockaddrs(PVOID lpOutputBuffer,/接收第一块数据的缓冲区DWORD dwReceriveAddressLength,/lpOutputBuffer的大小DWORD dwLocalAddressLength,/为本地地址预留的空间大小DWORD dwRemoteAddressLength,/为远程地址预留的空间大小LPSOCKADDR*LocalSockaddr,/返回本地地址LPINT LocalSockaddrLength,/本地地址长度LPSOCKADDR*RemoteSockaddr,/返回远程地址LPINR RemoteSockaddLength/远程地址长度);,18:36:20,40,TransmitFile()TransmitFile是微软专有的Winsock扩展,它允许从一个文件中传输高性能数据。函数的定义如下:BOOL TransmitFile(SOCKET hSocket,/在该套接字上传输文件数据 HANDLE hFile,/已打开的文件句柄 DWORD nNumberOfBytesToWrite,/要传输的字节数,0表示传输整个文件 DWORD nNumberOfBytesPerSend,/每次发送的数据块大小 LPOVERLAPPED lpOverlapped,/指定IO方式 LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,/指定文件发送前后要发送的数据 DWORD dwFlags/标志);,18:36:20,41,TransmitFile标志,18:36:20,42,TransmitPackets()TransmitPackets函数与TransmitFile类似,均用来发送数据,但是前者既可以发送文件又可以发送内存缓冲区中的数据。函数定义BOOL TransmitPackets(SOCKET hSocket,/套接字LPTRANSMIT_PACKETS_ELEMENT lpPacketArray,/元素数组DWORD nElementCount,/数组大小DWORD nSendSize,/每次发送数据大小LPOVERLAPPED lpOverlapped,/可选的重叠结构DWORD dwFlags/标志,以TP开头);,struct _TRANSMIT_PACKETS_ELEMENT ULONG dwElFlags;#define TP_ELEMENT_MEMORY 1#define TP_ELEMENT_FILE 2#define TP_ELEMENT_EOP 4 ULONG cLength;union struct LARGE_INTEGER nFileOffset;HANDLE hFile;PVOID pBuffer;TRANSMIT_PACKETS_ELEMENT;,18:36:20,43,18:36:20,44,ConnectEx函数为指定的套接字建立连接,连接建立之后也可以发送数据。此函数仅支持面向连接的套接字。函数定义BOOL PASCAL ConnectEx(SOCKET s,/未连接但已绑定的套接字const struct sockaddr*name,/远程地址int namelen,/远程地址长度PVOID lpSendBuffer,/建立连接后要发送的数据,可选DWORD dwSendDataLength,/缓冲区长度LPDWORD lpdwBytesSent,/返回发送字节数LPOVERLAPPED lpOverlapped/重叠结构,must);,18:36:20,45,DisconnectEx函数用于关闭套接字上的连接,允许重用套接字句柄。函数定义BOOL DisconnectEx(SOCKET hSocket,LPOVERLAPPED lpOverlapped,DWORD dwFlags,/只有一个标志TF_REUSE_SOCKETWORD reserved);,课后作业,完成端口中per-handle数据和per-IO数据各有什么作用?调试4.4节的实例程序,18:36:20,46,18:36:20,47,可伸缩服务器设计注意事项,1、内存资源管理应用程序通过Winsock来和传输协议驱动程序交互,AFD.SYS负责为应用程序进行缓冲区管理。也就是说,当应用程序调用send()或WSASend()函数来发送数据时,AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值),然后send()或WSASend()函数立即返回。也可以这么说,AFD.SYS在后台负责把数据发送出去。,18:36:20,48,如果应用程序通过设定SO_SNDBUF为0把内部缓冲区关闭,然后发出一个send()调用。此时,系统内核会把应用程序的缓冲区锁定,直到接收方确认收到了整个缓冲区后send()调用才返回。锁定内存的数量有限制,达到限制时IO操作失败。结论:没有必要设定SO_SNDBUF0。关闭缓冲区对于多数应用程序而言并不是什么好主意。另一个问题:内存页面大小与缓冲区大小 缓冲区大小N内存页面大小,18:36:20,49,2、接受连接的方法 高响应能力的服务器必须发出足够的AcceptEx()调用,一旦出现客户端连接请求就立刻响应。至于发出多少个AcceptEx()才够,就取决于服务器程序所期待的通信类型。比如,对进入连接率高的情况(因为连接持续时间较短,或者出现通信高峰),那么所需要投递的AcceptEx()当然要比那些偶尔才有客户端进入连接的情况要多。明智的做法是:由应用程序来分析通信状况,并调整投递的AcceptEx()数量,而不是固定在某个数量上。,18:36:20,50,在创建监听套接字时创建一个事件,通过WSAEventSelect()注册FD_ACCEPT事件通知,把监听套接字和这个事件关联起来。一旦系统收到一个连接请求,如果系统中没有AcceptEx()正在等待接受连接,那么上面的事件将收到一个信号。谁来投递AcceptEx()?工作线程OR其他线程?工作线程的职责:读完成端口中完成通知包,要求越快越好。因此用单独的线程投递AcceptEx()操作。,18:36:20,51,恶意客户连接问题 恶意客户指的是建立连接后既不发送数据也不关闭连接的客户端。解决方法:对已建立连接的套接字形成链表。以SO_CONNCET_TIME为参数定时调用getscoketopt()检查链表中的套接字连接建立时间,超时关闭。,18:36:20,52,包排序问题 线程调度引起数据收发顺序混乱 对某个套接字的缓冲区对象编号处理,18:36:20,53,可伸缩服务器系统设计实例,句柄唯一数据和IO唯一数据CIOCPServer类总体结构内存池方案开启和停止服务监听线程IO处理线程,18:36:20,54,句柄唯一数据和IO唯一数据,客户上下文对象(句柄唯一数据):主要作用在于关联完成端口时传递套接字信息,在查询完成端口时该套接字信息会通过lpCompletionKey参数返回。缓冲区对象(IO唯一数据):主要作用在于投递重叠IO时传递IO信息,包括缓冲区,IO类型、OVERLAPPED结构等等。在查询完成端口时通过lpOverlapped参数返回投递IO时使用的重叠结构地址,通过该该结构获得投递IO时使用IO唯一数据。通过二者可以确定已完成IO的套接字以及该套接字上的IO信息,进而处理数据。,18:36:20,55,缓冲区对象,应该记录哪些信息1、缓冲区信息,must?2、重叠结构,must?3、IO操作类型?4、IO编号?5、如果是接受连接?6、如何管理缓冲区对象?.,18:36:20,56,缓冲区对象,struct CIOCPBufferWSAOVERLAPPED ol;SOCKET sClient;/AcceptEx接收的客户方套接字char*buff;/I/O操作使用的缓冲区int nLen;/buff缓冲区(使用的)大小ULONG nSequenceNumber;/此I/O的序列号int nOperation;/操作类型#define OP_ACCEPT 1#define OP_WRITE 2#define OP_READ 3CIOCPBuffer*pNext;,18:36:20,57,客户上下文对象,应该记录哪些信息套接字句柄,must?地址信息?套接字状态?IO操作个数?对要接收的数据编号?没有按序完成接收IO是否要记录?所有的客户上下文对象如何管理?,18:36:21,58,客户上下文对象,/per-Handle数据,包含套接字的信息struct CIOCPContext SOCKET s;/套接字句柄 SOCKADDR_IN addrLocal;/连接的本地地址 SOCKADDR_IN addrRemote;/连接的远程地址 BOOL bClosing;/套接字是否关闭 int nOutstandingRecv;/此套接字上抛出的重叠操作数量 int nOutstandingSend;ULONG nReadSequence;/安排给接收的下一个序列号ULONG nCurrentReadSequence;/当前要读的序列号CIOCPBuffer*pOutOfOrderReads;/记录没按顺序完成的读I/OCRITICAL_SECTION Lock;/保护这个结构CIOCPContext*pNext;,18:36:21,59,CIOCPServer类总体结构,#define BUFFER_SIZE 1024*4/I/O请求的缓冲区大小#define MAX_THREAD 2/I/O服务线程的数量class CIOCPServer public:CIOCPServer();CIOCPServer();/开始服务BOOL Start(int nPort=4567,int nMaxConnections=2000,int nMaxFreeBuffers=200,int nMaxFreeContexts=100,int nInitialReads=4);/停止服务void Shutdown();/关闭一个连接和关闭所有连接void CloseAConnection(CIOCPContext*pContext);void CloseAllConnections();/取得当前的连接数量ULONG GetCurrentConnection()return m_nCurrentConnection;/向指定客户发送文本BOOL SendText(CIOCPContext*pContext,char*pszText,int nLen);,18:36:21,60,protected:/申请和释放缓冲区对象CIOCPBuffer*AllocateBuffer(int nLen);void ReleaseBuffer(CIOCPBuffer*pBuffer);/申请和释放套接字上下文CIOCPContext*AllocateContext(SOCKET s);void ReleaseContext(CIOCPContext*pContext);/释放空闲缓冲区对象列表和空闲上下文对象列表void FreeBuffers();void FreeContexts();/向连接列表中添加一个连接BOOL AddAConnection(CIOCPContext*pContext);/插入和移除未决的接受请求BOOL InsertPendingAccept(CIOCPBuffer*pBuffer);BOOL RemovePendingAccept(CIOCPBuffer*pBuffer);/取得下一个要读取的CIOCPBuffer*GetNextReadBuffer(CIOCPContext*pContext,CIOCPBuffer*pBuffer);,18:36:21,61,/投递接受I/O、发送I/O、接收I/OBOOL PostAccept(CIOCPBuffer*pBuffer);BOOL PostSend(CIOCPContext*pContext,CIOCPBuffer*pBuffer);BOOL PostRecv(CIOCPContext*pContext,CIOCPBuffer*pBuffer);void HandleIO(DWORD dwKey,CIOCPBuffer*pBuffer,DWORD dwTrans,int nError);/事件通知函数/建立了一个新的连接virtual void OnConnectionEstablished(CIOCPContext*pContext,CIOCPBuffer*pBuffer);/一个连接关闭virtual void OnConnectionClosing(CIOCPContext*pContext,CIOCPBuffer*pBuffer);/在一个连接上发生了错误virtual void OnConnectionError(CIOCPContext*pContext,CIOCPBuffer*pBuffer,int nError);/一个连接上的读操作完成virtual void OnReadCompleted(CIOCPContext*pContext,CIOCPBuffer*pBuffer);/一个连接上的写操作完成virtual void OnWriteCompleted(CIOCPContext*pContext,CIOCPBuffer*pBuffer);,18:36:21,62,protected:/记录空闲结构信息CIOCPBuffer*m_pFreeBufferList;CIOCPContext*m_pFreeContextList;int m_nFreeBufferCount;int m_nFreeContextCount;CRITICAL_SECTION m_FreeBufferListLock;CRITICAL_SECTION m_FreeContextListLock;/记录抛出的Accept请求CIOCPBuffer*m_pPendingAccepts;/抛出请求列表。long m_nPendingAcceptCount;CRITICAL_SECTION m_PendingAcceptsLock;/记录连接列表CIOCPContext*m_pConnectionList;int m_nCurrentConnection;CRITICAL_SECTION m_ConnectionListLock;/用于投递Accept请求HANDLE m_hAcceptEvent;HANDLE m_hRepostEvent;LONG m_nRepostCount;,18:36:21,63,int m_nPort;/服务器监听的端口int m_nInitialAccepts;int m_nInitialReads;int m_nMaxAccepts;int m_nMaxSends;int m_nMaxFreeBuffers;int m_nMaxFreeContexts;int m_nMaxConnections;HANDLE m_hListenThread;/监听线程HANDLE m_hCompletion;/完成端口句柄SOCKET m_sListen;/监听套接字句柄LPFN_ACCEPTEX m_lpfnAcceptEx;/AcceptEx函数地址LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockaddrs;/GetAcceptExSockaddrs函数地址BOOL m_bShutDown;/用于通知监听线程退出BOOL m_bServerStarted;/记录服务是否启动,18:36:2