WINDOWS重叠IO模型

转载 2013年12月03日 09:06:55

一. 重叠IO模型简介

重叠IO的核心实际上就是一个重叠的数据结构。应用程序在单个套接字上投递一个或者多个IO操作,当IO操作完成时对应的重叠数据结构中的事件对象会受信,相应的应用程序通过查事件对象可以得到通知。就这样,通过重叠的数据结构将异步的IO和程序连接起来了。

   重叠数据结构:

typedef struct _OVERLAPPED{

      DWORDInternal;

      DWORDInternalHigh;

      DWORDOffset;

      DWORDOffsetHigh;

      HANDLEhEvent;//核心的参数,这个就是连接异步IO和应用程序的桥梁

}OVERLAPPED, *LPOVERLAPPED;

二.重叠IO模型的优点

    1.可以运行在支持winsock2的所有windows平台上(IOCP只能运行在NT平台上)

    2.比起阻塞,select,WSAAsyncSelect以及WSAEventSelect等模型提供了更好的系统级性能。

    3.使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据直接    被拷贝到投递的缓冲区。(这一点和以上四种模型是截然不同的)

    4. 从《windows网络编程》中提供的试验结果中可以看到,在使用了P4 1.7G Xero处理器(CPU很强啊)以及768MB的回应服务器中,最大可以处理4万多个SOCKET连接,在处理1万2千个连接的时候CPU占用率才40% 左右 ―― 非常好的性能,已经直逼完成端口。

三。重叠IO机制的基本步骤

     1.要使用重叠数据结构我们就要使用新的IO函数(也就是send,recv,sendto,recvfrom要被WSASend(),WSARecv(),WSASendto(),WSARecvfrom(),WSAAccept()代替)。

     2.将WINDOWS事件对象与重叠数据结构相关联

     3.使用1中说的函数在套接字上投递IO请求

     4.不断的查询与重叠数据结构关联在一起的事件对象

    5.获得IO结果,处理结果。

四。废话少说直接上代码

     注意:这里是一份单线程的代码,所以它做多同时支持64个socket连接,如需更多的连接请采用线程池。

 

#include "initsock.h"
#include <Mswsock.h>
#include "stdio.h"
#include <windows.h>


CInitSock theSock;  //主要用来初始化socket库
#define BUFFER_SIZE 1024


//这里是与套接字相关的信息
typedef struct _SOCKET_OBJ
{
	SOCKET s;
	int nOutstandingOps;  //此套接字上重叠IO的数量
	LPFN_ACCEPTEX lpfnAcceptEx;  //AcceptEx()函数的指针(仅对于监听套接字而言)
}SOCKET_OBJ,*PSOCKET_OBJ;

typedef struct _BUFFER_OBJ
{
	OVERLAPPED ol;  //重叠的数据结构
	char* buff;   //投递IO请求时使用的缓冲区结构
	int nLen;    //缓冲区结构的长度

	PSOCKET_OBJ   pSocket;  //SOCKET结构
	int nOperation;  //提交的操作类型
#define OP_ACCEPT  1
#define OP_READ    2
#define OP_WRITE   3
	SOCKET   sAccept;   //客户端套接字,仅对于监听套接字而言
	_BUFFER_OBJ *pNext;

}BUFFER_OBJ,*PBUFFER_OBJ;

HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS];
int g_nBufferCount;
PBUFFER_OBJ g_pBufferHead,g_pBufferTail;

//申请套接字结构
PSOCKET_OBJ GetSocketObj(SOCKET s)
{
	PSOCKET_OBJ pSocket=(PSOCKET_OBJ)::GlobalAlloc(GPTR,sizeof(SOCKET_OBJ));
	if(pSocket!=NULL)
		pSocket->s=s;
	return pSocket;
}
void FreeSocketObj(PSOCKET_OBJ pSocket)
{
	if(pSocket->s!=INVALID_SOCKET)
		::closesocket(pSocket->s);
	::GlobalFree(pSocket);
}

PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket,ULONG nLen)
{
	if(g_nBufferCount>WSA_MAXIMUM_WAIT_EVENTS-1)
		return NULL;
	PBUFFER_OBJ pBuffer=(PBUFFER_OBJ)::GlobalAlloc(GPTR,sizeof(BUFFER_OBJ));
	if(pBuffer!=NULL)
	{
		pBuffer->buff=(char*)::GlobalAlloc(GPTR,nLen);
		pBuffer->ol.hEvent=WSACreateEvent();
		pBuffer->pSocket=pSocket;
		pBuffer->sAccept=INVALID_SOCKET;

		if(g_pBufferHead==NULL)
		{
			g_pBufferHead=g_pBufferTail=pBuffer;
		}
		else
		{
			g_pBufferTail->pNext=pBuffer;
			g_pBufferTail=pBuffer;
		}
		g_events[++g_nBufferCount]=pBuffer->ol.hEvent;
	}
	return pBuffer;
}

void FreeBufferObj(PBUFFER_OBJ pBuffer)
{
	PBUFFER_OBJ pTest=g_pBufferHead;
	BOOL bFind=FALSE;
	if(pTest==pBuffer)  //这边难道不会造成内存泄露吗?
	{
		g_pBufferHead=g_pBufferTail=NULL;
		bFind=TRUE;
	}
	else
	{
		while(pTest!=NULL&&pTest->pNext!=pBuffer)
		{
			pTest=pTest->pNext;
		}
		if(pTest!=NULL)
		{
			pTest->pNext=pBuffer->pNext;
			if(pTest->pNext==NULL)
				g_pBufferTail=pTest;
			bFind=TRUE;
		}
	}
	if(bFind)
	{
		g_nBufferCount--;
		::CloseHandle(pBuffer->ol.hEvent);
		::GlobalFree(pBuffer->buff);
		::GlobalFree(pBuffer);
	}
}

void RebuildArray()
{
	PBUFFER_OBJ pBuffer = g_pBufferHead;
	int i =  1;
	while(pBuffer != NULL)
	{
		g_events[i++] = pBuffer->ol.hEvent;
		pBuffer = pBuffer->pNext;
	}
}
PBUFFER_OBJ FindBufferObj(HANDLE hEvent)
{
	PBUFFER_OBJ pBuffer=g_pBufferHead;
	while(pBuffer!=NULL)
	{
		if(pBuffer->ol.hEvent==hEvent)
			break;
		pBuffer=pBuffer->pNext;
	}
	return pBuffer;
}


 

*/
BOOL PostAccept(PBUFFER_OBJ pBuffer)
{
	PSOCKET_OBJ pSocket=pBuffer->pSocket;
	if(pSocket->lpfnAcceptEx!=NULL)
	{
		pBuffer->nOperation=OP_ACCEPT;
		pSocket->nOutstandingOps++;

		//投递此IO请求
		DWORD dwBytes;
		pBuffer->sAccept=::WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);


 

                //<span style="color: rgb(255, 102, 102);">注意:AcceptEx()如果提供了第四个参数,则这个函数会一直等到得到第一块数据才会返回。
</span>		BOOL b=pSocket->lpfnAcceptEx(pSocket->s,pBuffer->sAccept,pBuffer->buff,BUFFER_SIZE-((sizeof(sockaddr_in)+16)*2),sizeof(sockaddr_in)+16,
			sizeof(sockaddr_in)+16,&dwBytes,&pBuffer->ol);
		if(!b)
		{
			if(::WSAGetLastError()!=WSA_IO_PENDING)
				return false;
		}
		return true;
	}
	return false;
}
BOOL PostRecv(PBUFFER_OBJ pBuffer)
{
	pBuffer->nOperation=OP_READ;
	pBuffer->pSocket->nOutstandingOps++;

	DWORD dwBytes;
	DWORD dwFlags=0;
	WSABUF buf;
	buf.buf=pBuffer->buff;
	buf.len=pBuffer->nLen;
	if(::WSARecv(pBuffer->pSocket->s,&buf,1,&dwBytes,&dwFlags,&pBuffer->ol,NULL)!=NO_ERROR)
	{
		if(::WSAGetLastError()!=WSA_IO_PENDING)
			return false;
	}
	return true;
}
BOOL PostSend(PBUFFER_OBJ pBuffer)
{
	pBuffer->nOperation=OP_WRITE;
	pBuffer->pSocket->nOutstandingOps++;

	DWORD dwBytes;
	DWORD dwFlags=0;
	WSABUF buf;
	buf.buf=pBuffer->buff;
	buf.len=pBuffer->nLen;
	if(::WSASend(pBuffer->pSocket->s,&buf,1,&dwBytes,dwFlags,&pBuffer->ol,NULL)!=NO_ERROR)
	{
		if(::WSAGetLastError()!=WSA_IO_PENDING)
			return false;
	}
	return true;
}

//最重要的函数:IO处理函数

BOOL HandleIO(PBUFFER_OBJ pBuffer)
{
	PSOCKET_OBJ pSocket=pBuffer->pSocket;
	pSocket->nOutstandingOps--;


	DWORD dwTrans;
	DWORD dwFlags;
	BOOL Ret=::WSAGetOverlappedResult(pBuffer->pSocket->s,&pBuffer->ol,&dwTrans,FALSE,&dwFlags);
	if(!Ret)
	{
		if(pSocket->s!=INVALID_SOCKET)
		{
			::closesocket(pSocket->s);
			pSocket->s=INVALID_SOCKET;
		}
		if(pSocket->nOutstandingOps==0)
			FreeSocketObj(pSocket);
		FreeBufferObj(pBuffer);
		return FALSE;
	}

	switch(pBuffer->nOperation)
	{
	case OP_ACCEPT:

		{
			PSOCKET_OBJ pClient=GetSocketObj(pBuffer->sAccept);
			PBUFFER_OBJ pSend=GetBufferObj(pClient,BUFFER_SIZE);
			if(pSend==NULL)
			{
				printf("Too much  connections!");
				FreeSocketObj(pClient);
				return FALSE;
			}
			RebuildArray();
			pSend->nLen=dwTrans;
			memcpy(pSend->buff,pBuffer->buff,dwTrans);


			if(!PostSend(pSend))
			{
				FreeSocketObj(pClient);
				FreeBufferObj(pSend);
				return FALSE;
			}
			PostAccept(pBuffer);
		}
		break;
	case OP_READ:
		if(dwTrans>0)
		{
			PBUFFER_OBJ pSend=pBuffer;
			pBuffer->nLen=dwTrans;
			PostSend(pSend);
		}
		else
		{
			if(pSocket->s!=INVALID_SOCKET)
			{
				::closesocket(pSocket->s);
				pSocket->s=INVALID_SOCKET;
			}
			if(pSocket->nOutstandingOps==0)
				FreeSocketObj(pSocket);
			FreeBufferObj(pBuffer);
			return FALSE;
		}
		break;
	case OP_WRITE:
		if(dwTrans>0)
		{
			pBuffer->nLen=BUFFER_SIZE;
			PostRecv(pBuffer);
		}
		else
		{
			if(pSocket->s!=INVALID_SOCKET)
			{
				::closesocket(pSocket->s);
				pSocket->s=INVALID_SOCKET;
			}
			if(pSocket->nOutstandingOps==0)
				FreeSocketObj(pSocket);
			FreeBufferObj(pBuffer);
			return FALSE;
		}
		break;

	}
	return TRUE;
}
void main()
{
	// 创建监听套节字,绑定到本地端口,进入监听模式
	int nPort = 4567;
	SOCKET sListen = 
		::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	SOCKADDR_IN si;
	si.sin_family = AF_INET;
	si.sin_port = ::ntohs(nPort);
	si.sin_addr.S_un.S_addr = INADDR_ANY;
	::bind(sListen, (sockaddr*)&si, sizeof(si));
	::listen(sListen, 200);

	// 为监听套节字创建一个SOCKET_OBJ对象
	PSOCKET_OBJ pListen = GetSocketObj(sListen);

	// 加载扩展函数AcceptEx
	GUID GuidAcceptEx = WSAID_ACCEPTEX;
	DWORD dwBytes;
	WSAIoctl(pListen->s, 
		SIO_GET_EXTENSION_FUNCTION_POINTER, 
		&GuidAcceptEx, 
		sizeof(GuidAcceptEx),
		&pListen->lpfnAcceptEx, 
		sizeof(pListen->lpfnAcceptEx), 
		&dwBytes, 
		NULL, 
		NULL);

	// 创建用来重新建立g_events数组的事件对象
	g_events[0] = ::WSACreateEvent();

	// 在此可以投递多个接受I/O请求
	for(int i=0; i<5; i++)
	{
		PostAccept(GetBufferObj(pListen, BUFFER_SIZE));
	}
	::WSASetEvent(g_events[0]);
	
	while(TRUE)
	{
		int nIndex = 
			::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);
		if(nIndex == WSA_WAIT_FAILED)
		{
			printf("WSAWaitForMultipleEvents() failed \n");
			break;
		}
		nIndex = nIndex - WSA_WAIT_EVENT_0;
		for(int i=0; i<=nIndex; i++)
		{
			int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);
			if(nRet == WSA_WAIT_TIMEOUT)
				continue;
			else
			{
				::WSAResetEvent(g_events[i]);
				// 重新建立g_events数组
				if(i == 0)
				{
					RebuildArray();
					continue;
				}

				// 处理这个I/O
				PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);
				if(pBuffer != NULL)
				{
					if(!HandleIO(pBuffer))
						RebuildArray();
				}
			}
		}
	}
}


 

windows Socket编程之重叠IO模型

上一篇文章我们讲了EventSelect网络模型,它已经解决了等待数据到来的这一大部分时间,但是它还有一小部分时间没有节省下来。那就是把数据从网卡的缓冲区拷贝到我们应用程序的缓冲区里边。而这一篇的重叠...
  • Timmiy
  • Timmiy
  • 2016年08月14日 15:17
  • 2302

基于事件的重叠IO模型

Windows socket重叠IO模型开发。        利用套接字重叠IO模型,应用程序能一次投递一个或多个IO请求,当系统完成IO操作后通知应用程序。该模型以win32异步IO机制为基...
  • rankun1
  • rankun1
  • 2015年12月19日 21:35
  • 1453

Socket编程模型之重叠IO(Overlapped I/O)模型

Winsock2的发布使得Socket I/O有了和文件I/O统一的接口。我们可以通过使用Win32文件操纵函数ReadFile和WriteFile来进行Socket I/O。伴随而来的,用于普通文件...
  • caoshiying
  • caoshiying
  • 2016年10月20日 20:23
  • 1804

【整理】重叠IO模型的编程思路及C++源代码

编程步骤 1、创建一个套接字,开始在指定的端口上监听连接请求。 2、接收一个入站的连接请求。 3、为接受的套接字创建新的WSAOVERLAPPED结构,并分配事件对象句柄。 4、以WSAOVE...
  • brk1985
  • brk1985
  • 2014年01月24日 16:08
  • 616

重叠IO之完成例程的实现方法

目录: 1.完成例程的优点 2.完成例程的基本原理 3.关于完成例程的函数介绍 4.完成例程的实现步骤 5.实际应用中应该进一步完善的地方     一.        完成例程的优点 ...
  • w52770567
  • w52770567
  • 2012年03月28日 11:31
  • 918

关于重叠io完成例程中的sleepEx()

写完成例程demo时发现一个问题,在这里记录一下。 对完成例程的介绍中,一般书上还有网上的资料代码都会说在投递完成例程后要调用SleepEx()或者类似的函数使线程处于一种警觉的等待状态,这...
  • shandongmachao
  • shandongmachao
  • 2015年09月24日 18:03
  • 436

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

手把手教你玩转SOCKET模型之重叠I/O篇 “身为一个初学者,时常能体味到初学者入门的艰辛,所以总是想抽空作点什么来尽我所能的帮助那些需要帮助的人。我也希望大家能把自己的所学和他人一起分享,不要去鄙...
  • PiggyXP
  • PiggyXP
  • 2004年09月23日 22:39
  • 45493

基于完成例程的重叠I/O网络模型

详细解释了网络重叠I/O模型中的完成例程,对相关难以理解的APi进行释义,附有详尽的代码。不足之处望大家多多指正,以促进共同进步...
  • qq_30145355
  • qq_30145355
  • 2017年11月01日 02:12
  • 113

C++服务器重叠I/O+事件通告模型

#include #include #include #include using namespace std; #pragma comment(lib,"Ws2_32.lib") #pragma ...
  • riyuedangkong1
  • riyuedangkong1
  • 2016年06月22日 21:14
  • 930

Windows五种IO模型性能分析

重叠I/O模型的另外几个优点在于,微软针对重叠I/O模型提供了一些特有的扩展函数。当使用重叠I/O模型时,可以选择使用不同的完成通知方式。  采用事件对象通知的重叠I/O模型是不可伸缩的,因为针对发出...
  • jay900323
  • jay900323
  • 2014年01月11日 15:38
  • 5186
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:WINDOWS重叠IO模型
举报原因:
原因补充:

(最多只允许输入30个字)