作者:elssann

 

在​​Windows​​​下进行网络服务端程序开发,毫无疑问,Winsock 完成端口模型是最高效的。Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解。如果不了解,推荐几本书:《Inside Windows 2000,《WINDOWS核心编程 》,《WIN32多线程程序设计》、《WINDOWS网络编程技术》。在去年,我在C语言下用完成端口模型写了一个​​Web​​​SERVER,前些天,我决定用​​C++​​​重写这个WEBSERVER,给这个WEBSERVER增加了一些功能,并改进完成端口操作方法,比如采用AcceptEx来代替accept和使用LOOKASIDE LIST来管理内存,使得WEBSERVER的性能有了比较大的提高。
  
  在重写的开始,我决定把完成端口模型封装成一个比较通用的C++类,针对各种网络服务端程序的开发,只要简单地继承这个类,改写其中两个虚拟函数就能满足各种需要。到昨天为止,WEBSERVER重写完毕,我就写了这篇文章对完成端口模型做一个总结,并介绍一下我的这个类。
  
  一:完成端口模型
  
  至于完成端口和Winsock完成端口模型的详细介绍,请参见我上面介绍的那几本书,这里只是我个人对完成端口模型理解的一点心得。
  
  首先我们要抽象出一个完成端口大概的处理流程 :
  
  1:创建一个完成端口。
  
  2:创建一个线程A。
  
  3:A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果,这个函数是个阻塞函数。
  
  4:主线程循环里调用accept等待客户端连接上来。
  
  5:主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。
  
  6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。
  
  7:WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。

这里。
  
​​​Windows​​系统进行的处理,不需要我们程序干预。

 

一个对Winsock完成端口模型封装的类_null


    归根到底概括完成端口模型一句话:

  

WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO都完成了,那么就在完成端口那里排成一个队列)。我们在另外一个线程里从完成端口不断地取出IO操作结果,然后根据需要再发出WSASend/WSARecv IO操作。

二:提高完成端口效率的几种有效方法
  
  1:使用AcceptEx代替accept。AcceptEx函数是微软的Winsosk 扩展函数,这个函数和accept的区别就是:accept是阻塞的,一直要到有客户端连接上来后accept才返回,而AcceptEx是异步的,直接就返回了,所以我们利用AcceptEx可以发出多个AcceptEx调用
  
  等待客户端连接。另外,如果我们可以预见到客户端一连接上来后就会发送数据(比如​​​Web​​​SERVER的客户端浏览器),那么可以随着AcceptEx投递一个BUFFER进去,这样连接一建立成功,就可以接收客户端发出的数据到BUFFER里,这样使用的话,一次AcceptEx调用相当于accpet和recv的一次连续调用。同时,微软的几个扩展函数针对​​操作系统​​​优化过,效率优于WINSOCK 的标准API函数。
  
  2:在套接字上使用SO_RC​​​VB​​​UF和SO_SNDBUF选项来关闭系统缓冲区。这个方法见仁见智,详细的介绍可以参考《​​Windows​​​核心编程》第9章。这里不做详细介绍,我封装的类中也没有使用这个方法。
  
  3:内存分配方法。因为每次为一个新建立的套接字都要动态分配一个“单IO数据”和“单句柄数据”的数据结构,然后在套接字关闭的时候释放,这样如果有成千上万个客户频繁连接时候,会使得程序很多开销花费在内存分配和释放上。这里我们可以使用lookaside list。开始在微软的platform sdk里的SAMPLE里看到lookaside list,我一点不明白,MSDN里有没有。后来还是在DDK的文档中找到了,,
  
  lookaside list
  

A system-managed queue from which entries of a fixed size can be allocated and into which entries can be deallocated dynamically. Callers of the Ex(ecutive) Support lookaside list routines can use a lookaside list to manage any dynamically sized set of fixed-size buffers or structures with caller-determined contents.

  
  For example, the I/O Manager uses a lookaside for fast allocation and deallocation of IRPs and MDLs. As another example, some of the system-supplied SCSI class drivers use lookaside lists to allocate and release memory for SRBs.
  lookaside list名字比较古怪(也许是我孤陋寡闻,第一次看到),其实就是一种内存管理方法,和内存池使用方法类似。我个人的理解:就是一个单链表。每次要分配内存前,先查看这个链表是否为空,如果不为空,就从这个链表中解下一个结点,则不需要新分配。如果为空,再动态分配。使用完成后,把这个数据结构不释放,而是把它插入到链表中去,以便下一次使用。这样相比效率就高了很多。在我的程序中,我就使用了这种单链表来管理。
  
  在我们使用AcceptEx并随着AcceptEx投递一个BUFFER后会带来一个副作用:比如某个客户端只执行一个connect操作,并不执行send操作,那么AcceptEx这个请求不会完成,相应的,我们用GetQueuedCompletionStatus在完成端口中得不到操作结果,这样,如果有很多个这样的连接,对程序性能会造成巨大的影响,我们需要用一种方法来定时检测,当某个连接已经建立并且连接时间超过我们规定的时间而且没有收发过数据,那么我们就把它关闭。检测连接时间可以用SO_CONNECT_TIME来调用getsockopt得到。
  
  还有一个值得注意的地方:就是我们不能一下子发出很多AcceptEx调用等待客户连接,这样对程序的性能有影响,同时,在我们发出的AcceptEx调用耗尽的时候需要新增加AcceptEx调用,我们可以把FD_ACCEPT事件和一个EVENT关联起来,然后用WaitForSingleObject等待这个Event,当已经发出AccpetEx调用数目耗尽而又有新的客户端需要连接上来,FD_ACCEPT事件将被触发,EVENT变为已传信状态,

  WaitForSingleObject返回,我们就重新发出足够的AcceptEx调用。
  
  关于完成端口模型就介绍到这里。下面介绍我封装的类,这个类写完后,我用这个类做了个ECHOSERVER。

 

void main()

{

CompletionPortModel p;

p.Init();p.AllocEventMessage();

if (FALSE == p.PostAcceptEx())

{

return;

}

p.ThreadLoop();

return;

}

  我在我自己的机器上测试,

  客户端的代码是

for (int i=0; i<10000; i++)

{

SOCKET s = socket(….);

connect(….);

send(…);recv(…..)

cout << buffer << endl;

}

  结果客户端程序在循环到3000多次的时候死掉,但是服务端程序运行良好,重新启动客户端程序,发送接收数据正常。
  
HandleData和DataAction这两个虚函数,对于那些需要连续发送相关联的数据应用(比如传送文件),使用者需要自己扩展这两个函数,比如创建一个全局队列,每次从完成端口里得到数据后插入队列,然后用另外一个线程专门处理这个队列。。。
  
CPU数据*2个完成端口线程等等,,因为我同时正在做的毕业设计NDIS​​​驱动​​​​防火墙​​​开发正在一个比较难的地方卡住了,时间和精力有限,就没有对这个类进行进一步完善,程序中也许有不合理和错误的地方,请高手多多指教。对于高性能的服务端程序开发是比较难的,记得有次和腾讯一个技术人员聊天,他说,像腾讯QQ的开发,难点不在客户端,而在服务端各个​​服务器​​​之间的​​通信​​​和同步。服务端程程序的集群和负载平衡是一个很复杂的问题,我在这方面刚接触,希望能有更多的高手出来共享自己的经验。
  
PLATFORM SDK里的例子看了一遍,借鉴了其中很多思路和方法,在此对写这个例子的微软程序员表示感谢:)
    
源代码说明:
在WINDOWS下进行网络服务端程序开发,毫无疑问,Winsock 完成端口模型是最高效的。Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解。如果不了解,推荐几本书:《Inside Windows 2000,《WINDOWS核心编程》,《WIN32多线程程序设计》、《WINDOWS网络编程技术》。在去年,我在C语言下用完成端口模型写了一个WEBSERVER,前些天,我决定用C++重写这个WEBSERVER,给这个WEBSERVER增加了一些功能,并改进完成端口操作方法,比如采用AcceptEx来代替accept和使用LOOKASIDE LIST来管理内存,使得WEBSERVER的性能有了比较大的提高。

 

 

/*++Copyright (c) 2004

模块名:

iomodel.cpp

模块描述:

Winsock 完成端口类实现文件

作者:

PPP elssann@hotmail.com

开发环境:

Visual C++ 6.0, Windows 2000.

修订记录:

创建于: 2004.1.16

最后修改日期:
2004.1.23


--*/

#include <iostream.h>
#include <winsock2.h>
#include <mswsock.h>
#include "iomodel.h"


CompletionPortModel::CompletionPortModel()
/*++

函数描述:
构造函数,初始化线程句柄数组,初始化AcceptEx()调用的计数。初始化临界段代码变量。

Arguments:
无。

Return Value:
无。

--*/

{
for (int i=0; i< MAXTHREAD_COUNT; i++)
{
m_hThreadArray[i] = INVALID_HANDLE_VALUE;
}

m_lAcceptExCounter = 0;

InitializeCriticalSection(&m_ListCriSection);
InitializeCriticalSection(&m_HandleCriSection);
InitializeCriticalSection(&m_IoCriSection);

m_lpHandleLOOKasideLists = NULL;
m_lpIoLookasideLists = NULL;

#ifndef _DEBUG
GetAddressAndPort();
#endif
}//end of CompletionPortModel()


CompletionPortModel::~CompletionPortModel()
/*++

函数描述:
析构函数,释放链表所有结点。

Arguments:
无。

Return Value:

--*/
{
PPER_IO_CONTEXT lpIoNode;

while (m_lpConnectionListHead->pNext)
{
lpIoNode = m_lpConnectionListHead->pNext;
m_lpConnectionListHead->pNext = lpIoNode->pNext;
closesocket(lpIoNode->sClient);
HeapFree(GetProcessHeap(), 0, lpIoNode);
}

while(NULL != m_lpIoLookasideLists)
{
lpIoNode = m_lpIoLookasideLists;
m_lpIoLookasideLists = m_lpIoLookasideLists->pNext;
HeapFree(GetProcessHeap(), 0, lpIoNode);
}

PPER_HANDLE_CONTEXT lpHandleNode;
while(NULL != m_lpHandleLOOKasideLists)
{
lpHandleNode = m_lpHandleLOOKasideLists;
m_lpHandleLOOKasideLists = m_lpHandleLOOKasideLists->pNext;
HeapFree(GetProcessHeap(), 0, lpHandleNode);
}

DeleteCriticalSection(&m_ListCriSection);
DeleteCriticalSection(&m_HandleCriSection);
DeleteCriticalSection(&m_IoCriSection);

}//end of ~CompletionPortModel()


BOOL CompletionPortModel::Init()
/*++

函数描述:
初始化,创建完成端口、创建完成端口线程,并调用类成员函数InitWinsock初始化Winsock、
建立一个监听套接字m_ListenSocket,并将此套接字同完成端口关联起来,获取AcceptEx指针。

Arguments:
无。

Return Value:
函数调用成功返回TRUE,失败返回FALSE。

--*/
{
BOOL bSuccess = InitLinkListHead();

if (FALSE == bSuccess)
{
return FALSE;
}

m_hCOP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
if (NULL == m_hCOP)
{
cout << "CreateIoCompletionPort() failed: " << GetLastError() << endl;

return FALSE;
}

//
//取得系统中CPU的数目,创建和CPU数目相等的线程,如果事先估计到完成端口处理线程会堵塞,
//可以考虑创建 SysInfo.dwNumberOfProcessors*2个线程。一般在单处理器上创建和CPU数目相等
//的线程就可以了
//
SYSTEM_INFO SysInfo;
GetSystemInfo(&SysInfo);
if (MAXTHREAD_COUNT < SysInfo.dwNumberOfProcessors)
{
SysInfo.dwNumberOfProcessors = MAXTHREAD_COUNT;
}

for (int i=0; i<(int)SysInfo.dwNumberOfProcessors; i++)
{
m_hThreadArray[i] = CreateThread(NULL, 0, CompletionRoutine, (LPVOID)this, 0, NULL);
if (NULL == m_hThreadArray[i])
{
while (i>0)
{
CloseHandle(m_hThreadArray[i-1]);
m_hThreadArray[i-1] = INVALID_HANDLE_VALUE;
i--;
}//end of while

cout << "CreateThread() failed: " << GetLastError() << endl;
CloseHandle(m_hCOP);
HeapFree(GetProcessHeap(), 0, m_lpConnectionListHead);

return FALSE;
}
}//end of for

//
//调用InitWinsock函数初始化Winsock、建立一个监听套接字m_ListenSocket,
//并将此套接字同完成端口关联起来,获取AcceptEx指针。
//
bSuccess = InitWinsock();
if (!bSuccess)
{
//
//给完成端口线程发送消息,指示线程退出。
//
PostQueuedCompletionStatus(m_hCOP, 0, NULL, NULL);
CloseThreadHandle();
CloseHandle(m_hCOP);
HeapFree(GetProcessHeap(), 0, m_lpConnectionListHead);

return FALSE;
}

//
//调用BindAndListenSocket()绑定套接字并将套接字置于监听状态
//
bSuccess = BindAndListenSocket();
if (!bSuccess)
{
PostQueuedCompletionStatus(m_hCOP, 0, NULL, NULL);
CloseThreadHandle();
CloseHandle(m_hCOP);
HeapFree(GetProcessHeap(), 0, m_lpConnectionListHead);

return FALSE;
}

return TRUE;
}//end of Init()


void CompletionPortModel::CloseThreadHandle()
/*++

函数描述:
对每一个创建的线程调用CloseHandle()。

Arguments:
无。

Return Value:
无。

--*/
{
for (int i=0; i< MAXTHREAD_COUNT; i++)
{
if (INVALID_HANDLE_VALUE != m_hThreadArray[i])
{
CloseHandle(m_hThreadArray[i]);
m_hThreadArray[i] = INVALID_HANDLE_VALUE;
}
}//end of for

return;
}//end of CloseThreadHandle()


BOOL CompletionPortModel::InitWinsock()
/*++

函数描述:
初始化Winsock,创建一个监听套接字,获取AcceptEx函数指针,为监听套接字分配一个单句柄
数据,并将监听套接字与完成端口hCOP关联。

Arguments:
无。

Return Value:
函数调用成功返回TRUE,失败返回FALSE。

--*/
{
WSADATA wsd;
int nResult = WSAStartup(MAKEWORD(2,2), &wsd);
if (0 != nResult)
{
cout << "WSAStartup() failed" << endl;

return FALSE;
}

m_ListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP,
NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == m_ListenSocket)
{
cout << "WSASocket() failed: " << WSAGetLastError() << endl;
WSACleanup();

return FALSE;
}


DWORD dwResult;

//
//获取微软SOCKET扩展函数指针
//
nResult = WSAIoctl(
m_ListenSocket,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&g_GUIDAcceptEx,
sizeof(g_GUIDAcceptEx),
&lpAcceptEx,
sizeof(lpAcceptEx),
&dwResult,
NULL,
NULL
);

if (SOCKET_ERROR == nResult)
{
cout << "Get AcceptEx failed: " << WSAGetLastError() << endl;
closesocket(m_ListenSocket);
WSACleanup();

return FALSE;
}

nResult = WSAIoctl(
m_ListenSocket,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&g_GUIDTransmitFile,
sizeof(g_GUIDTransmitFile),
&lpTransmitFile,
sizeof(lpTransmitFile),
&dwResult,
NULL,
NULL
);

if (SOCKET_ERROR == nResult)
{
cout << "Get TransmitFile failed: " << WSAGetLastError() << endl;
closesocket(m_ListenSocket);
WSACleanup();

return FALSE;
}


//
//为监听套接字分配一个单句柄数据
//
PPER_HANDLE_CONTEXT lpListenHandleContext = (PPER_HANDLE_CONTEXT)HeapAlloc(
GetProcessHeap(), HEAP_ZERO_MEMORY,
sizeof(PER_HANDLE_CONTEXT)
);
if (NULL == lpListenHandleContext)
{
closesocket(m_ListenSocket);
WSACleanup();

cout << "HeapAlloc() failed " << endl;


return FALSE;
}

lpListenHandleContext->IoSocket = m_ListenSocket;
lpListenHandleContext->pNext = NULL;

//
//将监听套接字m_ListenSocket和已经建立的完成端口关联起来
//
HANDLE hrc = CreateIoCompletionPort(
(HANDLE)m_ListenSocket,
m_hCOP,
(ULONG_PTR)lpListenHandleContext,
0
);
if (NULL == hrc)
{
closesocket(m_ListenSocket);
HeapFree(GetProcessHeap(), 0, lpListenHandleContext);
WSACleanup();

cout << "CreateIoCompletionPort failed: " << GetLastError() << endl;

return FALSE;
}

return TRUE;
}//end of InitWinsock()


BOOL CompletionPortModel::BindAndListenSocket()
/*++

函数描述:
private函数,供Init调用。
将监听套接字m_ListenSocket绑定到本地IP地址,并置于监听模式。


Arguments:
无。

Return Value:
函数调用成功返回TRUE,失败返回FALSE。

--*/
{
SOCKADDR_IN InternetAddr;
InternetAddr.sin_family = AF_INET;

#ifdef _DEBUG
InternetAddr.sin_addr.s_addr = inet_addr(LOCALADDRESS);
InternetAddr.sin_port = htons(PORT);
#else
InternetAddr.sin_addr.s_addr = inet_addr(szAddress);
InternetAddr.sin_port = htons(uPort);
#endif

int nResult = bind(m_ListenSocket, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));
if (SOCKET_ERROR == nResult)
{
WSACleanup();
closesocket(m_ListenSocket);

cout << "bind() failed: " << WSAGetLastError() << endl;

return FALSE;
}

nResult = listen(m_ListenSocket, 20);
if (SOCKET_ERROR == nResult)
{
WSACleanup();
closesocket(m_ListenSocket);

cout << "listen() failed: " << WSAGetLastError() << endl;

return FALSE;
}

return TRUE;
}//end of BindAndListenSocket()


DWORD __stdcall CompletionRoutine(LPVOID Param)
/*++

函数描述:
完成端口处理线程,循环调用GetQueuedCompletionStatus来获取IO操作结果。

Arguments:

Return Value:
线程退出代码。

--*/
{
CompletionPortModel* pThis = (CompletionPortModel*)Param;
DWORD dwNumberBytes;
PPER_HANDLE_CONTEXT lpHandleContext = NULL;
LPWSAOVERLAPPED lpOverlapped = NULL;
int nResult;
BOOL bSuccess;

while (TRUE)
{
bSuccess = GetQueuedCompletionStatus(
pThis->m_hCOP,
&dwNumberBytes,
(PULONG_PTR )&lpHandleContext,
&lpOverlapped,
INFINITE
);


if (FALSE == bSuccess)
{

#ifndef _DEBUG
cout << "GetQueuedCompletionStatus() failed: " << GetLastError() << endl;
#endif

continue;
}
if (NULL == lpHandleContext)
{
//
//PostQueuedCompletionStatus发过来一个空的单句柄数据,表示线程要退出了。
//
return 0;
}

PPER_IO_CONTEXT lpPerIoContext = (PPER_IO_CONTEXT)lpOverlapped;

#ifdef _DEBUG
cout << "recv buffer data: " << lpPerIoContext->szBuffer << endl;
#endif

if(IoAccept != lpPerIoContext->IoOperation)
{
if((!bSuccess) || (bSuccess && (0 == dwNumberBytes)))
{
closesocket(lpPerIoContext->sClient);
lpPerIoContext->pNext = NULL;
pThis->InsertToLookaside(lpPerIoContext, NULL);
lpHandleContext->pNext = NULL;
pThis->InsertToLookaside(NULL, lpHandleContext);

continue;
}
}

HANDLE hResult;
PPER_HANDLE_CONTEXT lpNewperHandleContext;

switch(lpPerIoContext->IoOperation)
{
case IoAccept :
if (dwNumberBytes)
{
//
//第一次连接成功并且收到了数据,将这个结点从链表中解除
//
EnterCriticalSection(&pThis->m_ListCriSection);
pThis->ReleaseNode(lpPerIoContext);
LeaveCriticalSection(&pThis->m_ListCriSection);
}
nResult = setsockopt(
lpPerIoContext->sClient,
SOL_SOCKET,
SO_UPDATE_ACCEPT_CONTEXT,
(char *)&pThis->m_ListenSocket,
sizeof(pThis->m_ListenSocket)
);
if(SOCKET_ERROR == nResult)
{
cout << "SO_UPDATE_ACCEPT_CONTEXT failed to update accept socket" << endl;
closesocket(lpPerIoContext->sClient);
lpPerIoContext->pNext = NULL;
pThis->InsertToLookaside(lpPerIoContext, NULL);

continue;
}


lpNewperHandleContext = pThis->GetHandleFromLookaside();
if (NULL == lpNewperHandleContext)
{
lpNewperHandleContext = (PPER_HANDLE_CONTEXT)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof(PER_HANDLE_CONTEXT)
);
if (NULL == lpNewperHandleContext)
{
cout << "HeapAlloc() for lpNewperHandlecontext failed" << endl;

closesocket(lpPerIoContext->sClient);
lpPerIoContext->pNext = NULL;
pThis->InsertToLookaside(lpPerIoContext, NULL);

continue;
}
}

lpNewperHandleContext->IoSocket = lpPerIoContext->sClient;
lpNewperHandleContext->pNext = NULL;

//
//将新建立的套接字关联到完成端口
//
hResult = CreateIoCompletionPort(
(HANDLE)lpPerIoContext->sClient,/
pThis->m_hCOP,
(DWORD_PTR)lpNewperHandleContext,
0
);
if (NULL == hResult)
{
cout << "CreateIoCompletionPort() failed: " << GetLastError();

closesocket(lpPerIoContext->sClient);
lpPerIoContext->pNext = NULL;
lpNewperHandleContext->pNext = NULL;
pThis->InsertToLookaside(lpPerIoContext, NULL);
pThis->InsertToLookaside(NULL, lpNewperHandleContext);

continue;
}

if (dwNumberBytes)
{
//
//分析处理数据。
//
pThis->HandleData(lpPerIoContext, IO_READ_COMPLETION);
bSuccess = pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
if (FALSE == bSuccess)
{
continue;
}
}

//
//如果连接成功但是没有收到数据
//
else
{
pThis->HandleData(lpPerIoContext, IO_ACCEPT_COMPLETION);
bSuccess = pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
if (FALSE == bSuccess)
{
continue;
}
}
break;//end of case IoAccept

case IoRead:
pThis->HandleData(lpPerIoContext, IO_READ_COMPLETION);
bSuccess = pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
if (FALSE == bSuccess)
{
continue;
}

break;//end of case IoRead

case IoWrite:
pThis->HandleData(lpPerIoContext, IO_WRITE_COMPLETION);
bSuccess = pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
if (FALSE == bSuccess)
{
continue;
}

break;

default:
continue;
break;
}
}

return 0;

}//end of CompletionRoutine()


BOOL CompletionPortModel::PostAcceptEx()
/*++

Fucntion Description:

连续发出10个AcceptEx调用。

Arguments:

Return Value:

函数调用成功返回TRUE,失败返回FALSE。

--*/
{
while (m_lAcceptExCounter < 10)
{
SOCKET AcceptSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, /
NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == AcceptSocket)
{
cout << "WSASocket failed " << endl;

return FALSE;
}

PPER_IO_CONTEXT lpAcceptExIoContext = GetIoFromLookaside();
if (NULL == lpAcceptExIoContext)
{
lpAcceptExIoContext = (PPER_IO_CONTEXT)HeapAlloc(/
GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_CONTEXT));
if (NULL == lpAcceptExIoContext)
{
cout << "HeapAlloc() failed " << endl;
closesocket(AcceptSocket);

return FALSE;
}
}

ZeroMemory(&(lpAcceptExIoContext->ol), sizeof(lpAcceptExIoContext->ol));
lpAcceptExIoContext->sClient = AcceptSocket;
lpAcceptExIoContext->IoOperation = IoAccept;
lpAcceptExIoContext->pNext = NULL;
ZeroMemory(lpAcceptExIoContext->szBuffer, BUFFER_SIZE);
lpAcceptExIoContext->wsaBuffer.buf = lpAcceptExIoContext->szBuffer;
lpAcceptExIoContext->wsaBuffer.len = BUFFER_SIZE;
lpAcceptExIoContext->unId = lpAcceptExIoContext->sClient;

DWORD dwBytes;
BOOL bSuccess = lpAcceptEx(
m_ListenSocket,
lpAcceptExIoContext->sClient,
lpAcceptExIoContext->szBuffer,
lpAcceptExIoContext->wsaBuffer.len - ((sizeof(SOCKADDR_IN) + 16) * 2),
sizeof(SOCKADDR_IN) + 16,
sizeof(SOCKADDR_IN) + 16,
&dwBytes,
&(lpAcceptExIoContext->ol));

if (FALSE == bSuccess)
{
int nResult = WSAGetLastError();
if (nResult != ERROR_IO_PENDING)
{
cout << "AcceptEx() failed :" << nResult << endl;
closesocket(AcceptSocket);
HeapFree(GetProcessHeap(), 0 , lpAcceptExIoContext);

return FALSE;
}

InsertNode(lpAcceptExIoContext, NULL);
InterlockedExchangeAdd(&m_lAcceptExCounter, 1);
}
}

InterlockedExchangeAdd(&m_lAcceptExCounter, -10);

return TRUE;
}//end of PostAccetExRoutine()


void CompletionPortModel::InsertNode(PPER_IO_CONTEXT pNode, PPER_HANDLE_CONTEXT pHandleNode)
/*++

Fucntion Description:

根据参数类型将传递进来结点插入到相应的链表头。

Arguments:

pNode - 要插入链表中的结点
pHandleNode - 要插入链表中的结点

Return Value:

无.

--*/
{
if (NULL != pNode)
{
EnterCriticalSection(&m_ListCriSection);
pNode->pNext = m_lpConnectionListHead->pNext;
m_lpConnectionListHead->pNext = pNode;
LeaveCriticalSection(&m_ListCriSection);
}

return;
}//end of InsertNode

 

/*++Copyright (c) 2004

模块名:

iomodel.h

模块描述:

Winsock 完成端口类头文件

作者:

PPP elssann@hotmail.com

开发环境:

Visual C++ 6.0, Windows 2000.

修订记录:

创建于: 2004.1.16

最后修改日期:
2004.1.23


--*/


#ifndef _IOMODEL_H
#define _IOMODEL_H

//
//Head files
//
#include <winsock2.h>
#include <mswsock.h>


/
/
/

#ifdef __cplusplus
extern "C" {
#endif


#define BUFFER_SIZE 4096
#define MAXTHREAD_COUNT 8

#define PORT 8080
#define LOCALADDRESS "172.29.90.96"

#define IO_READ_COMPLETION 100
#define IO_WRITE_COMPLETION 200
#define IO_ACCEPT_COMPLETION 300


//
//自定义枚举数据类型,用来标识套接字IO动作类型
//
typedef enum _IO_OPERATION
{
IoAccept, //AcceptEx/accept
IoRead, //WSARecv/recv/ReadFile
IoWrite, //WSASend/send/WriteFile
IoEnd
}IO_OPERATION, *PIO_OPERATION;


//
//自定义结构,即“完成键”(单句柄数据)
//
typedef struct _PER_HANDLE_CONTEXT
{
SOCKET IoSocket;

_PER_HANDLE_CONTEXT* pNext;
}PER_HANDLE_CONTEXT, *PPER_HANDLE_CONTEXT;


//
//单IO数据,扩展的WSAOVERLAPPED
//
typedef struct _PER_IO_CONTEXT
{
WSAOVERLAPPED ol;
char szBuffer[BUFFER_SIZE];
WSABUF wsaBuffer;
SOCKET sClient;

unsigned int unId;

IO_OPERATION IoOperation;

_PER_IO_CONTEXT* pNext;
}PER_IO_CONTEXT, *PPER_IO_CONTEXT;


//
// global var
//

static GUID g_GUIDAcceptEx = WSAID_ACCEPTEX;
static GUID g_GUIDTransmitFile = WSAID_TRANSMITFILE;

DWORD __stdcall CompletionRoutine(LPVOID);


//
//完成端口模型类
//


class CompletionPortModel
{
public:
CompletionPortModel();
~CompletionPortModel();

BOOL Init();
BOOL ThreadLoop();
BOOL AllocEventMessage();
BOOL PostAcceptEx();

virtual BOOL HandleData(
PPER_IO_CONTEXT lpPerIoContext,
int nFlags
);
virtual BOOL DataAction(
PPER_IO_CONTEXT lpPerIoContext,
PPER_HANDLE_CONTEXT lpNewperHandletext
);

void InsertNode(PPER_IO_CONTEXT pNode, PPER_HANDLE_CONTEXT pHandleNode);
void ReleaseNode(PPER_IO_CONTEXT pNode);
void InsertToLookaside(PPER_IO_CONTEXT lpIoNode, PPER_HANDLE_CONTEXT lpHandleNode);

PPER_IO_CONTEXT GetIoFromLookaside();
PPER_HANDLE_CONTEXT GetHandleFromLookaside();





HANDLE m_hCOP;
SOCKET m_ListenSocket;

CRITICAL_SECTION m_ListCriSection;
CRITICAL_SECTION m_HandleCriSection;
CRITICAL_SECTION m_IoCriSection;

LPFN_TRANSMITFILE lpTransmitFile;

volatile PPER_IO_CONTEXT m_lpIoLookasideLists;
volatile PPER_HANDLE_CONTEXT m_lpHandleLOOKasideLists;

protected:
BOOL InitWinsock();
BOOL BindAndListenSocket();
BOOL InitLinkListHead();
void CloseThreadHandle();
void GetAddressAndPort();



UINT uPort;
char szAddress[20];

HANDLE m_hThreadArray[MAXTHREAD_COUNT];
HANDLE m_hEvent;

volatile LONG m_lAcceptExCounter;
volatile PPER_IO_CONTEXT m_lpConnectionListHead;

LPFN_ACCEPTEX lpAcceptEx;

private:

};


#ifdef __cplusplus
}
#endif

#endif //_IOMODEL_H

 

在重写的开始,我决定把完成端口模型封装成一个比较通用的C++类,针对各种网络服务端程序的开发,只要简单地继承这个类,改写其中两个虚拟函数就能满足各种需要。到昨天为止,WEBSERVER重写完毕,我就写了这篇文章对完成端口模型做一个总结,并介绍一下我的这个类。

DEMO就是一个ECHOSERVER,记得使用Release模式编译。