重叠(overlapped)I/O模型


 重叠I/O模型是异步IO模型。  先简单说明下 同步IO和异步IO操作,


同步IO模型: 当一个线程启动一个I/O操作时,进入等待状态,等到IO操作完成结束后,才继续执行。

异步IO模型,当一个线程启动一个I/O时,通常会将I/O处理和某一内核对象绑定,I/O函数会立即返回,不会等待,等到系统处理完此I/O操作时,通知绑定的内核对象,I/O处理结束了。


下面的代买是 windows 网络与通信程序设计 书中的,笔者简单的测试了下,简单了解下重叠Io模型的操作

initsock.h :

#include <WinSock2.h>
#include <mswsock.h>
#pragma comment(lib,"WS2_32")
class CInitSock
{
public:
	CInitSock(BYTE minorVer = 2,BYTE majorVer = 2)
	{
		//初始化ws2_32.dll
		WSADATA wsaData;
		WORD sockVersion = MAKEWORD(minorVer, majorVer);
		if(WSAStartup(sockVersion, &wsaData) != 0)
		{
			exit(0);
		}
	}
	~CInitSock()
	{
		WSACleanup();
	}
};

代码:

#include "../initsock.h"
#include <stdio.h>
#include <Windows.h>
CInitSock initSock;
#define  PORT 4567
#define BUFFER_SIZE 1024

typedef struct _SOCKET_OBJ
{
	SOCKET s;   //套接字句柄;
	int nOutstandingOps;  //记录次套接字上重叠I/O的数量;
	LPFN_ACCEPTEX lpfnAcceptEx;  //扩展函数AcceptEx的指针(仅对监听套接字而言);
}SOCKET_OBJ,*PSOCKET_OBJ;

//缓冲区对象结构,记录重叠I/O的所有属性;
typedef struct _BUFFER_OBJ
{
	OVERLAPPED ol;  //重叠结构;
	char *buff;   //send,recv,acceptex所使用的缓冲区;
	int nLen; //buff 的长度;
	PSOCKET_OBJ pSocket; //此I/O所属的套接字对象;
	int nOperation;   //提交的操作类型;
#define OP_ACCEPT  1;
#define OP_READ    2;
#define OP_WRITE   3;
	SOCKET sAccept;   //用来保存AcceptEx接受客户端的套接字(仅对监听套接字而言);
	_BUFFER_OBJ *pNext;
}BUFFER_OBJ,*PBUFFER_OBJ;

HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS]; //I/O事件句柄数组;
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->nOutstandingOps != 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;
		//将新的BUFFER_OBJ添加到列表中;
		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)
{
	//从列表中移除BUFFER_OBJ对象;
	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);
	}
}
//在缓冲区中查找BUFFER_OBJ对象;
PBUFFER_OBJ FindBufferObj(HANDLE hEvent)
{
	PBUFFER_OBJ pBuffer = g_pBufferHead;
	while (pBuffer != NULL)
	{
		if(pBuffer->ol.hEvent == hEvent)
			break;
		pBuffer = pBuffer->pNext;
	}
	return pBuffer;
}
//更新句柄数组g_events中的内容;
void RebuildArrary()
{
	PBUFFER_OBJ pBuffer = g_pBufferHead;
	int i = 1;
	while(pBuffer != NULL)
	{
		g_events[i++] = pBuffer->ol.hEvent;
		pBuffer = pBuffer->pNext;
	}
}

//接受连接
BOOL PostAccept(PBUFFER_OBJ pBuffer)
{
	PSOCKET_OBJ pSocket = pBuffer->pSocket;
	if(pSocket->lpfnAcceptEx != NULL)
	{
		//设置IO类型,增加套接字上的技术
		pBuffer->nOperation = OP_ACCEPT;
		pSocket->nOutstandingOps++;
		//投递此重叠IO
		DWORD dwBytes;
		pBuffer->sAccept = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
		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)
{
	//设置IO类型,增加套接字上重叠IO计数
	pBuffer->nOperation = OP_READ;
	pBuffer->pSocket->nOutstandingOps++;
	//投递此重叠IO
	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++;
	//投递次IO
	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 dwFlag;
	BOOL bRet = WSAGetOverlappedResult(pSocket->s,
									&pBuffer->ol,
									&dwTrans,
									FALSE,
									&dwFlag);
	if(!bRet)
	{
		if(pSocket->s == INVALID_SOCKET)
		{
			closesocket(pSocket->s);
			pSocket->s = INVALID_SOCKET;
		}
		if(pSocket->nOutstandingOps == 0)
		{
			FreeSocketObj(pSocket);
		}
		FreeBufferObj(pBuffer);
		return FALSE;
	}
	//没有错误发生
	//处理已完成的IO
	switch (pBuffer->nOperation)
	{
	case 1:    //接到一个新的连接,并受到对方发来的第一个封包op_accept
		{

			//为新客户端创建一个SOCKET_OBJ 对象
			PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept);
			//为发送的数据创建一个BUFFER_OBJ的对象,这个对象会在套接字出错或者关闭时释放
			PBUFFER_OBJ pSend = GetBufferObj(pClient,BUFFER_SIZE);
			if(pSend == NULL)
			{
				printf("too much connections\n");
				FreeSocketObj(pClient);
				return FALSE;
			}
			RebuildArrary();
			//将数据复制到发送缓冲区
			pSend->nLen = dwTrans;
			memcpy(pSend->buff,pBuffer->buff,dwTrans);
			//投递IO,将数据发送给客户端
			if (!PostSend(pSend))
			{
				//如果出错,释放上面的两个对象
				FreeBufferObj(pSend);
				FreeSocketObj(pClient);
				return FALSE;
			}
			printf("one connect coming!\n ");
			PostAccept(pBuffer);
			break;
		}
	case 2:    //接收数据完成 op_read
		{
			if(dwTrans>0)
			{
				//创建一个缓冲区,以发送数据,这里使用原来的缓冲区
				PBUFFER_OBJ pSend = pBuffer;  //将原来的和数据发回去
				pSend->nLen = dwTrans;
				//投递发送Io(将数据同步回显给客户端)
				PostSend(pSend);
			}
			else
			{
					//关闭套接字,以便次套接字上的投递的其他的IO也返回
				if(pSocket->s != INVALID_SOCKET)
				{
					closesocket(pSocket->s);
					pSocket->s = INVALID_SOCKET;
				}
				if(pSocket->nOutstandingOps == 0)
				{
					FreeSocketObj(pSocket);
				}
				FreeBufferObj(pBuffer);
				return FALSE;
			}
			break;
		}
	case 3:   //发送数据完成 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;
}
//主函数
int main()
{
	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(PORT);
	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));
	}
	while(1)
	{
		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<=g_nBufferCount; 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)
				{
					RebuildArrary();
					continue;
				}
				//处理这个IO
				PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);
				if(pBuffer != NULL)
				{
					if(!HandleIo(pBuffer))
						RebuildArrary();
				}
			}
		}
	}

	return 1;
}


PS:  对于程序中使用两层循环调用 WSAWaitForMultipleEvents()函数,不太明白,网上搜索了下资料,解释如下:

第一个WSAWaitForMultipleEvents是等待事件对象Signal,如果同时有多个事件对象受信,WSAWaitForMultipleEvents函数的返回值也只能指明其中的一个,就事件句柄数组中最前面的那个。如果这个指明的事件对象总有网络事件发生,那么后面的其他事件对象所关联的网络事件就得不到及时的处理。因此WSAWaitForMultipleEvents函数返回以后,对每个事件对象再次调用WSAWaitForMultipleEvents函数,以确定其状态。


多个内核对象被触发时,WaitForMultipleObjects选择其中序号最小的返回。而WaitForMultipleObjects它只会改变使它返回的那个内核对象的状态。
这儿又会产生一个问题,如果序号最小的那个对象频繁被触发,那么序号比它大的内核对象将得不到被处理的机会。
为了解决这一问题,可以采用双WaitForMultipleObjects检测机制来实现。



 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值