博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个简单而又灵活的IOCP模块——完成端口通讯服务器(IOCP Socket Server)设计(四)
阅读量:2180 次
发布时间:2019-05-01

本文共 4211 字,大约阅读时间需要 14 分钟。

转载自 

完成端口通讯服务器(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请求”。

你可能感兴趣的文章
【虫师】【selenium】参数化
查看>>
【Python练习】文件引用用户名密码登录系统
查看>>
学习网站汇总
查看>>
【Python】用Python打开csv和xml文件
查看>>
【Loadrunner】性能测试报告实战
查看>>
【自动化测试】自动化测试需要了解的的一些事情。
查看>>
【selenium】selenium ide的安装过程
查看>>
【手机自动化测试】monkey测试
查看>>
【英语】软件开发常用英语词汇
查看>>
Fiddler 抓包工具总结
查看>>
【雅思】雅思需要购买和准备的学习资料
查看>>
【雅思】雅思写作作业(1)
查看>>
【雅思】【大作文】【审题作业】关于同不同意的审题作业(重点)
查看>>
【Loadrunner】通过loadrunner录制时候有事件但是白页无法出来登录页怎么办?
查看>>
【English】【托业】【四六级】写译高频词汇
查看>>
【托业】【新东方全真模拟】01~02-----P5~6
查看>>
【托业】【新东方全真模拟】03~04-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST05~06-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST09~10-----P5~6
查看>>
【托业】【新东方托业全真模拟】TEST07~08-----P5~6
查看>>