重叠模型的基本设计原理是让应用程序使用重叠的数据结构,一次投递一个或多个WinsockI/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。模型的总体设计以Windows重叠I/O机制为基础。这个机制可通过ReadFile和WriteFile两个函数,在设备上执行I/O操作。 要想在一个套接字上使用重叠I/O模型,首先必须创建一个设置了重叠标志的套接字。 主要有两种方法来管理重叠I/O的请求。1.事件对象通知 2.完成实例。 事件通知: 重叠I/O的事件通知方法要求将Windows事件对象与WSAOVERLAPPED结构关联在一起。若使用一个WSAOVERLAPPED结构,发出像WSASend和WSARecv这样的I/O调用,它们会立即返回。 WSAOVERLAPPED结构为重叠I/O请求的初始化及其后续的完成之间提供了一种通信媒介。结构的定义如下: typedef struct WSAOVERLAPPED { DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; WSAEVENT hEvent; }WSAOVERLAPPED, FAR* LPWSAOVERLAPPED; Internal,InternalHigh,Offset,OffsetHigh字段均由系统在内部使用,不能有应用程序直接进行处理或使用。hEvent字段则允许应用程序将事件对象的句柄同操作关联起来。 一个重叠I/O完成以后,应用程序要负责获取重叠I/O操作的结果。一个重叠请求操作最终完成之后,在事件通知方法中,Winsock会更改与WSAOVERLAPPED结构关联的事件对象的事件传信状态,将未传信变成已传信。由于已经有一个事件对象分配给WSAOVERLAPPED结构,所有只需简单的调用WSAWaitForMultipleEvents函数,便可判断出重叠I/O调用将在什么时候完成。WSAWaitForMultipleEvents会等待一段指定时间,等待一个或多个事件进入已传信状态。 WSAWaitForMultipleEvents一次只能等待64个事件对象。确定某个重叠事件完成以后,接着需要调用WSAGetOverlappedResult函数,判断这个重叠调用是否成功。 BOOL WSAGetOverlappedResult( SOCKET s, //重叠操作开始的时候,被指定的套接字 LPWSAOVERLAPPED lpOverlapped, //重叠操作开始的时候,被指定的WSAOVERLAPPED结构 LPDWORD lpcbTransfer,//负责接收一次重叠发送或接收操作实际传输的字节数 BOOL fWait,//用于决定函数是否应该等待挂起的重叠操作完成 LPWORD lpdwFlags //负责接收结果标志 ); 若WSAGetOverlappedResult函数调用成功,返回值就是TRUE,意味着重叠操作完成成功,而且lpcbTransfer参数所指向的值已进行了更新,若返回FALSE,那么可能是由以下原因造成的: 1.重叠I/O操作仍处于挂起状态 2.重叠操作已经完成,但含有错误 3.因为在提供给WSAGetOverlappedResult函数的一个或多个参数中存在错误,所有无法判断重叠操作的完成状态 失败后,lpcbTransfer所指向的值不会被更新,而且应用程序应调用WSAGetLastError函数查看错误原因。 利用事件通知机制设计一个简单的服务器应用程序,令其在一个套接字上对重叠I/O操作进行管理: #define DATA_BUFSIZE 2046 void main(void) { WSABUF DataBuf; char buffer[DATA_BUFSIZE]; DWORD EventTotal = 0; DWORD RecvBytes = 0; DWORD Flags = 0; WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; WSAOVERLAPPED AcceptOverlapped; SOCKET ListenSocket, AcceptSocket; //第一步 //启动Winsock,建立监听套接字 ... //第二步 //接收一个入站连接 AcceptSocket = accept(ListenSocket,NULL,NULL); //第三步 //建立一个重叠结构 EventArray[EventTotal] = WSACreateEvent(); ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED)); AcceptOverlapped.hEvent = EventArray[EventTotal]; DataBuf.len = DATA_BUFSIZE; DataBuf.buf = buffer; EventTotal++; //第四步 //接收一个WSARecv请求,以便在套接字上接收数据 if(SOCKET_ERROR == WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL)) { if(WSA_IO_PENDING != WSAGetLastError()) { //出错 } } //处理套件子上的重叠接收 while(TRUE) { DWORD Index; //第五步 //等候重叠I/O调用结束 Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE); //索引应为0,因为EventArray中仅有一个事件 //第六步 //重置已传信事件 WSAResetEvent(EventArray[Index-WSA_WAIT_EVENT_0]); //第七步 //确定重叠请求的状态 WSAGetOverlappedResult(AcceptSocket,&AcceptOverlapped,&BytesTransferred,FALSE,&Flags); //先 检查看通信对方是否已经关闭连接,如果关闭,则关闭套接字 if(BytesTransferred==0) { printf("Closing socket %d ", AcceptSocket); closesocket(AcceptSocket); WSACloseEvent(EventArray[Index-WSA_WAIT_EVENT_0]); return ; } //对接收到的数据进行某种处理 //DataBuf包含接收到的数据 ... //第八步 //在套接字上投递另一个WSARecv请求 Flags = 0; ZeroMemory(&AccpetOverlapped, sizeof(WSAOVERLAPPED)); AcceptOverlapped.hEvent = EventArray[Index-WSA_WAIT_EVENT_0]; DataBuf.len = DATA_BUFSIZE; DataBuf.buf = buffer; if(SOCKET_ERROR == WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL)) { if(WSA_IO_PENDING != WSAGetLastError()) { //出错 } } } } 对该程序采用的编程步骤总结如下: 1.创建一个套接字,开始在指定的端口上监听连接请求 2.接受一个入站的连接请求 3.为接收的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将该事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents使用。 4.将WSAOVERLAPPED指定为参数,在套接字上投递一个异步WSARecv请求 5.使用步骤3的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入已传信状态 6.使用WSAGetOverlappedResult函数,判断重叠调用的返回状态 7.函数完成后,针对重叠数组,调用WSAResetEvent函数,从而重设事件对象,并对完成的重叠请求进行处理 8.在套接字上投递另一个重叠WSARecv请求 9.重复步骤5~8 这个例子极易扩展,从而提供对多个套接字的支持。方法是将代码的重叠I/O处理部分移至一个对立的线程中,让主应用程序线程为额外的连接请求提供服务。 =================================================================== #include<winsock2.h> #include<stdio.h> #pragma comment(lib,"ws2_32.lib"); #define PORT 5050 #define MSGSIZE 1024 typedef struct { WSAOVERLAPPED overlap; WSABUF Buffer; char szMessage[MSGSIZE]; DWORD NumberOfBytesRecvd; DWORD Flags; }PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA; int g_iTotalConn = 0; SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS]; WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS]; LPPER_IO_OPERATION_DATA g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS]; DWORD WINAPI WorkerThread(LPVOID lpParam); void Cleanup(int index); int main() { WSADATA wsaData; SOCKET sListen, sClient; SOCKADDR_IN client, local; DWORD dwThreadId; int iAddrSize = sizeof(SOCKADDR_IN); WSAStartup(MAKEWORD(2,2), &wsaData); sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); memset(&local, 0, sizeof(SOCKADDR_IN)); local.sin_family = AF_INET; local.sin_port = htons(PORT); local.sin_addr.s_addr = htonl(INADDR_ANY); bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN)); listen(sListen, 5); CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId); while(TRUE) { sClient = accept(sListen, (SOCKADDR*)&client, &iAddrSize); printf("Accepted Client:%s:%d ", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); g_CliSocketArr[g_iTotalConn] = sClient; g_pPerIoDataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_OPERATION_DATA) ); g_pPerIoDataArr[g_iTotalConn]->Buffer.len = MSGSIZE; g_pPerIoDataArr[g_iTotalConn]->Buffer.buf = g_pPerIoDataArr[g_iTotalConn]->szMessage; g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent(); g_CliEventArr[g_iTotalConn] = g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent; WSARecv(g_CliSocketArr[g_iTotalConn], &g_pPerIoDataArr[g_iTotalConn]->Buffer, 1, &g_pPerIoDataArr[g_iTotalConn]->NumberOfBytesRecvd, &g_pPerIoDataArr[g_iTotalConn]->Flags, &g_pPerIoDataArr[g_iTotalConn]->overlap, NULL); g_iTotalConn++; } closesocket(sListen); WSACleanup(); return 0; } DWORD WINAPI WorkerThread(LPVOID lpParam) { int ret, index; DWORD cbTransferred; while(TRUE) { ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE); if(ret==WSA_WAIT_FAILED || ret==WSA_WAIT_TIMEOUT) { //如果当前没有客户端的话,要sleep一下,要不然CUP会占50%以上 if(g_iTotalConn==0) Sleep(1000); continue; } index = ret - WSA_WAIT_EVENT_0; WSAResetEvent(g_CliEventArr[index]); WSAGetOverlappedResult(g_CliSocketArr[index], &g_pPerIoDataArr[index]->overlap, &cbTransferred, TRUE, &g_pPerIoDataArr[index]->Flags); if(cbTransferred==0) { Cleanup(index); } else { g_pPerIoDataArr[index]->szMessage[cbTransferred] = '