网络编程学习3:IOCP模型

IOCP模型

IOCP(I/O completion port,I/O完成端口)是伸缩性最好的一种I/O模型。
I/O完成端口是应用程序使用线程池处理异步I/O请求的一种机制。处理多个并发异步I/O请求时,使用I/O完成端口比在I/O请求时创建线程更快更有效。
I/O完成端口最初的设计时应用程序发出一些异步I/O请求,当这些请求完成时,设备驱动将把这些工作项目排序到完成端口,这样,在完成端口上等待的线程池便可以处理这些完成I/O。
完成端口实际上时一个Windows I/O结构,它可以接收多个对象的句柄,如文件对象、套接字对象等。

1 接口

// 创建完成端口对象
// 使用完成端口模型,首先要调用CreateIoCompletionPort函数创建一个完成端口对象,Winsock将使用这个对象为任意数量的套接字句柄管理I/O请求。
HANDLE CreateIoCompletionPort(
	HANDLE FileHandle,
	HANLDE ExistingCompletionPort,
	ULONG_PRT CompletionKey,
	DWORD NumberOfConcurrentThreads
);
// 函数功能:
// 1:创建一个完成端口对象
// 2:将一个或者多个文件句柄关联到I/O完成端口对象
HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
// 获取完成端口的消息队列中的完成通知封包
// 向完成端口关联套接字句柄之后,便可以通过在套接字上投递重叠发送和接收请求处理I/O了。在这些I/O操作完成时,I/O系统会向完成端口对象发送一个完成通知封包。I/O完成端口以先进先出的方式为这些封包排队。应用程序使用GetQueuedCompletionStatus函数可以取得这些队列中的封包。这个函数应该在处理完成对象I/O的服务线程中调用。
BOOL GetQueuedCompletionStatus(
	HANDLE CompletionPort,             // 完成端口对象句柄
	LPDWORD lpNumberOfBytes,           // 取得I/O操作期间传输的字节数
	PULONG_PTR lpCompletionKey,        // 取得在关联套接字时指定的句柄唯一数据
	LPOVERLAPPED * lpOverlapped,       // 取得投递I/O操作时指定的OVERLAPPED结构
	DWORD dwMilliseconds               // 如果完成端口没有完成而封包,此参数指定了等待的事件,INFINITE为无穷大。
);
// 注: I/O服务线程调用 GetQueuedCompletionStatus函数取得有事件发生的套接字的信息,通过lpNumberOfBytes参数得到传输的字节数量,通过lpCompletionKey参数得到捆绑到完成端口的套接字的唯一(per-handle)句柄数据,通过lpOverlapped参数得到投递I/O请求时使用的重叠对象地址,进一步得到I/O唯一(per-I/O)数据。
// 这些参数中,最重要的时per-handle数据和per-I/O数据。
// lpCompletionKey参数包含了我们称为per-handle的数据,因为当套接字第一次与完成端口关联时,这个数据就关联到了一个套接字句柄。这是传递给CreateIoCompletionPort函数的CompletionKey参数。如前所述,可以给这个参数传递任何类型的数据。
// lpOverlapped参数指向一个OVERLAPPED结构,结构后面便是我们称为per-I/O的数据,这可以是工作线程处理完成封包时想要知道的任何信息。

2 实例

// 初始化Winsock库
CInitSock theSock;
#define BUFFER_SIZE 1024
typedef struct _PER_HANDLE_DATA       // per_handle数据
{
	SOCKET s;                         // 对应的套接字句柄
	sockaddr_in addr;                 // 客户方地址
}PER_HANDLE_DATA, *PPER_HANDLE_DATA;
typedef struct _PER_IO_DATA           // per-I/O数据
{
	OVERLAPPED ol;                    // 重叠结构
	char buf[BUFFER_SIZE];            // 数据缓冲区
	int nOperationType;               // 操作类型
#define OP_READ   1
#define OP_WRITE  2
#define OP_ACCEPT 3
}PER_IO_DATA, *PPER_IO_DATA;
//注:主线程首先创建完成端口对象,创建工作线程处理完成端口对象中的事件;然后再创建监听套接字,开始监听服务端口;接下来便进入无限循环,处理到来的连接请求,这个过程如下:
// 1:调用accept函数等待接收未决的连接请求
// 2: 接受到新连接之后,为它创建一个per-handle数据,并将它们关联到完成端口对象。
// 3:再新接受的套接字上投递一个接收请求。这个I/O完成之后,由工作线程负责处理。
void main()
{
	int nPort = 4567;
	// 创建完成端口对象,创建工作线程处理完成端口对象中事件
	HANLDE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
	::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0);
	// 创建监听套接字,绑定到本地地址,开始监听
	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN si;
	si.sin_family = AF_INET;
	si.sin_port = ::ntos(nPort);
	si.sin_addr.S_un.S_addr = INADDR_ANY;
	::bind(sListen, (sockaddr*)&si, sizeof(si));
	::listen(sListen, 5);
	// 循环处理到来的连接
	while(TRUE)
	{
		// 等待接受未决的连接请求
		SOCKADDR_IN saRemote;
		int nRemoteLen = sizeof(saRemote);
		SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);
		// 接受到新连接之后,为它创建一个per-handle数据,并将它们关联到完成端口对象
		PPER_HANDLE_DATA pPerHandle = 
		(PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
		pPerHandle->s = sNew;
		memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);
		::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (DWORD)pPerHandle, 0);
		// 投递一个接收请求
		PPER_IO_DATA pPerIO = (PPER_IO_DATA)::GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
		pPerIO->nOperationType = OP_READ;
		WSABUF buf;
		buf.buf = pPerIO->buf;
		buf.len = BUFFER_SIZE;
		DWORD dwRecv;
		DWORD dwFlags = 0;
		::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIO->ol, NULL);
	}
}
// I/O服务线程循环调用GetQueuedCompletionStatus函数从I/O完成端口移除完成的I/O封包,然后根据封包的类型进行处理。
DWORD WINAPI ServerThread(LPVOID lpParam)
{
	// 得到完成端口对象句柄
	HANDLE hCompletion = (HANDLE)lpParam;
	DWORD dwTrans;
	PPER_HANDLE_DATA pPerHandle;
	PPER_IO_DATA pPerIO;
	
	while(TRUE)
	{
		// 在关联到此完成端口的所有套接字上等待I/O完成
		BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, &dwTrans, (LPDWORD)&pPerHandle, (LPOVERLAPPED*)&pPerIO, WSA_INFINITE);
		if(!bOK){             // 在此套接字上有错误发生
			::closesocket(pPerHandle->s);
			::GlobalFree(pPerHandle);
			::GlobalFree(pPerIO);
			continue;
		}
		
		// 套接字被对方关闭
		if(dwTrans == 0 && 
			(pPerIO->nOperation == OP_READ || pPerIO->nOperationType == OP_WRITE)){
			::closesocket(pPerHandle->s);
			::GlobalFree(pPerHandle);
			::GlobalFree(pPerIO);
			continue;
		}
		
		// 通过per-I/O数据中的nOperationType域查看什么I/O请求完成了
		switch(pPerIO->nOperationType){
			case OP_READ:             // 完成一个接收请求
			{
				pPerIO->buf[dwTrans] = '\0';
				printf(pPerIO->buf);
				// 继续投递接收I/O请求
				WSABUF buf;
				buf.buf = pPerIO->buf;
				buf.len = BUFFER_SIZE;
				pPerIO->nOperationType = OP_READ;
				DWORD nFlags = 0;
				::WSARecv(pPerHandle->s, &buf, 1, &dwTrans, &nFlags, &pPerIO->ol, NULL);
			}
			break;
			case OP_WRITE:  // 本例中没有投递这些类型的I/O请求
			case OP_ACCEPT:
				break;
		}
	}
	return 0;
}
///////////////////////////////////////////////////////////////// // 初始化Socket bool CIOCPModel::_InitializeListenSocket() { // AcceptEx 和 GetAcceptExSockaddrs 的GUID,用于导出函数指针 GUID GuidAcceptEx = WSAID_ACCEPTEX; GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; // 服务器地址信息,用于绑定Socket struct sockaddr_in ServerAddress; // 生成用于监听的Socket的信息 m_pListenContext = new PER_SOCKET_CONTEXT; // 需要使用重叠IO,必须得使用WSASocket来建立Socket,才可以支持重叠IO操作 m_pListenContext->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (INVALID_SOCKET == m_pListenContext->m_Socket) { this->_ShowMessage("初始化Socket失败,错误代码: %d.\n", WSAGetLastError()); return false; } else { TRACE("WSASocket() 完成.\n"); } // 将Listen Socket绑定至完成端口中 if( NULL== CreateIoCompletionPort( (HANDLE)m_pListenContext->m_Socket, m_hIOCompletionPort,(DWORD)m_pListenContext, 0)) { this->_ShowMessage("绑定 Listen Socket至完成端口失败!错误代码: %d/n", WSAGetLastError()); RELEASE_SOCKET( m_pListenContext->m_Socket ); return false; } else { TRACE("Listen Socket绑定完成端口 完成.\n"); } // 填充地址信息 ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress)); ServerAddress.sin_family = AF_INET; // 这里可以绑定任何可用的IP地址,或者绑定一个指定的IP地址 //ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY); ServerAddress.sin_addr.s_addr = inet_addr(m_strIP.GetString()); ServerAddress.sin_port = htons(m_nPort); // 绑定地址和端口 if (SOCKET_ERROR == bind(m_pListenContext->m_Socket, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress))) { this->_ShowMessage("bind()函数执行错误.\n"); return false; } else { TRACE("bind() 完成.\n"); } // 开始进行监听 if (SOCKET_ERROR == listen(m_pListenContext->m_Socket,SOMAXCONN)) { this->_ShowMessage("Listen()函数执行出现错误.\n"); return false; } else { TRACE("Listen() 完成.\n"); } // 使用AcceptEx函数,因为这个是属于WinSock2规范之外的微软另外提供的扩展函数 // 所以需要额外获取一下函数的指针, // 获取AcceptEx函数指针 DWORD dwBytes = 0; if(SOCKET_ERROR == WSAIoctl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值