一个简单而又灵活的IOCP模块——完成端口通讯服务器(IOCP Socket Server)设计(四)

转载自 http://blog.csdn.net/guestcode/article/details/4529644


完成端口通讯服务器(IOCP Socket Server)设计

(四)一个简单而又灵活的IOCP模块

Copyright © 2009 代码客(卢益贵)版权所有

QQ:48092788   源码博客:http://blog.csdn.net/guestcode

 

本文对部分IOCP不再多做重复的说明,阅读本文应该对IOCP有一定的了解(本篇也并未包括异步socket)。

 

(一)、四个IOCP的API

1、创建完成端口:

HANDLE WINAPI CreateIoCompletionPort(
  __in          HANDLE FileHandle,
  __in          HANDLE ExistingCompletionPort,
  __in          ULONG_PTR CompletionKey,
  __in          DWORD NumberOfConcurrentThreads
);

2、关联完成端口

HANDLE WINAPI CreateIoCompletionPort(
  __in          HANDLE FileHandle,
  __in          HANDLE ExistingCompletionPort,
  __in          ULONG_PTR CompletionKey,
  __in          DWORD NumberOfConcurrentThreads
);

3、获取队列完成状态

BOOL WINAPI GetQueuedCompletionStatus(
  __in          HANDLE CompletionPort,
  __out         LPDWORD lpNumberOfBytes,
  __out         PULONG_PTR lpCompletionKey,
  __out         LPOVERLAPPED* lpOverlapped,
  __in          DWORD dwMilliseconds
);

4、投递一个队列完成状态

BOOL WINAPI PostQueuedCompletionStatus(
  __in          HANDLE CompletionPort,
  __in          DWORD dwNumberOfBytesTransferred,
  __in          ULONG_PTR dwCompletionKey,
  __in          LPOVERLAPPED lpOverlapped
);

创建和关联完成端口是同一个函数仅是参数传递不一样而已,有关其他参数这里不多重复,请参阅MSDN。


(二)两个关键的参数

1、dwCompletionKey

在这里,本人把这个完成键扩展为如下定义:

函数指针定义:

typedef  void(*PFN_ON_GIOCP_ERROR)(void* pCompletionKey, void* pOverlapped);
typedef  void(*PFN_ON_GIOCP_OPER)(DWORD dwBytes, void* pCompletionKey, void* pOverlapped);

完成键结构定义:

typedef struct _COMPLETION_KEY
{
PFN_ON_GIOCP_OPER           pfnOnIocpOper;
PFN_ON_GIOCP_ERROR          pfnOnIocpError;
}GIOCP_COMPLETION_KEY, *PGIOCP_COMPLETION_KEY;

在其他Io操作的时候,满足这个既定格式的,可以在这个数据结构基础之上进行扩展。

 

2、lpOverlapped

本模块尚未用到,但在以后的异步Socket里面是Io操作的关键。

 

(三)工作线程源码

typedef struct _GWORKER
{
_GWORKER*          pNext;
DWORD              dwThreadId;
DWORD              dwRunCount;  //记录工作线程循环次数,为监视窗口提供数据
HANDLE             hFinished;   //表示工作线程已经结束
void               *pData;      //每个工作线程独立拥有的数据项
}GWORKER, *PGWORKER;
 
DWORD WINAPI GIocpWorkerThread(PGWORKER pWorker)
/*说明:工作线程函数
**输入:被创建的工作者的结构指针
**输出:*/
{
     BOOL bResult;
     DWORD dwBytes;
     PGIOCP_COMPLETION_KEY pCompletionKey;
     LPOVERLAPPED pOverlapped;
 
//调用工作线程开始工作的的回调函数,以便创建每个线程独立拥有的会话,比如数据库连接会话,//并使用GIocp_SetWorkerData设置该工作线程独立关联的数据项,比如数据库连接类的指针
     pfnOnGIocpWorkerThreadBegin((DWORD)pWorker);
//死循环标签,也可以使用for(;;)
Loop:
     //等待完成端口事件
     bResult = GetQueuedCompletionStatus(hGIocpCompletionPort, &dwBytes, (PULONG_PTR)&pCompletionKey, &pOverlapped, INFINITE);
     //工作线程计数器累加,表示工作线程活动计数
     pWorker->dwRunCount++;
     //如果完成键是空,表示要结束工作线程
     if(!pCompletionKey)
         goto End;
     //等待完成事件失败
     if(bResult)
         //调用读写处理函数
         pCompletionKey->pfnOnIocpOper(dwBytes, pCompletionKey, pOverlapped);
     else
         //调用错误处理函数
         pCompletionKey->pfnOnIocpError(pCompletionKey, pOverlapped);
     //继续等待完成事件
     goto Loop;
End:
     //结束了,调用回调处理函数,比如摧毁数据库库会话对象,
     pfnOnGIocpWorkerThreadEnd((DWORD)pWorker);
     //设置结束标志
     SetEvent(pWorker->hFinished);
 
     return(0);
}

从这个工作线程代码来看,它做到了与Io操作结果的无关性,它不知道你是读操作还是写操作的返回,或者是AcceptEx的返回等等。它看上去似乎非常简单,也正因为这样它的扩展性是相当强大的。如果TcpServer功能模块的操作和这个完成端口句柄关联,就可以实现一个面向连接的服务器;如果TcpClient功能模块的操作和这个完成端口句柄关联,就可以实现一个完成端口的客户端,如何与TcpServer功能模块一起使用,即可实现集群服务器之间的通讯;如果UDP功能模块的操作和这个完成端口句柄关联,即可实现完成端口的P2P通讯,如果和TcpServer功能模块一起使用,即可实现双协议服务器

(四)工作线程的不确定性

当我们创建一定数量的工作线程的时候,这个IOCP的多线程机制和我们常规的多线程机制有着很大的区别。比如说使用PostQueuedCompletionStatus投递一个完成事件,我们无法预知是由哪个线程来处理,这个和常规的线程WaitForSingleObject有着本质区别(SetEvent可以指定某个线程来响应)。并且多个线程的工作率是不平衡,甚至会出现有的工作线程忙得要死有的却闲得要死的现象,即使你设置了并发线程数量(NumberOfConcurrentThreads)足够大(当然在多核或多CPU情况下),还是会出现工作量不平衡的现象。

如下图(单核):

(五)结束工作线程

先看下面代码:

PGWORKER pWorker;
 
     //给每个工作者线程抛出结束事件
     pWorker = pGIocpWorkerHead;
     while(pWorker)
     {
         PostQueuedCompletionStatus(hGIocpCompletionPort, 0, 0, NULL);
         pWorker = pWorker->pNext;
     }
    
     //等待每个工作线程都结束
     pWorker = pGIocpWorkerHead;
     while(pWorker)
     {
         WaitForSingleObject(pWorker->hFinished, INFINITE);
         CloseHandle(pWorker->hFinished);
         pWorker = pWorker->pNext;
     }
由PostQueuedCompletionStatus抛出一个假完成事件,完成键和重叠结构都是空的,工作线程会这样判断结束:
if(!pCompletionKey)
     goto End;

并且注意:有多少个工作线程,就调用多少个PostQueuedCompletionStatus,确保工作线程都结束了(WaitForSingleObject),才能释放Worker占用的内存。

另外,在调用PostQueuedCompletionStatus结束工作线程之前一定确保没有“未决的Io请求”。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值