Windows socket-重叠模型

本文详细介绍了重叠I/O模型及其优势,并深入探讨了基于事件通知和完成例程两种管理重叠I/O请求的方法。通过具体实例代码展示了如何在Windows环境下实现这两种模型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重叠I/O

一.   重叠模型的优点

1.      可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。

2.      比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。

         因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。

而这4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。

3.      非常好的性能,已经直逼完成端口了

二. 重叠模型的基本原理

      概括一点说,重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。

      需要注意的是,有两个方法可以用来管理重叠IO请求的完成情况(就是说接到重叠操作完成的通知):

1.      事件对象通知(event object notification)

2.      完成例程(completion routines) ,注意,这里并不是完成端口

既然要使用重叠结构,我们常用的send, sendto, recv, recvfrom也都要被WSASend, WSASendto, WSARecv, WSARecvFrom替换掉了, 它们的用法我后面会讲到,这里只需要注意一点,它们的参数中都有一个Overlapped参数,我们可以假设是把我们的WSARecv这样的操作操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交给重叠结构去操心,而其中重叠结构又要与Windows的事件对象“绑定”在一起,这样我们调用完WSARecv以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成,然后我们就可以来根据重叠操作的结果取得我们想要得的数据了。

 三. 关于重叠模型的基础知识

1.      WSAOVERLAPPED结构

typedef  struct  _WSAOVERLAPPED {
  DWORD Internal;
  DWORD InternalHigh;
  DWORD Offset;
  DWORD OffsetHigh;
  WSAEVENT hEvent;      
//  唯一需要关注的参数,用来关联WSAEvent对象
 } WSAOVERLAPPED,  * LPWSAOVERLAPPED;
WSAEVENT  event ;                    //  定义事件
WSAOVERLAPPED AcceptOverlapped ;  //  定义重叠结构
event   =  WSACreateEvent();          //  建立一个事件对象句柄
ZeroMemory( & AcceptOverlapped,  sizeof (WSAOVERLAPPED));  //  初始化重叠结构
AcceptOverlapped.hEvent  =   event ;     //  Done !!
2.      WSARecv系列函数
  int  WSARecv(
SOCKET s,                      
//  当然是投递这个操作的套接字
LPWSABUF lpBuffers,           //  接收缓冲区,与Recv函数不同
              //  这里需要一个由WSABUF结构构成的数组
DWORD dwBufferCount,       //  数组中WSABUF结构的数量
LPDWORD lpNumberOfBytesRecvd,   //  如果接收操作立即完成,这里会返回函数调用
                  //  所接收到的字节数
LPDWORD lpFlags,              //  这里设置为0 即可
LPWSAOVERLAPPED lpOverlapped,   //  “绑定”的重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
                               
//  完成例程中将会用到的参数,否则设置为 NULL
  );

返回值:
WSA_IO_PENDING : 最常见的返回值,这是说明我们的WSARecv操作成功了,但是
                    I
/ O操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成
ContractedBlock.gif ExpandedBlockStart.gif 例子
SOCKET s;
WSABUF DataBuf;           
// 定义WSABUF结构的缓冲区
// 初始化一下DataBuf
#define DATA_BUFSIZE 5096
char buffer[DATA_BUFSIZE];
ZeroMemory(buffer, DATA_BUFSIZE);
DataBuf.len 
= DATA_BUFSIZE;
DataBuf.buf 
= buffer;
DWORD dwBufferCount 
= 1, dwRecvBytes = 0, Flags = 0;
// 建立需要的重叠结构
WSAOVERLAPPED AcceptOverlapped ;// 如果要处理多个操作,这里当然需要一个
// WSAOVERLAPPED数组
WSAEVENT event;     // 如果要多个事件,这里当然也需要一个WSAEVENT数组
                           
// 需要注意的是可能一个SOCKET同时会有一个以上的重叠请求,
//  也就会对应一个以上的WSAEVENT
Event = WSACreateEvent();
ZeroMemory(
&AcceptOverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent 
= event;     // 关键的一步,把事件句柄“绑定”到重叠结构上
// 作了这么多工作,终于可以使用WSARecv来把我们的请求投递到重叠结构上了,呼。。。。
WSARecv(s, &DataBuf, dwBufferCount, &dwRecvBytes, 
&Flags, &AcceptOverlapped, NULL);

3.      WSAWaitForMultipleEvents函数

 

 DWORD WSAWaitForMultipleEvents(
 DWORD cEvents,                        
//  等候事件的总数量
const  WSAEVENT *  lphEvents,            //  事件数组的指针
BOOL fWaitAll,           //  这个要多说两句:
//  如果设置为 TRUE,则事件数组中所有事件被传信的时候函数才会返回
 
//  FALSE则任何一个事件被传信函数都要返回
 
//  我们这里肯定是要设置为FALSE的
DWORD dwTimeout,     //  超时时间,如果超时,函数会返回 WSA_WAIT_TIMEOUT
                               
//  如果设置为0,函数会立即返回
                            
//  如果设置为 WSA_INFINITE只有在某一个事件被传信后才会返回
                            
//  在这里不建议设置为WSA_INFINITE,
BOOL fAlertable        //  在完成例程中会用到这个参数,这里我们先设置为FALSE
 );

返回值:
    WSA_WAIT_TIMEOUT :最常见的返回值,我们需要做的就是继续Wait
    WSA_WAIT_FAILED : 出现了错误,请检查cEvents和lphEvents两个参数是否有效
4.      WSAGetOverlappedResult函数
既然我们可以通过WSAWaitForMultipleEvents函数来得到重叠操作完成的通知,那么我们自然也需要一个函数来查询一下重叠操作的结果,定义如下
            BOOL WSAGetOverlappedResult(
                          SOCKET s,                   
//  SOCKET,不用说了
                          LPWSAOVERLAPPED lpOverlapped,   //  这里是我们想要查询结果的那个重叠结构的指针
                          LPDWORD lpcbTransfer,      //  本次重叠操作的实际接收(或发送)的字节数
                          BOOL fWait,                 //  设置为TRUE,除非重叠操作完成,否则函数不会返回
                                                              
//  设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE
                                                              
//  错误为WSA_IO_INCOMPLETE
                                                              
//  不过因为我们是等待事件传信来通知我们操作完成,所以我们这里设
                
//  置成什么都没有作用…..-_-b  别仍鸡蛋啊,我也想说得清楚一些…
                          LPDWORD lpdwFlags        //  指向DWORD的指针,负责接收结果标志
                        );
如果WSAGetOverlappedResult完成以后,第三个参数返回是 0 ,则说明通信对方已经关闭连接,我们这边的SOCKET, Event之类的也就可关闭。
ContractedBlock.gif ExpandedBlockStart.gif 例子
DWORD dwBytesTransferred;
WSAGetOverlappedResult( AcceptSocket, AcceptOverlapped ,
&dwBytesTransferred, FALSE, &Flags);
// 先检查通信对方是否已经关闭连接
// 如果==0则表示连接已经,则关闭套接字
if(dwBytesTransferred == 0)
{
         closesocket(AcceptSocket);
      WSACloseEvent(EventArray[dwIndex]);    
// 关闭事件
         return;
}

 

原文链接:http://blog.youkuaiyun.com/PiggyXP/archive/2004/09/23/114883.aspx

完成例程

一.         完成例程的优点

1.    首先需要指明的是,这里的“完成例程”(Completion Routine)并非是大家所常听到的 “完成端口”(Completion Port),而是另外一种管理重叠I/O请求的方式,而至于什么是重叠I/O,简单来讲就是Windows系统内部管理I/O的一种方式,核心就是调用的ReadFile和WriteFile函数,在制定设备上执行I/O操作,不光是可用于网络通信,也可以用于其他需要的地方。

在Windows系统中,管理重叠I/O可以有三种方式:

(1)  基于事件通知的重叠I/O模型

 (2)  基于“完成例程”的重叠I/O模型

 (3)  “完成端口”模型

虽然都是基于重叠I/O,但是因为前两种模型都是需要自己来管理任务的分派 ,所以性能上没有区别,而完成端口是创建完成端口对象使操作系统亲自来管理任务的分派,所以完成端口肯定是能获得最好的性能。

2.    如果你想要使用重叠I/O机制带来的高性能模型,又懊恼于基于事件通知的重叠模型要收到64个等待事件的限制,还有点畏惧完成端口稍显复杂的初始化过程,那么“完成例程”无疑是你最好的选择!^_^ 因为完成例程摆脱了事件通知的限制,可以连入任意数量客户端而不用另开线程,也就是说只用很简单的一些代码就可以利用Windows内部的I/O机制来获得网络服务器的高性能,是不是心动了呢?那就一起往下看。。。。。。。。。。

3.    而且个人感觉“完成例程”的方式比重叠I/O更好理解,因为就和我们传统的“回调函数”是一样的,也更容易使用一些,推荐!

括一点说,上一篇拙作中提到的那个基于事件通知的重叠I/O模型,在你投递了一个请求以后(比如WSARecv),系统在完成以后是用事件来通知你的,而在完成例程中,系统在网络操作完成以后会自动调用你提供的回调函数,区别仅此而已,是不是很简单呢?如果还没有看明白,我们打个通俗易懂的比方,完成例程的处理过程,也就像我们告诉系统,说“我想要在网络上接收网络数据,你去帮我办一下”(投递WSARecv操作),“不过我并不知道网络数据合适到达,总之在接收到网络数据之后,你直接就调用我给你的这个函数(比如_CompletionProess),把他们保存到内存中或是显示到界面中等等,全权交给你处理了”,于是乎,系统在接收到网络数据之后,一方面系统会给我们一个通知,另外同时系统也会自动调用我们事先准备好的回调函数,就不需要我们自己操心了。

 

完成例程回调函数原型及传递方式

Void CALLBACK _CompletionRoutineFunc(   
  DWORD dwError, 
//  标志咱们投递的重叠操作,比如WSARecv,完成的状态是什么   
  DWORD cbTransferred,  //  指明了在重叠操作期间,实际传输的字节量是多大   
  LPWSAOVERLAPPED lpOverlapped,  //  参数指明传递到最初的IO调用内的一个重叠  结构   
  DWORD dwFlags   //  返回操作结束时可能用的标志(一般没用));   

 

四. 完成例程的实现步骤

基础知识方面需要知道的就是这么多,下面我们配合代码,来一步步的讲解如何亲手实现一个完成例程模型(前面几步的步骤和基于事件通知的重叠I/O方法是一样的)。

【第一步】创建一个套接字,开始在指定的端口上监听连接请求

和其他的SOCKET初始化全无二致,直接照搬即可,在此也不多费唇舌了,需要注意的是为了一目了然,我去掉了错误处理,平常可不要这样啊,尽管这里出错的几率比较小。

view plaincopy to clipboardprint?
WSADATA wsaData;  
WSAStartup(MAKEWORD(2,2),&wsaData);  
 
ListenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);  //创建TCP套接字  
 
SOCKADDR_IN ServerAddr;                           //分配端口及协议族并绑定  
ServerAddr.sin_family=AF_INET;                                  
ServerAddr.sin_addr.S_un.S_addr  =htonl(INADDR_ANY);            
ServerAddr.sin_port=htons(11111);        // 在11111端口监听  
                                    // 端口号可以随意更改,但最好不要少于1024  
 
bind(ListenSocket,(LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)); // 绑定套接字  
 
listen(ListenSocket, 5);                                   //开始监听  


【第二步】接受一个入站的连接请求

  一个accept就完了,都是一样一样一样一样的啊~~~~~~~~~~

 至于AcceptEx的使用,在完成端口中我会讲到,这里就先不一次灌输这么多了,不消化啊^_^

view plaincopy to clipboardprint?
AcceptSocket = accept (ListenSocket, NULL,NULL) ;  

当然,这里是我偷懒,如果想要获得连入客户端的信息(记得论坛上也常有人问到),accept的后两个参数就不要用NULL,而是这样

view plaincopy to clipboardprint?
SOCKADDR_IN ClientAddr;                   // 定义一个客户端得地址结构作为参数  
int addr_length=sizeof(ClientAddr);  
AcceptSocket = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length);  
// 于是乎,我们就可以轻松得知连入客户端的信息了  
LPCTSTR lpIP =  inet_ntoa(ClientAddr.sin_addr);      // 连入客户端的 IP  
UINT nPort = ClientAddr.sin_port;                      // 连入客户端的Port  

 

【第三步】准备好我们的重叠结构

有新的套接字连入以后,新建立一个WSAOVERLAPPED重叠结构(当然也可以提前建立好),准备绑定到我们的重叠操作上去。这里也可以看到和上一篇中的明显区别,就是不用再为WSAOVERLAPPED结构绑定一个hEvent了。

view plaincopy to clipboardprint?
// 这里只定义一个,实际上是每一个SOCKET的每一个操作都需要绑定一个重叠结构的,所以在实际使用面对多个客户端的时候要定义为数组,详见示例代码;  
WSAOVERLAPPED AcceptOverlapped;   
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));      // 置零  

    

【第四步】开始在套接字上投递WSARecv请求,需要将第三步准备的WSAOVERLAPPED结构和我们定义的完成例程函数为参数

各个变量都已经初始化OK以后,我们就可以开始进行具体的Socket通信函数调用了,然后让系统内部的重叠结构来替我们管理I/O请求,我们只用等待网络通信完成后调用咱们的回调函数就OK了。

这个步骤的重点就是 绑定一个Overlapped变量和一个完成例程函数
view plaincopy to clipboardprint?
// 将WSAOVERLAPPED结构指定为一个参数,在套接字上投递一个异步WSARecv()请求  
// 并提供下面的作为完成例程的_CompletionRoutine回调函数(函数名字)  
if(WSARecv(  
    AcceptSocket,  
    &DataBuf,  
    1,  
    &dwRecvBytes,  
    &Flags,  
    &AcceptOverlapped,  
    _CompletionRoutine) == SOCKET_ERROR)  // 注意我们传入的回调函数指针  
    {  
        if(WSAGetLastError() != WSA_IO_PENDING)  
        {  
            ReleaseSocket(nSockIndex);  
            continue;  
            }  
        }  
}  

  

【第五步】 调用WSAWaitForMultipleEvents函数或者SleepEx函数等待重叠操作返回的结果

  我们在前面提到过,投递完WSARecv操作,并绑定了Overlapped结构和完成例程函数之后,我们基本就是完事大吉了,等了系统自己去完成网络通信,并在接收到数据的时候,会自动调用我们的完成例程函数。

  而我们在主线程中需要做的事情只有:做别的事情,并且等待系统完成了完成例程调用后的返回结果。

就是说在WSARecv调用发起完毕之后,我们不得不在后面再紧跟上一些等待完成结果的代码。有两种办法可以实现:

1)    和上一篇重叠I/O中讲到的一样,我们可以使用WSAWaitForMultipleEvent来等待重叠操作的事件通知, 方法如下:

view plaincopy to clipboardprint?
// 因为WSAWaitForMultipleEvents() API要求  
// 在一个或多个事件对象上等待, 但是这个事件数组已经不是和SOCKET相关联的了  
// 因此不得不创建一个伪事件对象.   
WSAEVENT EventArray[1];       
EventArray[0] = WSACreateEvent();                        // 建立一个事件  
          
// 然后就等待重叠请求完成就可以了,注意保存返回值,这个很重要  
DWORD dwIndex = WSAWaitForMultipleEvents(1,EventArray,FALSE,WSA_INFINITE,TRUE);  


这里参数的含义我就不细说了,MSDN上一看就明白,调用这个函数以后,线程就会置于一个警觉的等待状态,注意 fAlertable 参数一定要设置为 TRUE。

2)    可以直接使用SleepEx函数来完成等待,效果都是一样的。

SleepEx函数调用起来就简单得多,它的函数原型定义是这样的


    
view plaincopy to clipboardprint?
DWORD SleepEx(  
             DWORD dwMilliseconds,  // 等待的超时时间,如果设置为INFINITE就会一直等待下去  
             BOOL   bAlertable   // 是否置于警觉状态,如果为FALSE,则一定要等待超时时间完毕之后才会返回,这里我们是希望重叠操作一完成就能返回,所以同(1)一样,我们一定要设置为TRUE  
 );  

    调用这个函数的时候,同样注意用一个DWORD类型变量来保存它的返回值,后面会派上用场。


【第六步】通过等待函数的返回值取得重叠操作的完成结果

这是我们最关心的事情,费了那么大劲投递的这个重叠操作究竟是个什么结果呢?就是通过上一步中我们调用的等待函数的DWORD类型的返回值,正常情况下,在操作完成之后,应该是返回WAIT_IO_COMPLETION,如果返回的是 WAIT_TIMEOUT,则表示等待设置的超时时间到了,但是重叠操作依旧没有完成,应该通过循环再继续等待。如果是其他返回值,那就坏事了,说明网络通信出现了其他异常,程序就可以报错退出了……

判断返回值的代码大致如下:
view plaincopy to clipboardprint?
///  
// 返回WAIT_IO_COMPLETION表示一个重叠请求完成例程代码的结束。继续为更多的完成例程服务  
if(dwIndex == WAIT_IO_COMPLETION)  
{  
TRACE("重叠操作完成...\n");  
}  
else if( dwIndex==WAIT_TIMEOUT )  
{  
     TRACE(“超时了,继续调用等待函数”);  
}  
else  
{  
    TRACE(“废了…”);  
}  

 

操作完成了之后,就说明我们上一个操作已经成功了,成功了之后做什么?当然是继续投递下一个重叠操作了啊…..继续上面的循环。

 

【第七步】继续回到第四步,在套接字上继续投递WSARecv请求,重复步骤4-7

 

代码

ContractedBlock.gif ExpandedBlockStart.gif 共同部分-监听socket
    nRet=WSAStartup(MAKEWORD(2,2),&wsaData);                      
    
if(nRet!=0)
    {
        AfxMessageBox(
"Load winsock2 failed");
        WSACleanup();
        
return -1;
    }

    sockListen 
= socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);             //创建服务套接字(TCP)

    SOCKADDR_IN ServerAddr;                                           
//分配端口及协议族并绑定

    ServerAddr.sin_family
=AF_INET;                                
    ServerAddr.sin_addr.S_un.S_addr  
=htonl(INADDR_ANY);          
    ServerAddr.sin_port
=htons(port);

    nRet
=bind(sockListen,(LPSOCKADDR)&ServerAddr,sizeof(ServerAddr)); // 绑定套接字

    
if(nRet==SOCKET_ERROR)
    {
        AfxMessageBox(
"Bind Socket Fail!");
        closesocket(sockListen);
        
return -1;
    }

    nRet
=listen(sockListen,1);                                        //开始监听,并设置监听客户端数量
    if(nRet==SOCKET_ERROR)     
    {
        AfxMessageBox(
"Listening Fail!");
        
return -1;
    }

 

监听线程

ContractedBlock.gif ExpandedBlockStart.gif BlockingModel监听线程
UINT _ServerListenThread(LPVOID lParam)
{
    TRACE(
"服务器端监听中dot.gif..\n");

    SOCKADDR_IN ClientAddr;                
// 定义一个客户端得地址结构作为参数

    
int addr_length=sizeof(ClientAddr);

    CServerSocket
* psvSock = (CServerSocket*)lParam;

    ::SendMessage(psvSock
->m_DlgWnd,WM_MSG_NEWMSG,0,(LPARAM)(LPCSTR)"开始监听dot.gif.");

    
while(TRUE)
    {
        psvSock
->m_sockComm = accept(psvSock->m_sockListen,(SOCKADDR*)&ClientAddr,&addr_length); 

                
if(psvSock->m_sockComm == INVALID_SOCKET)
        {
            AfxMessageBox(
"Accept Connection failed");
            
return 1;
        }

        CString strClientIP 
= inet_ntoa(ClientAddr.sin_addr);

        ::SendMessage(psvSock
->m_DlgWnd,WM_MSG_NEWMSG,0,(LPARAM)(LPCSTR)(strClientIP+"连接到本服务器"));

        
while(TRUE)
        {
            
char pRevMsg[BUF_SIZE]={0};

            
int iLen=recv(psvSock->m_sockComm,pRevMsg,BUF_SIZE,0);     // 接收数据

            
if(iLen>0)
            {
                
if(strcmp((LPCSTR)pRevMsg,"[EXIT]")==0)
                    
return 0;

                ::SendMessage(psvSock
->m_DlgWnd,WM_MSG_NEWMSG,0,(LPARAM)(LPCSTR)pRevMsg);
            }
            
else if(iLen==SOCKET_ERROR)
            {
                ::SendMessage(psvSock
->m_DlgWnd,WM_MSG_NEWMSG,0,(LPARAM)(LPCSTR)(strClientIP+"断开连接"));

                
break;
            }
        }
    }

    
return 0;
}

 

 

ContractedBlock.gif ExpandedBlockStart.gif 事件选择模型-监听线程
UINT _ServerListenThread(LPVOID lParam)
{
        TRACE(
"开始监听!");

    
while(TRUE)
    {
        TRACE(
"\n进入循环!");

        Index 
= WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE);

        Index 
= Index - WSA_WAIT_EVENT_0;

        
for(i = Index;i < EventTotal;i++)
        {
            Index 
= WSAWaitForMultipleEvents(1,&EventArray[i],TRUE,1000,FALSE);

            
if((Index == WSA_WAIT_FAILED) || (Index == WSA_WAIT_TIMEOUT))
                
continue;
            
else
            {
                Index 
= i;

                WSAEnumNetworkEvents(sockAcceptArray[Index], EventArray[Index],
&NetworkEvents);

                
if(NetworkEvents.lNetworkEvents & FD_ACCEPT)
                {
                    
if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
                    {
                        AfxMessageBox(
"accept error!");

                        
return -1;
                    }

                    sockAccept 
= accept(sockAcceptArray[Index],NULL,NULL);

                    NewEvent 
= WSACreateEvent();

                    WSAEventSelect(sockAccept,NewEvent,FD_READ 
| FD_CLOSE);

                    EventArray[EventTotal] 
= NewEvent;
                    sockAcceptArray[EventTotal] 
= sockAccept;

                    EventTotal 
++;

                    TRACE(
"连接成功!%d\n",sockAccept);


                }

                
if(NetworkEvents.lNetworkEvents & FD_READ)
                {
                    
if(NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
                    {
                        AfxMessageBox(
"Read error!");

                        
return -1;
                    }

                    
char buffer[DATA_BUFSIZE];

                    recv(sockAcceptArray[Index 
- WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
                    
                    TRACE(buffer);
                }

                
if(NetworkEvents.lNetworkEvents & FD_CLOSE)
                {
                    
if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
                    {
                        AfxMessageBox(
"close error!");

                        
return -1;
                    }
                    closesocket(sockAcceptArray[Index]);

                    TRACE(
"\nclose!");

                    WSACloseEvent(EventArray[Index 
- WSA_WAIT_EVENT_0]);

                    EventTotal
--;
                }

            }
        }
    }

    
return 0;
}

 

ContractedBlock.gif ExpandedBlockStart.gif 重叠模型-监听线程

UINT _ServerListenThread(LPVOID lParam)
{
    TRACE(
"服务器端监听中dot.gif..\n");

    CServerSocket
* pServer = (CServerSocket*)lParam;

    SOCKADDR_IN ClientAddr;                   
// 定义一个客户端得地址结构作为参数
    int addr_length=sizeof(ClientAddr);

    
while(TRUE)
    {
        
if(dwEventTotal>=WSA_MAXIMUM_WAIT_EVENTS)  // 因为超出了Windows的最大等待事件数量
        {
            AfxMessageBox(
"已达到最大连接数!");
            
continue;
        }

        SOCKET sockTemp 
= accept(sockListen,(SOCKADDR*)&ClientAddr,&addr_length); 
        
if(sockTemp  == INVALID_SOCKET)
        {
            AfxMessageBox(
"Accept Connection failed!");
            
continue;
        }

        nSockIndex 
= pServer->GetEmptySocket();    // 从Socket数组中获得一个空闲的socket

        sockAcceptArray[nSockIndex] 
= sockTemp;
        
        
// 这里可以取得客户端的IP和端口,但是我们只取其中的SOCKET编号
        LPCTSTR lpIP =  inet_ntoa(ClientAddr.sin_addr);
        UINT nPort 
= ClientAddr.sin_port;
        CString strSock;
        strSock.Format(
"SOCKET编号:%d",sockAcceptArray[nSockIndex] );
        ::SendMessage(pServer
->m_hNotifyWnd,WM_MSG_NEW_SOCKET,
            (LPARAM)(LPCTSTR)strSock,(LPARAM)(LPCTSTR)
"客户端建立连接!");


        
// 接收客户端连接以后,为每一个连入的SOCKET都初始化建立一个重叠结构
        Flags = 0;

        EventArray[nSockIndex] 
= WSACreateEvent();

        ZeroMemory(
&AcceptOverlapped[nSockIndex],sizeof(WSAOVERLAPPED));

        
char buffer[DATA_BUFSIZE];
        ZeroMemory(buffer,DATA_BUFSIZE);

        AcceptOverlapped[nSockIndex].hEvent 
= EventArray[nSockIndex]; // 关联事件

        DataBuf[nSockIndex].len 
= DATA_BUFSIZE;
        DataBuf[nSockIndex].buf 
= buffer;


    
        
// 投递第一个WSARecv请求,以便开始在套接字上接受数据
        if(WSARecv(sockAcceptArray[nSockIndex] ,&DataBuf[nSockIndex],1,&dwRecvBytes,&Flags,
            
& AcceptOverlapped[nSockIndex] ,NULL) == SOCKET_ERROR)
        {
            
if(WSAGetLastError() != WSA_IO_PENDING)    
            {
                
// 返回WSA_IO_PENDING是正常情况,表示IO操作正在进行,不能立即完成
                
// 如果不是WSA_IO_PENDING错误,就表示操作失败了
                AfxMessageBox("错误:第一次投递Recv操作失败!!此套接字将被关闭!");
                closesocket(sockAcceptArray[nSockIndex]);
                sockAcceptArray[nSockIndex] 
= INVALID_SOCKET;

                WSACloseEvent(EventArray[nSockIndex]);

                
continue;
            }
        }

        dwEventTotal 
++;

        
if(dwEventTotal == 1)                          // 此时如果dwEventTotal加以后等于1 
                                                       
// 就说明_OverlappedThread休眠了,此时唤醒之
            pOverlappedThread->ResumeThread();       
    }

    
return 0;
}
ContractedBlock.gif ExpandedBlockStart.gif 重叠IO线程
//重叠I/O处理线程
UINT _OverlappedThread(LPVOID lParam)
{
    CServerSocket
* pServer = (CServerSocket*)lParam;

    
while(bOverlapped)                    // 循环检测事件数组中的事件,并对接收的数据进行处理:)
    {
        DWORD dwIndex;

        
// 等候重叠I/O调用结束
        
// 因为我们把事件和Overlapped绑定在一起,重叠操作完成后我们会接到事件通知
        dwIndex = WSAWaitForMultipleEvents(dwEventTotal,EventArray,FALSE,10,FALSE);

        
if(dwIndex == WSA_WAIT_TIMEOUT)
            
continue;

        
if(dwIndex == WSA_WAIT_FAILED)  // 出现监听错误
        {
            
int nErrorCode = WSAGetLastError();

            
if(nErrorCode == WSA_INVALID_HANDLE)
            {
                AfxMessageBox(
"监听出现错误:无效的 lphEvents 参数!");   // 代码经常会出现这种错误,我实在没时间改了,55555555
            }
            
else
                
if(nErrorCode == WSA_INVALID_PARAMETER)
                {
                    AfxMessageBox(
"监听出现错误:无效的 CEvents 参数!");
                }

            
continue;
        }

        
// 取得索引值,得知事件的索引号
        dwIndex = dwIndex - WSA_WAIT_EVENT_0;

        
// 获得索引号以后,这个事件已经没有利用价值,重置之
        WSAResetEvent(EventArray[dwIndex]);

        
// 然后确定当前索引号的SOCKET的重叠请求状态
        DWORD dwBytesTransferred;
        WSAOVERLAPPED
& CurrentOverlapped = AcceptOverlapped[dwIndex]; // 这里纯粹为了减少代码长度,付给了一个临时变量
        SOCKET& sockCurrent = sockAcceptArray[dwIndex] ;  // 同上

        WSAGetOverlappedResult(sockCurrent,
&CurrentOverlapped ,
            
&dwBytesTransferred,FALSE,&Flags);

        
// 先检查通信对方是否已经关闭连接
        
// 如果==0则表示连接已经,则关闭套接字
        if(dwBytesTransferred == 0)
        {
            CString strSock;
            strSock.Format(
"SOCKET编号:%d",sockAcceptArray[dwIndex] );
            ::SendMessage(pServer
->m_hNotifyWnd,WM_MSG_NEW_SOCKET,
                (LPARAM)(LPCTSTR)strSock,(LPARAM)(LPCTSTR)
"客户端断开连接!");

            closesocket(sockCurrent);
            sockCurrent 
= INVALID_SOCKET;
            
//WSACloseEvent( EventArray[dwIndex] );

            dwEventTotal
--;
            
if(dwEventTotal <= 0)                  // 如果没有事件等待则暂停重叠IO处理线程
            {

                pOverlappedThread
->SuspendThread();  // 没有需要处理的重叠结构了,则本线程休眠
            }
            
continue;
        }


        
// DataBuf中包含接收到的数据,我们发到对话框中给予显示
        CString strSock;
        strSock.Format(
"SOCKET编号:%d",sockCurrent);
        ::SendMessage(pServer
->m_hNotifyWnd,WM_MSG_NEW_MSG,
            (LPARAM)(LPCTSTR)strSock,(LPARAM)(LPCTSTR)DataBuf[dwIndex].buf);


        
// 然后在套接字上投递另一个WSARecv请求(不要晕,注意看,代码和前面第一次WSARecv一样^_^)
        Flags = 0;
        ZeroMemory(
&CurrentOverlapped,sizeof(WSAOVERLAPPED));

        
char buffer[DATA_BUFSIZE];
        ZeroMemory(buffer,DATA_BUFSIZE);

        CurrentOverlapped.hEvent 
= EventArray[dwIndex];
        DataBuf[dwIndex].len 
= DATA_BUFSIZE;
        DataBuf[dwIndex].buf 
= buffer;

        
// 开始另外一个WSARecv
        if(WSARecv(sockCurrent,&DataBuf[dwIndex],1,&dwRecvBytes,&Flags,
            
&CurrentOverlapped ,NULL) == SOCKET_ERROR)
        {
            
if(WSAGetLastError() != WSA_IO_PENDING) 
            {
                AfxMessageBox(
"错误:投递Recv操作失败!!此套接字将被关闭!");

                closesocket(sockCurrent);
                sockCurrent 
= INVALID_SOCKET;

                WSACloseEvent(EventArray[dwIndex]);

                dwEventTotal
--;

                
continue;
            }
        }
    }

    
return 0;
}

 

 

 

ContractedBlock.gif ExpandedBlockStart.gif 完成例程-监听模型
///
//
//    Purposes:    系统自动调用的回调函数
//
//                在我们投递的WSARecv操作完成的时候,系统会自动调用这个函数    
//

void CALLBACK CompletionRoutine(DWORD Error,
                                DWORD BytesTransfered,
                                LPWSAOVERLAPPED Overlapped,
                                DWORD inFlags)
{
    TRACE(
"回调CompletionRoutinedot.gifdot.gif");

    nCurSockIndex 
= GetCurrentSocketIndex(Overlapped);             // 根据传入的重叠结构,
                                                                   
// 来寻找究竟是哪个SOCKET上触发了事件

    
//
    //    错误处理:可能是对方关闭套接字,或者发生一个严重错误
    if(Error != 0 || BytesTransfered == 0)                         
    {
        ReleaseSocket(nCurSockIndex);

        
return;
    }

    TRACE(
"数据:");
    TRACE(DataBuf[nCurSockIndex].buf);

    
//
    //    程序执行到这里,说明我们先前投递的WSARecv操作已经胜利完成了!!!^_^
    
//    DataBuf结构里面就有客户端传来的数据了!!^_^

    CServerSocket::ShowMessage(nCurSockIndex,CString(DataBuf[nCurSockIndex].buf));
        
    
return;
}

//
//
//    Purposes:    监听端口,接收连入的连接
//

UINT _ServerListenThread(LPVOID lParam)
{
    TRACE(
"服务器端监听中dot.gif..\n");

    CServerSocket
* pServer = (CServerSocket*)lParam;

    SOCKADDR_IN ClientAddr;                                  
// 定义一个客户端得地址结构作为参数
    int addr_length=sizeof(ClientAddr);

    
while(TRUE)
    {
        SOCKET sockTemp 
= accept(                            // 接收连入的客户端
            sockListen,
            (SOCKADDR
*)&ClientAddr,
            
&addr_length
            ); 

        
if(sockTemp  == INVALID_SOCKET)
        {
            AfxMessageBox(
"Accept Connection failed!");
            
            
continue;
        }

        nSockIndex 
= pServer->GetEmptySocket();               // 获得一个空闲的SOCKET索引号

        sockArray[nSockIndex] 
= sockTemp;

        
// 这里可以取得客户端的IP和端口,但是我们只取其中的SOCKET编号
        
//LPCTSTR lpIP =  inet_ntoa(ClientAddr.sin_addr);     // IP
        
//UINT nPort = ntohs(ClientAddr.sin_port);            // PORT

        CServerSocket::ShowMessage(nSockIndex,
"客户端建立连接!");

        nSockTotal
++;                                         // SOCKET总数加一

        bNewSocket 
= TRUE;                                    // 标志投递一个新的WSARecv请求

        
if(nSockTotal == 1)                                   // SOCKET数量不为空时激活处理线程
            pWaitForCompletionThread->ResumeThread();
    }

    
return 0;
}
ContractedBlock.gif ExpandedBlockStart.gif 完成例程-I/O线程
///
// 
//    Purposes:    用于投递第一个WSARecv请求,并等待系统完成的通知,然后继续投递后续的请求
//
////
UINT _WaitForCompletionThread(LPVOID lParam)
{
    TRACE(
"开始等待线程dot.gif.\n");

    EventArray[
0= WSACreateEvent();                        // 建立一个事件

    DWORD dwRecvBytes 
= 0,                                   // WSARecv的参数
          Flags = 0;

    
while(TRUE)
    {
        
if(bNewSocket)                                       // 如果标志为True,则表示投递第一个WSARecv请求!
        {
            bNewSocket 
= FALSE;

        
//************************************************************************
        
//
        
//                现在开始投递第一个WSARecv请求! 
        
//
        
//************************************************************************

            Flags 
= 0;

            ZeroMemory(
&AcceptOverlapped[nSockIndex],sizeof(WSAOVERLAPPED));

            
char buffer[DATA_BUFSIZE];
            ZeroMemory(buffer,DATA_BUFSIZE);

            DataBuf[nSockIndex].len 
= DATA_BUFSIZE;
            DataBuf[nSockIndex].buf 
= buffer;

            
// 将WSAOVERLAPPED结构指定为一个参数,在套接字上投递一个异步WSARecv()请求
            
// 并提供下面的作为完成例程的CompletionRoutine回调函数
            if(WSARecv(
                sockArray[nSockIndex],
                
&DataBuf[nSockIndex],
                
1,
                
&dwRecvBytes,
                
&Flags,
                
&AcceptOverlapped[nSockIndex],
                CompletionRoutine) 
== SOCKET_ERROR)
            {
                
if(WSAGetLastError() != WSA_IO_PENDING)
                {
                    ReleaseSocket(nSockIndex);

                    
continue;
                }
            }
        }

        
////
        // 等待重叠请求完成,自动回调完成例程函数
        DWORD dwIndex = WSAWaitForMultipleEvents(1,EventArray,FALSE,10,TRUE);


        
///
        // 返回WAIT_IO_COMPLETION表示一个重叠请求完成例程例结束。继续为更多的完成例程服务
        if(dwIndex == WAIT_IO_COMPLETION)
        {
            
            TRACE(
"重叠操作完成dot.gif\n");

            
//************************************************************************
            
//
            
//                现在开始投递后续的WSARecv请求! 
            
//
            
//**********************************************************************

            
// 前一个完成例程结束以后,开始在此套接字上投递下一个WSARecv,代码和前面的一模一样^_^

            
if(nCurSockIndex != NULLSOCKET)                          // 这个nCurSockIndex来自于前面完成例程得到的那个
            {
                Flags 
= 0;

                ZeroMemory(
&AcceptOverlapped[nCurSockIndex],sizeof(WSAOVERLAPPED));

                
char buffer[DATA_BUFSIZE];
                ZeroMemory(buffer,DATA_BUFSIZE);

                DataBuf[nCurSockIndex].len 
= DATA_BUFSIZE;
                DataBuf[nCurSockIndex].buf 
= buffer;

                
/
                // 将WSAOVERLAPPED结构制定为一个参数,在套接字上投递一个异步WSARecv()请求
                
// 并提供下面作为完成例程的CompletionRoutine回调函数
                if(WSARecv(
                    sockArray[nCurSockIndex],
                    
&DataBuf[nCurSockIndex],
                    
1,
                    
&dwRecvBytes,
                    
&Flags,
                    
&AcceptOverlapped[nCurSockIndex],
                    CompletionRoutine                                
// 注意这个回调函数
                    ) == SOCKET_ERROR)
                {
                    
if(WSAGetLastError() != WSA_IO_PENDING)          // 出现错误,关闭SOCKET
                    {
                        ReleaseSocket(nCurSockIndex);

                        
continue;
                    }
                }

                
// 这里最好添加一个步骤,去掉数组中无效的SOCKET
                
// 因为非正常关闭的客户端,比如拔掉网线等,这里是不会接到通知的
            }

            
continue;
        }
        
else  
        {
            
if(dwIndex == WAIT_TIMEOUT)                              // 继续等待
                continue;
            
else                                                     // 如果出现其他错误就坏事了 
            { 
                AfxMessageBox(
"_WaitForCompletionThread 发生异常,线程将退出!");
                
break;
            }
        }
    }

    
return 0;
}

转载于:https://www.cnblogs.com/dubingsky/archive/2009/07/15/1524067.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值