重叠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操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成
例子
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之类的也就可关闭。
例子
DWORD dwBytesTransferred;
WSAGetOverlappedResult( AcceptSocket, AcceptOverlapped ,
&dwBytesTransferred, FALSE, &Flags);
// 先检查通信对方是否已经关闭连接
// 如果==0则表示连接已经,则关闭套接字
if(dwBytesTransferred == 0)
{
closesocket(AcceptSocket);
WSACloseEvent(EventArray[dwIndex]); // 关闭事件
return;
}
原文链接:http://blog.csdn.net/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
代码
共同部分-监听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;
}
监听线程
BlockingModel监听线程
UINT _ServerListenThread(LPVOID lParam)
{
TRACE("服务器端监听中
..\n");
SOCKADDR_IN ClientAddr; // 定义一个客户端得地址结构作为参数
int addr_length=sizeof(ClientAddr);
CServerSocket* psvSock = (CServerSocket*)lParam;
::SendMessage(psvSock->m_DlgWnd,WM_MSG_NEWMSG,0,(LPARAM)(LPCSTR)"开始监听
.");
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;
}
事件选择模型-监听线程
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;
}
重叠模型-监听线程
UINT _ServerListenThread(LPVOID lParam)
{
TRACE("服务器端监听中
..\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;
}
重叠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;
}
完成例程-监听模型
///////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 系统自动调用的回调函数
//
// 在我们投递的WSARecv操作完成的时候,系统会自动调用这个函数
//
////////////////////////////////////////////////////////////////////////////////////
void CALLBACK CompletionRoutine(DWORD Error,
DWORD BytesTransfered,
LPWSAOVERLAPPED Overlapped,
DWORD inFlags)
{
TRACE("回调CompletionRoutine![](http://img.e-com-net.com/image/product/63e4968cd4604d6bbd0e10ee8f20c8a5.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("服务器端监听中
..\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;
}
完成例程-I/O线程
///////////////////////////////////////////////////////////////////////////////////////////
//
// Purposes: 用于投递第一个WSARecv请求,并等待系统完成的通知,然后继续投递后续的请求
//
////////////////////////////////////////////////////////////////////////////////////////////
UINT _WaitForCompletionThread(LPVOID lParam)
{
TRACE("开始等待线程
.\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("重叠操作完成
\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;
}