Start函数用于开启服务
1 初始化状态变量
2 创建监听套接字
3 加载使用扩展API函数
4 创建完成端口对象
5 建立监听套接字和完成端口对象间的关联
6 为监听套接字注册FD_ACCEPT时间
7 投递AcceptEx IO不够时可以得到通知后创建监听线程
代码语言:javascript复制BOOL CIOCOPServer::Start(int nPort,int nMaxConnnections,int nMaxFreeBuffers,int nMaxFreeContexts,int nInitialReads)
{
//检查服务是否启动
if(m_bServerStarted)
return FALSE;
//保存参数
m_nPort = nPort;
m_nMaxConnnections = nMaxConnnections;
m_nMaxFreeBuffers = nMaxFreeBuffers;
m_nMaxFreeContexts = nMaxFreeContexts;
m_nInitialReads = nInitialReads;
//初始化变量
m_bServerStarted = TRUE;
m_bShutDown = FALSE;
//创建监听套接字,绑定到本地端口, 进入监听模式
m_sListen = ::WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
SOCKADDR_IN si;
si.sin_family = AF_INET;
si.sin_port = nPort;
si.sin_addr.S_un.S_addr = INADDR_ANY;
if(::bind(m_sListen,(sockaddr*)&si,sizeof(si))==SOCKET_ERROR)
{
m_bServerStarted = FALSE;
return FALSE;
}
::listen(m_sListen,200);
//创建完成端口
m_hConnection = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
//加载扩展函数AcceptEx
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes;
WSAIotcl(
m_sListen,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx,
sizeof(GuidAcceptEx),
&m_lpfnAcceptEx,
sizeof(m_lpfnAcceptEx),
&dwBytes,
NULL,
NULL
);
//加载GetAcceptExSockaddrs
GUID GuidAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
::WSAIoctl(m_sListen,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuiGetAcceptExSockaddrs,
sizeof(GuidGetAcceptExSockaddrs),
&m_lpfnAcceptExSockaddrs,
sizeof(m_lofnAcceptExSockaddrs),
&dwBytes,
NULL,
NULL);
//将监听套接字关联到完成端口
::CreateIoCompletionPort((HANDLE)m_sListen,m_hConnection,(DWORD)0,0);
//注册FD_ACCEPT事件
WSAEventSelect(m_sListen,m_hAcceptEvent,FD_ACCEPT);
//创建监听线程
m_hListenThread = ::CreateThread(NULL,0,_ListenThreadProc,this,0,NULL);
return TRUE;
}
监听线程_ListenThreadProc主要责任:监听套接字投递AcceptEx IO请求。
m_hAcceptEvent:当winsock接收到新的连接请求,但是AcceptEx IO,请求来接收这个连接时,就会触发该时间对象。
m_hRepostEvent:与IO进行交互。
_ListenThreadProc在下面3中情况下投递Accept请求:
1 程序初始化,要先投递几个Accept请求,个数由用户指定
2 处理IO的线程接受到一个客户,使m_hRepostEvent时间受信,_ListenThreadProc线程得到通知后再投递一个Accept请求。
3 程序运行期间,如果投递的Accept请求不够用,用户的连接请求未能够马上处理,这时候再投递若干个Accept请求。
代码语言:javascript复制DWORD WINAPI CIOCPServer::_ListenThreadProc(LPVOID lpParam)
{
CIOCPServer *pThis = (CIOCPServer*)lpParam;
//在监听套接字上投递几个AcceptIO
CIOCPBuffer *pBuffer;
for(int i=0;i<pThis->m_nInitialAccepts;i )
{
pBuffer = pThis->AllocateBuffer(BUFFER_SIZE);
if(pBuffer==NULL)
return -1;
pTHis->InsertPendingAccept(pBuffer);
pThis->PostAccept(pBuffer);
}
//构建事件对象数组
HANDLE hWaitEvents[2 MAX_THREAD];
int nEventCount = 0;
hWaitEvents[nEventCount ]=pThis->m_hAcceptEvent;
hWaitEvents[nEventCount ]=pThis->m_hRepostEvent;
//创建指定数量的工作线程在完成端口上处理IO
for(i=0;i<MAX_THREAD;i )
{
hWaitEvents[nEventCount ]=::CreateThread(NULL,0,_WorkerThreadProc,pThis,0,NULL);
}
//下面进入无限循环,处理时间对象数组中的事件
while(TRUE)
{
int nIndex = ::WSAWaitForMultipleEvents(nEventCount,hWaitEvents,FALSE,60*1000,FALSE);
//检查是否要停止服务
if(pThis->m_bShutDown || nIndex==WSA_WAIT_FAILED)
{
//关闭所有连接
pThis->CloseAllConnections();
::Sleep(0);
//关闭监听套接字
::closesocket(pThis->m_sListen);
pThis->m_sListen=INVALID_SOCKET;
::Sleep(0);
//通知所有IO处理线程退出
for(int i=2;i<MAX_THREAD 2;i )
{
::PostQueuedCompletionStatus(pThis->m_hCompletion,-1,0,NULL);
}
//等待IO处理线程退出
::WaitForMultipleObjects(MAX_THREAD,&hWaitEvents[2],TRUE,5*1000);
for(i=2;i<MAX_THREAD 2;i )
{
::CloseHandle(hWaitEvents[i]);
}
::CloseHandle(pThis->m_hCompletion);
pThis->FreeBuffers();
pThis->FreeContexts();
::ExitThread(0);
}
//定时检查所有未返回的AcceptEx IO的连接建立多长时间
if(nIndex == WSA_WAIT_TIMEOUT)
{
pBuffer = pThis->m_pPendingAccepts;
while(pBuffer!=NULL)
{
int nSeconds;
int nLen = sizeof(nSeconds);
//取得连接建立时间
::getsockopt(pBuffer->sClient,SOL_SOCKET,SO_CONNECT_TIME,(char*)&nSeconds,&nLen);
//如果超过两分钟,就丢弃
if(nSeconds!=-1 && nSeconds>=2*60)
{
closesocket(pBuffer->sClient);
pBuffer->sClient = INVALIDE_SOCKET;
}
pBuffer = pBuffer->pNext;
}
}
else
{
nIndex = nIndex-WAIT_OBJECT_0;
WSANETWORKEVENTS ne;
int nLimit=0;
if(nIndex==0)//m_hAcceptEvent时间对象受信,说明投递的Accept请求不够,需要增加
{
::WSAEnumNetworkEvents(pThis->m_sListen,hWaitEvents[nIndex],&ne);
if(ne.lNetworkEvents & FD_ACCEPT)
{
nLimit = 50;
}
}
else if(nIndex==1)//m_hRepostEvent事件对象受信,说明处理IO的线程接受到新的客户
{
nLimit = InterlockedExchange(&pThis->m_nRepostCount,0);
}
else if(nIndex>1)//IO服务线程退出,说明有错误发生,关闭服务器
{
pThis->m_bShutDown = TRUE;
continue;
}
//投递nLimit个AcceptEx IO 请求
int i=0;
while(i < nLimit && pThis->m_nPendingAcceptCount < pThis->m_nMaxAccepts)
{
pBuffer = pThis->AllocateBuffer(BUFFER_SIZE);
if(pBuffer!=NULL)
{
pThis->InsertPendingAccept(pBuffer);
pThis->PostAccept(pBuffer);
}
}
}
}
return 0;
}
3 停止服务函数ShutDown
代码语言:javascript复制void CIOCPServer::ShutDown()
{
if(!m_bServerStarted)
return;
//通知监听线程,马上停止服务
m_bShutDown = TRUE;
::SetEvent(m_hAcceptEvent);
//等待监听线程退出
::WaitForSingleObject(m_hListenThread,INFINITE);
::CloseHandle(m_hListenThread);
m_hListenThread = NUll;
m_bServerStarted = FALSE;
}