现在的位置: 首页 > 综合 > 正文

手把手教你玩转SOCKET模型之重叠I/O篇(下)

2012年12月11日 ⁄ 综合 ⁄ 共 4088字 ⁄ 字号 评论关闭

四。     实现重叠模型的步骤

作了这么多的准备工作,费了这么多的笔墨,我们终于可以开始着手编码了。其实慢慢的你就会明白,要想透析重叠结构的内部原理也许是要费点功夫,但是只是学会如何来使用它,却是真的不难,唯一需要理清思路的地方就是和大量的客户端交互的情况下,我们得到事件通知以后,如何得知是哪一个重叠操作完成了,继而知道究竟该对哪一个套接字进行处理,应该去哪个缓冲区中的取得数据,everything will be OK^_^

下面我们配合代码,来一步步的讲解如何亲手完成一个重叠模型。

第一步定义变量…………

#define DATA_BUFSIZE     4096          // 接收缓冲区大小

SOCKET         ListenSocket,             // 监听套接字

AcceptSocket;             // 与客户端通信的套接字

WSAOVERLAPPED  AcceptOverlapped;     // 重叠结构一个

WSAEVENT  EventArray[WSA_MAXIMUM_WAIT_EVENTS];  

// 用来通知重叠操作完成的事件句柄数组

WSABUF     DataBuf[DATA_BUFSIZE] ;      

DWORD     dwEventTotal = 0,            // 程序中事件的总数

             dwRecvBytes = 0,            // 接收到的字符长度

                   Flags = 0;                    // WSARecv的参数

 

 

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

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

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);

 

bind(ListenSocket,(LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)); // 绑定套接字

 

listen(ListenSocket, 5);                                   //开始监听

 

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

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

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

 AcceptSocket = accept (ListenSocket, NULL,NULL) ; 

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

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重叠结构,并且象前面讲到的那样,为这个重叠结构从事件句柄数组里挑出一个空闲的对象句柄“绑定”上去。

// 创建一个事件

// dwEventTotal可以暂时先作为Event数组的索引

EventArray[dwEventTotal] = WSACreateEvent();      

 

ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));      // 置零

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

 

char buffer[DATA_BUFSIZE];

ZeroMemory(buffer, DATA_BUFSIZE);

DataBuf.len = DATA_BUFSIZE;

DataBuf.buf = buffer;                          // 初始化一个WSABUF结构

dwEventTotal ++;                              // 总数加一

 

【第五步】以WSAOVERLAPPED结构为参数,在套接字上投递WSARecv请求

各个变量都已经初始化OK以后,我们就可以开始Socket操作了,然后让WSAOVERLAPPED结构来替我们管理I/O 请求,我们只用等待事件的触发就OK了。

if(WSARecv(AcceptSocket ,&DataBuf,1,&dwRecvBytes,&Flags,

                                        & AcceptOverlapped, NULL) == SOCKET_ERROR)

{ 

   // 返回WSA_IO_PENDING是正常情况,表示IO操作正在进行,不能立即完成

   // 如果不是WSA_IO_PENDING错误,就大事不好了~~~~~~!!!

      if(WSAGetLastError() != WSA_IO_PENDING)    

      {

                 // 那就只能关闭大吉了

                         closesocket(AcceptSocket);

                         WSACloseEvent(EventArray[dwEventTotal]);

         }

}

 

【第六步】 WSAWaitForMultipleEvents函数等待重叠操作返回的结果

  我们前面已经给WSARecv关联的重叠结构赋了一个事件对象句柄,所以我们这里要等待事件对象的触发与之配合,而且需要根据WSAWaitForMultipleEvents函数的返回值来确定究竟事件数组中的哪一个事件被触发了,这个函数的用法及返回值请参考前面的基础知识部分。

DWORD dwIndex;

// 等候重叠I/O调用结束

// 因为我们把事件和Overlapped绑定在一起,重叠操作完成后我们会接到事件通知

dwIndex = WSAWaitForMultipleEvents(dwEventTotal, 

EventArray ,FALSE ,WSA_INFINITE,FALSE);

// 注意这里返回的Index并非是事件在数组里的Index,而是需要减去WSA_WAIT_EVENT_0

dwIndex = dwIndex – WSA_WAIT_EVENT_0;

 

【第七步】使用WSAResetEvent函数重设当前这个用完的事件对象

事件已经被触发了之后,它对于我们来说已经没有利用价值了,所以要将它重置一下留待下一次使用,很简单,就一步,连返回值都不用考虑

WSAResetEvent(EventArray[dwIndex]);

 

【第八步】使用WSAGetOverlappedResult函数取得重叠调用的返回状态

  这是我们最关心的事情,费了那么大劲投递的这个重叠操作究竟是个什么结果呢?其实对于本模型来说,唯一需要检查一下的就是对方的Socket连接是否已经关闭了

DWORD dwBytesTransferred;

WSAGetOverlappedResult( AcceptSocket, AcceptOverlapped ,

&dwBytesTransferred, FALSE, &Flags);

// 先检查通信对方是否已经关闭连接

// 如果==0则表示连接已经,则关闭套接字

if(dwBytesTransferred == 0)

{

         closesocket(AcceptSocket);

      WSACloseEvent(EventArray[dwIndex]);    // 关闭事件

         return;

}

 

【第九步】“享受”接收到的数据

如果程序执行到了这里,那么就说明一切正常,WSABUF结构里面就存有我们WSARecv来的数据了,终于到了尽情享用成果的时候了!喝杯茶,休息一下吧~~~^_^

DataBuf.buf就是一个char*字符串指针,听凭你的处理吧,我就不多说了

 

【第十步】同第五步一样,在套接字上继续投递WSARecv请求,重复步骤 6 ~ 9

 这样一路作下来,我们终于可以从客户端接收到数据了,但是回想起来,呀~~~~~,这样岂不是只能收到一次数据,然后程序不就Over了?…….-_-b  所以我们接下来不得不重复一遍第四步和第五步的工作,再次在这个套接字上投递另一个WSARecv请求,并且使整个过程循环起来,are u clear??

     大家可以参考我的代码,在这里就先不写了,因为各位都一定比我smart

抱歉!评论已关闭.