socket编程之完成端口(附一个简单的IOCP例子)

“完成端口”模型是迄今为止最为复杂的—种I/O模型。然而。假若—个应用程序同时需要管理为数众多的套接字,那么采用这种模型。往往可以达到最佳的系统性能,然而不幸的是,该模型只适用于以下操作系统(微软的)Windows NTWindows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候、而且希望随着系统内安装的CPU数量的增多、应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NTwindows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择.

    从本质上说,完成端口模型要求我们创建一个Win32完成端口对象,通过指定数量的线程对重叠I/O请求进行管理。以便为已经完成的重叠I/O请求提供服务。要注意的是。所谓“完成端口”,实际是Win32Windows NT以及windows 2000采用的一种I/O构造机制,除套接字句柄之外,实际上还可接受其他东西。然而,本节只打算讲述如何使用套接字句柄,来发挥完成端口模型的巨大威力。使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄。管理多个I/O请求。要做到这—点,需要调用CreateIoCompletionPort函数。该函数定义如下:  

 

HANDLE CreateIoCompletionPort(

 

                              HANDLE FileHandle,

 

                              HANDLE ExistingCompletionPort,

 

                              DWORD CompletionKey,

 

                              DWORD  NumberOfConcurrentThreads

 

                              );

 

在我们深入探讨其中的各个参数之前,首先要注意意该函数实际用于两个明显有别的目的:

    ■用于创建—个完成端口对象。

 

    ■将一个句柄同完成端口关联到一起。

 

    最开始创建—个完成端口的时候,唯一感兴趣的参数便是NumberOfConcurrentThreads 并发线程的数量);前面三个参数都会被忽略。NumberOfConcurrentThreads 参数的特殊之处在于.它定义了在一个完成端口上,同时允许执行的线程数量。理想情况下我们希望每个处理器各自负责—个线程的运行,为完成端口提供服务,避免过于频繁的线程“场景”切换。若将该参数设为0,说明系统内安装了多少个处理器,便允许同时运行多少个线程!可用下述代码创建一个I/O完成端口:

CompetionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0)

    该语加的作用是返问一个句柄.在为完成端口分配了—个套接字句柄后,用来对那个端

口进行标定(引用)

  

 1.工作者线程与完成端口

 

    成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但在关联套接字之前、首先必须创建—个或多个“工作者线程”,以便在I/O请求投递给完成端口对象后。为完成端口提供服务。在这个时候,大家或许会觉得奇怪、到底应创建多少个线程。以便为完成端口提供服务呢?这实际正是完成端口模型显得颇为“复杂”的—个方面, 因为服务I/O请求所需的数量取决于应用程序的总体设计情况。在此要记住的—个重点在于,在我们调用CreateIoComletionPort时指定的并发线程数量,与打算创建的工作者线程数量相比,它们代表的并非同—件事情。早些时候,我们曾建议大家用CreateIoCompletionPort函数为每个处理器都指定一个线程(处理器的数量有多少,便指定多少线程)以避免由于频繁的线程“场景”交换活动,从而影响系统的整体性能。CreateIoCompletionPort函数的NumberofConcurrentThreads参数明确指示系统:  在一个完成端口上,一次只允许n个工作者线程运行。假如在完成端门上创建的工作者线程数量超出n个.那么在同一时刻,最多只允许n个线程运行。但实际上,在—段较短的时间内,系统有可能超过这个值。但很快便会把它减少至事先在CreateIoCompletionPort函数中设定的值。那么,为何实际创建的工作者线程数最有时要比CreateIoCompletionPort函数设定的多—些呢?这样做有必要吗?如先前所述。这主要取决于应用程序的总体设计情况,假设我们的工作者线程调用了一个函数,比如Sleep()或者WaitForSingleobject(),但却进入了暂停(锁定或挂起)状态、那么允许另—个线程代替它的位置。换行之,我们希望随时都能执行尽可能多的线程;当然,最大的线程数量是事先在CreateIoCompletonPort调用里设定好的。这样—来。假如事先预料到自己的线程有可能暂时处于停顿状态,那么最好能够创建比CreateIoCompletionPortNumberofConcurrentThreads参数的值多的线程.以便到时候充分发挥系统的潜力。—旦在完成端口上拥有足够多的工作者线程来为I/O请求提供服务,便可着手将套接字句柄同完成端口关联到一起。这要求我们在—个现有的完成端口上调用CreateIoCompletionPort函数,同时为前三个参数: FileHandle,ExistingCompletionPortCompletionKey——提供套接字的信息。其中,FileHandle参数指定—个要同完成端口关联在—一起的套接字句柄。

    ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey(完成键)参数则指定要与某个特定套接字句柄关联在—起的“单句柄数据”,在这个参数中,应用程序可保存与—个套接字对应的任意类型的信息。之所以把它叫作“单句柄数据”,是由于它只对应着与那个套接字句柄关联在—起的数据。可将其作为指向一个数据结构的指针、来保存套接字句柄;在那个结构中,同时包含了套接字的句柄,以及与那个套接字有关的其他信息。就象本章稍后还会讲述的那样,为完成端口提供服务的线程例程可通过这个参数。取得与其套字句柄有关的信息。

根据我们到目前为止学到的东西。首先来构建—个基本的应用程序框架。

程序清单89向人家阐述了如何使用完成端口模型。来开发—个回应(或“反射’)服务器应用

在这个程序中。我们基本上按下述步骤行事:

1)      创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。

2)      判断系统内到底安装了多少个处理器。

3)      创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务。在这个简单的例子中,我们为每个处理器都只创建—个工作者线程。这是出于事先已经预计到,到时候不会有任何线程进入“挂起”状态,造成由于线程数量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用CreateThread函数时,必须同时提供—个工作者线程,由线程在创建好执行。本节稍后还会详细讨论线程的职责。

4)      准备好—个监听套接字。在端口5150上监听进入的连接请求。

5)      使用accept函数,接受进入的连接请求。

6)      创建—个数据结构,用于容纳“单句柄数据”。  同时在结构中存入接受的套接字句柄。

7)      调用CreateIoCompletionPort将自accept返回的新套接字句柄向完成端口关联到  一起,通过完成键(CompletionKey)参数,将但句柄数据结构传递给CreateIoCompletionPort

8)      开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecvWSASend请求。这些I/O请求完成后,一个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求,稍后便会在步骤3)指定的工作者例程中。体验到这一点。

9)      重复步骤5)8)。直到服务器终止。

程序清单8完成端口的建立

StartWinsock()

//步骤一,创建一个完成端口

CompletionPort=CreateIoCompletionPort(INVALI_HANDLE_VALUE,NULL,0,0);

//步骤二判断有多少个处理器

GetSystemInfo(&SystemInfo);

//步骤三:根据处理器的数量创建工作线程,本例当中,工作线程的数目和处理器数目是相同的

for(i  = 0; i < SystemInfo.dwNumberOfProcessers,i++){

HANDLE ThreadHandle;

//创建工作者线程,并把完成端口作为参数传给线程

ThreadHandle=CreateThread(NULL,0

ServerWorkerThreadCompletionPort,

    0  &ThreadID);

//关闭线程句柄(仅仅关闭句柄,并非关闭线程本身)

CloseHandle(ThreadHandle);

}

//步骤四:创建监听套接字

Listen=WSASocket(AF_INET,S0CK_STREAM,0,NULL,

    WSA_FLAG_OVERLAPPED);

InternetAddr.sin_famlly=AF_INET;

InternetAddr.sin_addr.s_addr =  htonl(INADDR_ANY);

InternetAddr.sln_port  =  htons(5150);

bind(Listen,(PSOCKADDR)&InternetAddrsizeof(InternetAddr));

//准备监听套接字

listen(Listen5);

while(TRUE){

//步骤五,接入Socket,并和完成端口关联

Accept = WSAAccept(Listen,NULL,NULL,NULL,0);

//步骤六 创建一个perhandle结构,并和端口关联

PerHandleData=(LPPER_HANDLE_DATA)GlobalAlloc(GPTRsizeof(PER_HANDLE_DATA));

printf("Socket  number  %d  connected/n",Accept);

PerHandleData->Socket=Accept;

//步骤七,接入套接字和完成端口关联

CreateIoCompletionPort((HANDLE)Accept,

  CompletionPort,(DWORD)PerHandleData,0);

//步骤八

//开始进行I/O操作,用重叠I/O发送一些WSASend()WSARecv()

WSARecv(...)

 

 

2.完成端口和重叠I/O

 

    将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础。投递发送或接

 

收请求。开始I/O请求的处理。接下来,可开始依赖完成端口,来接收有关I/O操作完成情况的通知。从本质上说、完成瑞口模型利用了Win32重叠I/O机制。在这种机制中。象WSASendWSARecv这样的Winsock API调用会立即返回。此时, 需要由我们的应用程序负责在以后的某个时间。通过一个OVERLAPPED结构,来接收调用的结果。在完成端口模型中。要想做到这一点,需要使用GetQueuedCompletionStatus(获取排队完成状态)函数。让一个或者多个工作者线程在完成端口上等待。该函数的定义如下:

 

 

BOOL GetQueuedCompletionStatus(

 

                               HANDLE CompletionPort,

 

                               LPDWORD lpNumberOfBytesTransferred,

 

                               LPDWORD lpCompletionKey,

 

                               LPOVERLAPPED *lpOverlapped,

 

                               DWORD dwMilliseconds

 

                               )

其中,

CompletionPort参数对应与要在上面等待的完成端口.

lpNumberOfBytesTransferred参数负责在完成了—次I/O操作后(WSASendWSARecv)、接收实际传输的字节数。

lpCompletionKey参数为原先传递进入CreateCompletionPort函数的套接字返回“单句柄数据”。如我们早先所述,大家最好将套接字句柄保存在这个“键”(Key)中。

lpOverlapped参数用于接收完成的I/O操作的重叠结果。这实际是一个相当重要的参数,因为要用它获取每个I/O操作的数据。

DwMilliseconds用于指定调用者希望等待一个完成数据包在完成端门上出现的时间。假如将其设为INFINITE。调用会无休止地等持下去。

    

 

3.单句柄数据和单I/O操作数据

 

    —个工作者线程从GetQueuedCompletionStatus这个API调用接收到I/O完成通知后。在lpCompletionKeylpOverlapped参数中,会包含—些必要的套接字信息。利用这些信息,可通过完成端口,继续在一个套接字上的I/O处理,通过这些参数。可获得两方面重要的套接字数据: 单句柄数据,以及单I/O操作数据。

    其中,lpCompletionKey参数包含了“单句柄数据”,因为在—个套接字首次与完成端口关联到—起的时候。那些数据便与一个特定的套接字句柄对应起来了。这些数据正是我们在进行CreateIoCompletionPort  API调用的时候,通过CompletionKey参数传递的。  如早先所述。应用程序可通过该参数传递任意类型的数据。通常情况下,应用程序会将与I/O请求有关的套接字句柄保存在这里。

    lpOVerlapped参数则包含了—个OVERLAPPED结构,在它后边跟随“单I/O操作数据”。

我们的工作者线程处理—个完成数据包时(将数据原封不动打转回去,接受连接,投递另—个线程,等等). 这些信息是它必须要知道的. I/O操作数据可以是追加到一个OVERLAPPED结构末尾的任意数量的字节。假如一个函数要求用到一个OVERLAPPED结构,我们便必须将这样的—个结构传递进去,以满足它的耍求。要想做到这一点,一个简单的方法是定义—个结构。然后将OVERLAPPED结构作为新结构的第一个元素使用。举个例子来说。  可定义上述数据结构,实现对单I/O操作数据的管理:

typedef  struct  {

  OVERLAPPED Overlapped;

  WSABUF    DataBuf;

  CHAR      Bufferl[DATA_BUFSIZE];

  BOOL      OperationType;

}PER_IO_OPERATION_DATA;

该结构演示了通常要与I/O操作关联在—起的某些重要数据元素,比如刚才完成的那个I/O操作的类型(发送或接收请求).在这个结构中。我们认为用于已完成I/O操作的数据缓冲区是非常有用的。要想调用—个Winsock API函数,同时为其分配一个OVERLAPPED结构,既可将自己的结构“造型”为一个OVERLAPPED指针,亦可简单地撤消对结构中的OVBRLAPPED元素的引用。如下例所示:

 

PER_IO_OPERATION_DATA PerIoData;

 

可以象下边这样调用一个函数

 

  WSARecv(socket,…,(OVERLAPPED *)&PerIoData;

 

或者象下边这样

  WSARecv(socket,…,&( PerIoData.Overlapped));

    在工作线程的后面部分。等GetQueuedCompletionStatus函数返回了—个重叠结构(和完成键)后。便可通过撤消对OperationType成员的引用。调查到底是哪个操作投递到了这个句柄之上(只需将返回的重叠结构造型为自的PER_IO_OPERATlON_DATA结构)。对单I/O操作数据来说,它最大的—个优点便是允许我们在同一个句柄上。同时管理多个I/O操作(/写、多个读、多个写,等等)。大家此时或许会产生这样的疑问:在同—个套接字上,真的有必要同时投递多个I/O操作吗?答案在于系统的“伸缩性”,或者说“扩展能力”。例如,假定我们的机器安装了多个中央处理器。每个处理器都在远行一个工作者线程,那么在同一个时候、完全可能有几个不同的处理器在同一个套接字上,进行数据的收发操作。

    为了完成前述的简单回应服务器示例,我们需要提供一个ServerWorkerThread(服务器工作者线程)函数。在程序消单8.10中,我们展示了如何设计一个工作者线程例程,令其使用单句柄数据以及单I/O操作数据,I/O请求提供服务。

     程序代码见下节……

在程序清单8-9和程序清单8-10列出的简单服务器示例中(配套光盘也有),最后要注意的一处细节是如何正确地关闭I/O完成端口一—特别是同时运行了一个或多个线程,在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时,强行释放—个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket函数。任何尚未进行的重叠I/O操作都会完成。—旦所有套接字句柄都已关闭。便需在完成端口上,终止所有工作者线程的运行。要想做到这一点,需要使用

PostQueuedCompletionStatus函数,向每个工作者线程都发送—个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”.下面是PostQueuedCompletionStatus函数的定义:

BOOL PostQueuedCompletionStatus(

    HANDLE CompletlonPort,

    DW0RD  dwNumberOfBytesTrlansferred,

    DWORD  dwCompletlonKey,

LPOVERLAPPED lpoverlapped,

);

    其中,CompletionPort参数指定想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred,dwCompletionKeylpOverlapped这三个参数来说.每—个都允许我们指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数。这样—来。—个工作者线程收到传递过来的三个GetQueuedCompletionStatus函数参数后,便可根据由这三个参数的某一个设置的特殊值,决定何时应该退出。例如,可用dwCompletionPort参数传递0,而—个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭,便可使用CloseHandle函数,关闭完成端口。最终安全退出程序。

    4.其他问题

 

另外还有几种颇有价值的技术。可用来进—步改善套接字应用程序的总体I/O性能。值得考虑的一项技术是试验不同的套接字缓冲区大小,以改善I/O性能和应用程序的扩展能力。例如,假如某个程序只采用了—个比较大的缓冲区,仅能支持—个wSARecv请求,而不是同时设置了三个较小的缓冲区。提供对三个WSARecv请求的支持,那么该程序的扩展能力并不是很好,特别是在转移到安装了多个处理器的机器上之后。这是由于单独一个缓冲区每次只能处理一个线程!除此以外,单缓冲区设计还会对性能造成一定的干扰,假如—次仅能进行—次接收操作。网络协议驱动程序的潜力使不能得到充分发挥(它经常都会很“闲”)。换言之,假如在接收更多的数据前、需要等待—次WSARecv操作的完成,那么在WSARecv完成和下一次接收之间,整个协议实际上处于“休息”状态。

 另—个值得考虑的性能改进措施是用套接宇选项SO_SNDBUFSO_RCVBUF对内部套接字缓冲区的大小进行控制。利用这些选项,应用程序可更改—个套接字的内部数据缓冲区的大小。如将该设为0,Winsock便会在重叠I/O调用中直接使用应用程序的缓冲区、进行数据在

 

协议堆栈里的传人,传出。这样一来,在应用程序与Winsock之间,便避免了进行—次缓冲区复制的必要。下述代码片断阐释了如何使用SO_SNDBUF选项,来进行setsockopt函数的调用:

    setsockopt(socketSOL_S0CKET,SO_SNDBUF

    (char *)&nZero,sizeof(nZero));

要注意的是,将这些缓冲区的大小设为0,只有在一段给定的时间内,存在着多个I/O请求的前提下才会产生积极作用。等到第9章,我们会向大家更深入地讲述套接字选项的知识。

提升性能的最后一项措施是使用AcceptEx这个API调用,来进行连接请求的处理,并投递少量数据。这样一来,我们的应用程序只需通过一次API调用,便可为一次接受请求和数据的接收提供服务。从而减少了单独进行accept WSARecv调用造成的开销。这样做还有另一个好处,我们可使完成端口为AcceptEx提供服务,因为它也提供了一个OVERLAPPED结构。

假如事先预计到自己的服务器应用在一个连接建立好之后,只会进行少量的recv-send(收发)操作,那么AcceptEx便显得相当有用(比如在设计 一个Web服务器的时候)。否则的话,接受一个连接后,假如程序要负责数百上千次数据 的传输操作,这样的对性能便没有多大的助益。

最后提醒大家,注意,在Winsock中,一个Winsock应用不应使用ReadFile WriteFile 这两个Win32函数,在一个完成端口上进行IO处理。尽管这两个函数确实提供了一个OVERLAPPED结构,而且可在完成端口上成功的使用,但就在Winsock 2环境下进行IO处理来说,WSARecv WSASend 这两个函数却进行了更大程序的优化。若使用ReadFile WriteFile ,需在进行大量不必要的内核/用户模式进行调用、线程执行场景的频繁切换以及参数的汇集等等,使总体性能大打折扣

 

#include  " stdafx.h "

 

#include 
< iostream.h >

 

#include 

 

#include 

 

#include 

 


 

#define  PORT 5150

 

#define  DATA_BUFSIZE 8192

 


 

typedef 
struct  

 

{

 

  OVERLAPPED OVerlapped;

 

  WSABUF DATABuf;

 

  CHAR Buffer[DATA_BUFSIZE];

 

  DWORD BytesSend,BytesRecv;

 

}PER_IO_OPERATION_DATA, 
* LPPER_IO_OPERATION_DATA;

 


 

typedef 
struct  

 

 {

 

  SOCKET Socket;

 

}PER_HANDLE_DATA,
* LPPER_HANDLE_DATA;

 


 


 

DWORD WINAPI ServerWorkerThread(LPVOID ComlpetionPortID);

 


 


int  main( int  argc,  char *  argv[])


 

{

 

       SOCKADDR_IN InternetAddr;

 

  SOCKET Listen,Accept;

 

  HANDLE CompetionPort;

 

  SYSTEM_INFO SystenInfo;

 

  LPPER_HANDLE_DATA PerHandleData;

 

  LPPER_IO_OPERATION_DATA PerIOData;

 

  
int  i;

 

  DWORD RecvBytes;

 

  DWORD Flags;

 

  DWORD ThreadID;

 

  WSADATA wsadata;

 

  DWORD Ret;

 

  

 

  
if  (Ret  =  WSAStartup( 0x2020 , & wsadata)  !=   0 )

 

  {

 

    printf(
" WSAStartup failed with error %d " ,Ret);

 

    
return   0 ;

 

  }

 


 


 

   
// 打开一个空的完成端口

 

  
if  ((CompetionPort  =  CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0 , 0 ))  ==  NULL)

 

  {

 

    printf(
" CreateIoCompletionPort failed with error %d " ,GetLastError());

 

    
return   0 ;

 

  }

 

  

 

  GetSystemInfo(
& SystenInfo);

 

   

 

  
//  开启cpu个数的2倍个的线程

 

  
for  (i = 0 ; i  <  SystenInfo.dwNumberOfProcessors * 2 ; i ++ )

 

  {

 

    HANDLE ThreadHandle;

 

    
// 创建服务器工作线程,并且向线程传送完成端口

 

    
if  ((ThreadHandle  =  CreateThread(NULL, 0 ,ServerWorkerThread,CompetionPort, 0 , & ThreadID))  ==  NULL)

 

    {

 

      printf(
" CreateThread failed with error %d "  ,GetLastError());

 

      
return   0 ;

 

    }

 

    CloseHandle(ThreadHandle);

 

  }

 

  

 

  
// 打开一个服务器socket

 

  
if  ((Listen  =  WSASocket(AF_INET, SOCK_STREAM,  0 , NULL,  0 , WSA_FLAG_OVERLAPPED))  ==  INVALID_SOCKET)

 

  {

 

    printf(
" WSASocket() failed with error %d " , WSAGetLastError());

 

    
return   0 ;

 

  } 

 


 


 

  InternetAddr.sin_family 
=  AF_INET;

 

  InternetAddr.sin_addr.S_un.S_addr 
=  htonl(INADDR_ANY);

 

  InternetAddr.sin_port 
=  htons(PORT);

 

  

 

  
if  (bind(Listen,(LPSOCKADDR) & InternetAddr, sizeof (InternetAddr))  ==  SOCKET_ERROR)

 

  {

 

    printf(
" bind failed with error %d " ,WSAGetLastError());

 

    
return   0 ;

 

  }

 


 

  
if  (listen(Listen, 5 ==  SOCKET_ERROR)

 

  {

 

    printf(
" listen failed with error %d " ,WSAGetLastError());

 

    
return   0 ;

 

  }

 


 

  
// 接收连接并且分发给完成端口

 

  
while  (TRUE)

 

  {

 

    
if  ((Accept  =  WSAAccept(Listen,NULL,NULL,NULL, 0 ))  ==  SOCKET_ERROR)

 

    {

 

      printf(
" WSAAccept failed with error %d " ,WSAGetLastError());

 

      
return   0 ;

 

    }

 


 

    
// 创建与套接字相关的套接字信息结构

 

    
if  ((PerHandleData  =  (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof (PER_HANDLE_DATA)))  ==  NULL)

 

    {

 

      printf(
" GlobalAlloc failed with error %d " ,GetLastError());

 

      
return   0 ;

 

    }

 

    

 

    
//  Associate the accepted socket with the original completion port.

 

    printf(
" Socket number %d connected " ,Accept);

 

    PerHandleData
-> Socket  =  Accept; // 结构中存入接收的套接字

 

    

 

    
// 与我们的创建的那个完成端口关联起来,将关键项也与指定的一个完成端口关联

 

    
if  ((CreateIoCompletionPort((HANDLE)Accept,CompetionPort,(DWORD)PerHandleData, 0 ))  ==  NULL)

 

    {

 

      printf(
" CreateIoCompletionPort failed with error%d " ,GetLastError());

 

      
return   0 ;

 

    } 

 


 

    
//  创建同下面的WSARecv调用相关的IO套接字信息结构体

 

    
if  ((PerIOData  =  (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof (PER_IO_OPERATION_DATA)))  =  NULL)

 

    {

 

      printf(
" GlobalAloc failed with error %d " ,GetLastError());

 

      
return   0 ;

 

    }

 

    ZeroMemory(
& (PerIOData -> OVerlapped), sizeof (OVERLAPPED));

 

    PerIOData
-> BytesRecv  =   0 ;

 

    PerIOData
-> BytesSend  =   0 ;

 

    PerIOData
-> DATABuf.len  =  DATA_BUFSIZE;

 

    PerIOData
-> DATABuf.buf  =  PerIOData -> Buffer;

 

    Flags 
=   0 ;

 


 

    
if  (WSARecv(Accept, & (PerIOData -> DATABuf), 1 , & RecvBytes, & Flags, & (PerIOData -> OVerlapped),NULL)  ==  SOCKET_ERROR)

 

    {

 

     
if  (WSAGetLastError()  !=  ERROR_IO_PENDING)

 

     {

 

       printf(
" WSARecv() failed with error %d " ,WSAGetLastError());

 

       
return   0 ;

 

     }

 

    }

 

  }

 

  
return   0 ;

 

}

 
// 工作线程 

 

DWORD WINAPI ServerWorkerThread(LPVOID ComlpetionPortID) 

 



 

  HANDLE ComplectionPort 
=  (HANDLE) ComlpetionPortID; 

 

  DWORD BytesTransferred; 

 

  LPOVERLAPPED Overlapped; 

 

  LPPER_HANDLE_DATA PerHandleData; 

 

  LPPER_IO_OPERATION_DATA PerIOData; 

 

  DWORD SendBytes,RecvBytes; 

 

  DWORD Flags; 

 

  

 

  
while  (TRUE) 

 

  { 

 

    
if  (GetQueuedCompletionStatus(ComplectionPort, & BytesTransferred,(LPDWORD) & PerHandleData,(LPOVERLAPPED * ) & PerIOData,INFINITE)  ==   0

 

    { 

 

      printf(
" GetQueuedCompletionStatus failed with error%d " ,GetLastError()); 

 

      
return   0

 

    } 

 


 

    
// 首先检查套接字上是否发生错误,如果发生了则关闭套接字并且清除同套节字相关的SOCKET_INFORATION 结构体 

 

    
if  (BytesTransferred  ==   0

 

    { 

 

      printf(
" Closing Socket %d " ,PerHandleData -> Socket); 

 

      
if  (closesocket(PerHandleData -> Socket)  ==  SOCKET_ERROR) 

 

      { 

 

        printf(
" closesocket failed with error %d " ,WSAGetLastError()); 

 

        
return   0

 

      } 

 

      GlobalFree(PerHandleData); 

 

      GlobalFree(PerIOData); 

 

      
continue

 

    } 

 


 

    
// 检查BytesRecv域是否等于0,如果是,说明WSARecv调用刚刚完成,可以用从己完成的WSARecv调用返回的BytesTransferred值更新BytesRecv域 

 

    
if  (PerIOData -> BytesRecv  ==   0

 

    { 

 

      PerIOData
-> BytesRecv  =  BytesTransferred; 

 

      PerIOData
-> BytesSend  =   0

 

    } 

 

    
else  

 

    { 

 

      PerIOData
-> BytesRecv  += BytesTransferred; 

 

    } 

 


 

    
//  

 

    
if  (PerIOData -> BytesRecv  >  PerIOData -> BytesSend) 

 



 

       
// 发布另一个WSASend()请求,因为WSASendi 不能确保发送了请的所有字节,继续WSASend调用直至发送完所有收到的字节 

 

      ZeroMemory(
& (PerIOData -> OVerlapped), sizeof (OVERLAPPED)); 

 

      PerIOData
-> DATABuf.buf  =  PerIOData -> Buffer  +  PerIOData -> BytesSend; 

 

      PerIOData
-> DATABuf.len  =  PerIOData -> BytesRecv  -  PerIOData -> BytesSend; 

 

      

 

      
if  (WSASend(PerHandleData -> Socket, & (PerIOData -> DATABuf), 1 , & SendBytes, 0 , & (PerIOData -> OVerlapped),NULL)  == SOCKET_ERROR ) 

 

      { 

 

        
if  (WSAGetLastError()  !=  ERROR_IO_PENDING) 

 

        { 

 

          printf(
" WSASend() fialed with error %d " ,WSAGetLastError()); 

 

          
return   0

 

        } 

 

      } 

 

    } 

 

    
else  

 

    { 

 

      PerIOData
-> BytesRecv  =   0

 

      
// Now that is no more bytes to send post another WSARecv() request 

 

      
// 现在己经发送完成 

 

      Flags 
=   0

 

      ZeroMemory(
& (PerIOData -> OVerlapped), sizeof (OVERLAPPED)); 

 

      PerIOData
-> DATABuf.buf  =  PerIOData -> Buffer; 

 

      PerIOData
-> DATABuf.len  =  DATA_BUFSIZE; 

 

      
if  (WSARecv(PerHandleData -> Socket, & (PerIOData -> DATABuf), 1 , & RecvBytes, & Flags, & (PerIOData -> OVerlapped),NULL)  ==  SOCKET_ERROR) 

 

      { 

 

        
if  (WSAGetLastError()  !=  ERROR_IO_PENDING) 

 

        { 

 

          printf(
" WSARecv() failed with error %d " ,WSAGetLastError()); 

 

          
return   0

 

        } 

 

      } 

 

    } 

 

  } 

 

  

 






  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值