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

 

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

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

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

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

 

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

 

(一)、四个IOCPAPI

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

 

 

 

 

(二)两个关键的参数

1dwCompletionKey

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

函数指针定义:

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操作的时候,满足这个既定格式的,可以在这个数据结构基础之上进行扩展。

 

2lpOverlapped

本模块尚未用到,但在以后的异步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请求”。

 

 

你可能感兴趣的:(工作,socket,server,服务器,通讯,winapi)