IOCPWinSock2新函数打造高性能SOCKET池.docx

上传人:b****5 文档编号:6824979 上传时间:2023-01-10 格式:DOCX 页数:14 大小:23.52KB
下载 相关 举报
IOCPWinSock2新函数打造高性能SOCKET池.docx_第1页
第1页 / 共14页
IOCPWinSock2新函数打造高性能SOCKET池.docx_第2页
第2页 / 共14页
IOCPWinSock2新函数打造高性能SOCKET池.docx_第3页
第3页 / 共14页
IOCPWinSock2新函数打造高性能SOCKET池.docx_第4页
第4页 / 共14页
IOCPWinSock2新函数打造高性能SOCKET池.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

IOCPWinSock2新函数打造高性能SOCKET池.docx

《IOCPWinSock2新函数打造高性能SOCKET池.docx》由会员分享,可在线阅读,更多相关《IOCPWinSock2新函数打造高性能SOCKET池.docx(14页珍藏版)》请在冰豆网上搜索。

IOCPWinSock2新函数打造高性能SOCKET池.docx

IOCPWinSock2新函数打造高性能SOCKET池

IOCP+WinSock2新函数打造高性能SOCKET池

默认分类2010-03-1517:

47:

16阅读24评论1字号:

大中小

 

在前一篇文章《WinSock2编程之打造完整的SOCKET池》中,介绍了WinSock2的一些新函数,并重点详细介绍了什么是SOCKET池,有了这个概念,现在就接着展开更深入的讨论。

首先这里要重点重申一下就是,SOCKET池主要指的是使用面向连接的协议的情况下,最常用的就是需要管理大量的TCP连接的时候。

常见的就是Web服务器、FTP服务器等。

下面就分步骤的详细介绍如何最终实现SOCKET池。

 

一、WinSock2环境的初始化:

 

要使用WinSock2就需要先初始化Socket2.0的环境,不废话,上代码:

WSADATAwd={0};

intiError=WSAStartup(MAKEWORD(2,0,&wd;

if(0!

=iError

{//出现错误,最好跟踪看下错误码是多少

      returnFALSE;

}

if(LOBYTE(lpwsaData->wVersion!

=2

{//非2.0以上环境退出了事可能是可怜的WinCE系统

      WSACleanup(;

      returnFALSE;

}

最后再不使用WinSock之后都要记得调用一下WSACleanup(这个函数;

 

二、装载WinSock2函数:

 

上一篇文章中给出了一个装载WinSock2函数的类,这里分解介绍下装载的具体过程,要提醒的就是,凡是类里面演示了动态装载的函数,最好都像那样动态载入,然后再调用。

以免出现上网发帖跪求高手赐教为什么AcceptEx函数无法编译通过等问题。

看完这篇文章详细你不会再去发帖找答案了,呵呵呵,好了,上代码:

//定义一个好用的载入函数摘自CGRSMsSockFun类

BOOLLoadWSAFun(GUID&funGuid,void*&pFun

{//本函数利用参数返回函数指针

      DWORDdwBytes=0;

      pFun=NULL;

      //随便创建一个SOCKET供WSAIoctl使用并不一定要像下面这样创建

      SOCKETskTemp=:

:

WSASocket(AF_INET,

                    SOCK_STREAM,IPPROTO_TCP,NULL,

                    0,WSA_FLAG_OVERLAPPED;

      if(INVALID_SOCKET==skTemp

      {//通常表示没有正常的初始化WinSock环境

             returnFALSE;

      }

      :

:

WSAIoctl(skTemp,SIO_GET_EXTENSION_FUNCTION_POINTER,

                    &funGuid,sizeof(funGuid,&pFun,

                    sizeof(pFun,&dwBytes,NULL,NULL;

      :

:

closesocket(skTemp;

      returnNULL!

=pFun;

}

//演示如何动态载入AcceptEx函数

......

LPFN_ACCEPTEXpfnAcceptEx;//首先声明函数指针

GUIDGuidAcceptEx=WSAID_ACCEPTEX;

LoadWSAFun(GuidAcceptEx,(void*&pfnAcceptEx;//载入

......

//使用丰富的参数调用

......

pfnAcceptEx(sListenSocket,sAcceptSocket,lpOutputBuffer,

             dwReceiveDataLength,dwLocalAddressLength,dwRemoteAddressLength,

lpdwBytesReceived,lpOverlapped;

             //或者:

             SOCKETskAccept=:

:

WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,

                                                NULL,0,WSA_FLAG_OVERLAPPED;

             PVOIDpBuf=newBYTE[sizeof(sockaddr_in+16];

             pfnAcceptEx(skServer,skAccept,pBuf,

0,//将接收缓冲置为0,令AcceptEx直接返回,防止拒绝服务攻击

                    sizeof(sockaddr_in+16,sizeof(sockaddr_in+16,NULL,

                                                (LPOVERLAPPEDpAcceptOL;

......

      以上是一个简单的演示,如何动态载入一个WinSock2扩展函数,并调用之,其它函数的详细例子可以看前一篇文章中CGRSMsSockFun类的实现部分。

如果使用CGRSMsSockFun类的话当然更简单,像下面这样调用即可:

            CGRSMsSockFunMsSockFun;

            MsSockFun.AcceptEx(skServer,skAccept,pBuf,

0,//将接收缓冲置为0,令AcceptEx直接返回,防止拒绝服务攻击

                    sizeof(sockaddr_in+16,sizeof(sockaddr_in+16,NULL,

                    (LPOVERLAPPEDpAcceptOL;

如果要使用这个类,那么需要一些修改,主要是异常处理部分,自己注释掉,或者用其它异常代替掉即可,这个对于有基础的读者来说不是什么难事。

 

三、定义OVERLAPPED结构:

 

要想“IOCP”就要自定义OVERLAPPED,这是彻底玩转IOCP的不二法门,可以这么说:

“江湖上有多少种自定义的OVERLAPPED派生结构体,就有多少种IOCP的封装!

OVERLAPPED本身是WindowsIOCP机制内部需要的一个结构体,主要用于记录每个IO操作的“完成状态”,其内容对于调用者来说是没有意义的,但是很多时候我们把它当做一个“火车头”,因为它可以方便的把每个IO操作的相关数据简单的“从调用处运输到完成回调函数中”,这是一个非常有用的特性,哪么如何让这个火车头发挥运输的作用呢?

其实很简单:

让它成为一个自定义的更大结构体的第一个成员。

然后用强制类型转换,将自定义的结构体转换成OVERLAPPED指针即可。

当然不一定非要是新结构体的第一个成员,也可以是任何第n个成员,这时使用VC头文件中预定义的一个宏CONTAINING_RECORD再反转回来即可。

说到这里一些C++基础差一点的读者估计已经很头晕了,更不知道我再说什么,那么我就将好人做到底吧,来解释下这个来龙去脉。

首先就以我们将要使用的AcceptEx函数为例子看看它的原型吧(知道孙悟空的火眼金睛用来干嘛的吗?

就是用来看原型的,哈哈哈):

BOOLAcceptEx(

 __in         SOCKETsListenSocket,

 __in         SOCKETsAcceptSocket,

 __in         PVOIDlpOutputBuffer,

 __in         DWORDdwReceiveDataLength,

 __in         DWORDdwLocalAddressLength,

 __in         DWORDdwRemoteAddressLength,

 __out        LPDWORDlpdwBytesReceived,

 __in         LPOVERLAPPEDlpOverlapped

;

注意最后一个参数,是一个OVERLAPPED结构体的指针(LP的意思是LongPointer,即指向32位地址长指针,注意不是“老婆”拼音的缩写),本身这个参数的意思就是分配一块OVERLAPPED大小的内存,在IOCP调用方式下传递给AcceptEx函数用,调用者不用去关心里面的任何内容,而在完成过程中(很多时候是另一个线程中的事情了),通常调用GetQueuedCompletionStatus函数后,会再次得到这个指针,接着让我们也看看它的原型:

BOOLWINAPIGetQueuedCompletionStatus(

 __in         HANDLECompletionPort,

 __out        LPDWORDlpNumberOfBytes,

 __out        PULONG_PTRlpCompletionKey,

 __out        LPOVERLAPPED*lpOverlapped,

 __in         DWORDdwMilliseconds

;

注意这里的LPOVERLAPPED多了一个*变成了指针的指针,并且前面的说明很清楚Out!

很明白了吧,不明白就真的Out了。

这里就可以重新得到调用AcceptEx传入的LPOVERLAPPED指针,也就是得到了这个“火车头”,因为只是一个指针,并没有详细的限定能有多大,所以可以在火车头的后面放很多东西。

再仔细观察GetQueuedCompletionStatus函数的参数,会发现,这时只能知道一个IO操作结束了,但是究竟是哪个操作结束了,或者是哪个SOCKET句柄上的操作结束了,并没有办法知道。

通常这个信息非常重要,因为只有在IO操作实际完成之后才能释放发送或接收等操作的缓冲区。

这些信息可以定义成如下的一个扩展OVERLAPPED结构:

structMYOVERLAPPED

{

   OVERLAPPEDm_ol;          

   int                  m_iOpType;      

//操作类型0=AcceptEx1=DisconnectEx      2=ConnectEx3=WSARecv等等

   SOCKET        m_skServer;      //服务端SOCKET

   SOCKET       m_skClient;       //客户端SOCKET

   LPVOID         m_pBuf;           //本次IO操作的缓冲指针

   ......                                          //其它需要的信息

};

使用时:

MYOVERLAPPED*pMyOL=newMYOVERLAPPED;

ZeroMemory(pMyOL,sizeof(MYOVERLAPPED;

pMyOL->m_iOpType=0;       //AcceptEx操作

pMyOL->m_skServer=skServer;

pMyOL->m_skClient=skClient;

BYTE*pBuf=newBYTE[256];//一个缓冲

.................. //朝缓冲中写入东西

pMyOL->m_pBuf=pBuf;

...............//其它的代码

AcceptEx(skServer,skClient,pBuf,

0,//将接收缓冲置为0,令AcceptEx直接返回                     

256,256,NULL,(LPOVERLAPPEDpMyOL;//注意最后这个强制类型转换

 

      在完成过程回调线程函数中,这样使用:

 

UINTCALLBACKClient_IOCPThread(void*pParam

      {//IOCP线程函数

             .....................

             DWORDdwBytesTrans=0;

             DWORDdwPerData=0;

             LPOVERLAPPEDlpOverlapped=NULL;

             while(1

             {//又见死循环呵呵呵

                    BOOLbRet=GetQueuedCompletionStatus(

                           pThis->m_IOCP,&dwBytesTrans,&dwPerData,

                           &lpOverlapped,INFINITE;

                    if(NULL==lpOverlapped

                    {//没有真正的完成

                           SleepEx(20,TRUE;//故意置成可警告状态

                           continue;

                    }

                    //找回“火车头”以及后面的所有东西

                    MYOVERLAPPED* pOL=CONTAINING_RECORD(lpOverlapped

MYOVERLAPPED,m_ol;

                    switch(pOL->m_iOpType

{

case0:

//AcceptEx结束

{//有链接进来了SOCKET句柄就是pMyOL->m_skClient

   

}

break;

............................

}

........................

}//endwhile

........................

      }//endfun

 

至此,关于这个“火车头”如何使用,应该是看明白了,其实就是从函数传入,又由函数返回。

只不过其间可能已经转换了线程环境,是不同的线程了。

这里再补充一个AcceptEx容易被遗漏的一个细节问题,那就是在AcceptEx完成返回之后,如下在那个连入的客户端SOCKET上调用一下:

      intnRet=:

:

setsockopt(

             pOL->m_skClient,SOL_SOCKET,SO_UPDATE_ACCEPT_CONTEXT,

             (char*&pOL->m_skServer,sizeof(SOCKET;

这样才可以继续在这个代表客户端连接的pOL->m_skClient上继续调用WSARecv和WSASend。

另外,在AcceptEx完成之后,通常可以用:

LPSOCKADDRaddrHost=NULL;     //服务端地址

LPSOCKADDRaddrClient=NULL;    //客户端地址

intlenHost=0;

intlenClient=0;

GetAcceptExSockaddrs(

      pOL->m_pBuf,0,sizeof(sockaddr_in+16,sizeof(sockaddr_in+16,

      (LPSOCKADDR*&addrHost,&lenHost,(LPSOCKADDR*&addrClient,&lenClient;

这样来得到连入的客户端地址,以及连入的服务端地址,通常这个地址可以和这个客户端的SOCKET绑定在一起用map或hash表保存,方便查询,就不用再调用那个getpeername得到客户端的地址了。

要注意的是GetAcceptExSockaddrs也是一个WinSock2扩展函数,专门配合AcceptEx使用的,需要像AcceptEx那样动态载入一下,然后再调用,详情请见前一篇文章中的CGRSMsSockFun类。

至此AcceptEx算讨论完整了,OVERLAPPED的派生定义也讲完了,让我们继续下一步。

 

四、编写线程池回调函数:

 

在讨论扩展定义OVERLAPPED结构体时,给出了非线程池版的线程函数的大概框架,也就是传统IOCP使用的自建线程使用方式,这种方式要自己创建完成端口句柄,自己将SOCKET句柄绑定到完成端口,这里就不在赘述,主要介绍下调用BindIoCompletionCallback函数时,应如何编写这个线程池的回调函数,其实它与前面那个线程函数是很类似的。

先来看看回调函数长个什么样子:

VOIDCALLBACKFileIOCompletionRoutine(

 [in]                DWORDdwErrorCode,

 [in]                DWORDdwNumberOfBytesTransfered,

 [in]                LPOVERLAPPEDlpOverlapped

;

第一个参数就是一个错误码,如果是0恭喜你,操作一切ok,如果有错也不要慌张,前一篇文章中已经介绍了如何翻译和看懂这个错误码。

照着做就是了。

第二个参数就是说这次IO操作一共完成了多少字节的数据传输任务,这个字段有个特殊含义,如果你发现一个Recv操作结束了,并且这个参数为0,那么就是说,客户端断开了连接(注意针对的是TCP方式,整个SOCKET池就是为TCP方式设计的)。

如果这个情况发生了,在SOCKET池中就该回收这个SOCKET句柄。

第三个参数现在不用多说了,立刻就知道怎么用它了。

跟刚才调用GetQueuedCompletionStatus函数得到的指针是一个含义。

下面就来看一个实现这个回调的例子:

VOIDCALLBACKMyIOCPThread(DWORDdwErrorCode

DWORDdwBytesTrans,LPOVERLAPPEDlpOverlapped

      {//IOCP回调函数

             .....................

             if(NULL==lpOverlapped

             {//没有真正的完成

                    SleepEx(20,TRUE;//故意置成可警告状态

                    return;

             }

             //找回“火车头”以及后面的所有东西

             MYOVERLAPPED* pOL=CONTAINING_RECORD(lpOverlapped

MYOVERLAPPED,m_ol;

             switch(pOL->m_iOpType

{

case0:

//AcceptEx结束

{//有链接进来了SOCKET句柄就是pMyOL->m_skClient

   

}

break;

............................

}

........................

      }//endfun

 

看起来很简单吧?

好像少了什么?

对了那个该死的循环,这里不用了,因为这个是由线程池回调的一个函数而已,线程的活动状态完全由系统内部控制,只管认为只要有IO操作完成了,此函数就会被调用。

这里关注的焦点就完全的放到了完成之后的操作上,而什么线程啊,完成端口句柄啊什么的就都不需要了(甚至可以忘记)。

这里要注意一个问题,正如在《IOCP编程之“双节棍”》中提到的,这个函数执行时间不要过长,否则会出现掉线啊,连接不进来啊等等奇怪的事情。

另一个要注意的问题就是,这个函数最好套上结构化异常处理,尽可能的多拦截和处理异常,防止系统线程池的线程因为你糟糕的回调函数而壮烈牺牲,如果加入了并发控制,还要注意防止死锁,不然你的服务器会“死”的很难看。

理论上来说,你尽可以把这个函数看做一个与线程池函数等价的函数,只是他要尽可能的“短”(指执行时间)而紧凑(结构清晰少出错)。

最后,回调函数定义好了,就可以调用BindIoCompletionCallback函数,将一个SOCKET句柄丢进完成端口的线程池了:

BindIoCompletionCallback((HANDLEskClient,MyIOCPThread,0;

注意最后一个参数到目前为止,你就传入0吧。

这个函数的神奇就是不见了CreateIoCompletionPort的调用,不见了CreateThread的调用,不见了GetQueuedCompletionStatus等等的调用,省去了n多繁琐且容易出错的步骤,一个函数就全部搞定了。

 

五、服务端调用:

 

以上的所有步骤在完全理解后,最终让我们看看SOCKET池如何实现之。

1、按照传统,要先监听到某个IP的指定端口上:

SOCKADDR_IN   saServer={0};

//创建监听Socket

SOCKETskServer=:

:

WSASocket(AF_INET,SOCK_STREAM,IPPROTO_IP

NULL,0,WSA_FLAG_OVERLAPPED;

//把监听SOCKET扔进线程池,这个可以省略              :

:

BindIoCompletionCallback((HANDLEskServer,MyIOCPThread,0;

//必要时打开SO_REUSEADDR属性,重新绑定到这个监听地址

BOOL  bReuse=TRUE;                :

:

setsockopt(m_skServer,SOL_SOCKET,SO_REUSEADDR

(LPCSTR&bReuse,sizeof(BOOL;

saServer.sin_family=AF_INET;

saServer.sin_addr.s_addr=INADDR_ANY;

//INADDR_ANY这个值的魅力是监听所有本地IP的相同端口

saServer.sin_port=htons(80;     //用80得永生

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 公共行政管理

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1