系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) windows完成端口(六)
// 最后释放掉所有资源
void CIOCPModel::_DeInitialize()
{
// 删除客户端列表的互斥量
DeleteCriticalSection(&m_csContextList);
// 关闭系统退出事件句柄
RELEASE_HANDLE(m_hShutdownEvent);
// 释放工作者线程句柄指针
for( int i=0;i<m_nThreads;i++ )
{
RELEASE_HANDLE(m_phWorkerThreads[i]);
}
RELEASE(m_phWorkerThreads);
// 关闭IOCP句柄
RELEASE_HANDLE(m_hIOCompletionPort);
// 关闭监听Socket
RELEASE(m_pListenContext);
this->_ShowMessage(_T("释放资源完毕.\n"));
}
//====================================================================================
//
// 投递完成端口请求
//
//====================================================================================
//////////////////////////////////////////////////////////////////
// 投递Accept请求
bool CIOCPModel::_PostAccept( PER_IO_CONTEXT* pAcceptIoContext )
{
ASSERT( INVALID_SOCKET!=m_pListenContext->m_Socket );
// 准备参数
DWORD dwBytes = 0;
pAcceptIoContext->m_OpType = ACCEPT_POSTED;
WSABUF *p_wbuf = &pAcceptIoContext->m_wsaBuf;
OVERLAPPED *p_ol = &pAcceptIoContext->m_Overlapped;
// 为以后新连入的客户端先准备好Socket( 这个是与传统accept最大的区别 )
pAcceptIoContext->m_sockAccept = WSASocket(AF_INET,
SOCK_STREAM,
IPPROTO_TCP,
NULL, 0,
WSA_FLAG_OVERLAPPED);
if( INVALID_SOCKET==pAcceptIoContext->m_sockAccept )
{
_ShowMessage(_T("创建用于Accept的Socket失败!错误代码: %d"),
WSAGetLastError());
return false;
}
// 投递AcceptEx
if(FALSE == m_lpfnAcceptEx( m_pListenContext->m_Socket,
pAcceptIoContext->m_sockAccept,
p_wbuf->buf,
p_wbuf->len - ((sizeof(SOCKADDR_IN)+16)*2),
sizeof(SOCKADDR_IN)+16,
sizeof(SOCKADDR_IN)+16,
&dwBytes, p_ol))
{
if(WSA_IO_PENDING != WSAGetLastError())
{
_ShowMessage(_T("投递 AcceptEx 请求失败,错误代码: %d"),
WSAGetLastError());
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////
// 在有客户端连入的时候,进行处理
// 流程有点复杂,你要是看不懂的话,就看配套的文档吧....
// 如果能理解这里的话,完成端口的机制你就消化了一大半了
// 总之你要知道,传入的是ListenSocket的Context
//我们需要复制一份出来给新连入的Socket用
// 原来的Context还是要在上面继续投递下一个Accept请求
//
bool CIOCPModel::_DoAccpet( PER_SOCKET_CONTEXT* pSocketContext,
PER_IO_CONTEXT* pIoContext )
{
SOCKADDR_IN* ClientAddr = NULL;
SOCKADDR_IN* LocalAddr = NULL;
int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN);
///////////////////////////////////////////////////////////////////////////
// 1. 首先取得连入客户端的地址信息
// 这个 m_lpfnGetAcceptExSockAddrs 不得了啊~~~~~~
// 不但可以取得客户端和本地端的地址信息,还能顺便取出客户端发来的第一组数据,老强大了...
this->m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf,
pIoContext->m_wsaBuf.len - ((sizeof(SOCKADDR_IN)+16)*2),
sizeof(SOCKADDR_IN)+16,
sizeof(SOCKADDR_IN)+16,
(LPSOCKADDR*)&LocalAddr,
&localLen,
(LPSOCKADDR*)&ClientAddr,
&remoteLen);
this->_ShowMessage( _T("客户端 %s:%d 连入."),
inet_ntoa(ClientAddr->sin_addr),
ntohs(ClientAddr->sin_port) );
this->_ShowMessage( _T("客户额 %s:%d 信息:%s."),
inet_ntoa(ClientAddr->sin_addr),
ntohs(ClientAddr->sin_port),
pIoContext->m_wsaBuf.buf );
//////////////////////////////////////////////////////////////////////////////////////////////////////
// 2. 这里需要注意,这里传入的这个是ListenSocket上的Context,这个Context我们还需要用于监听下一个连接
// 所以我还得要将ListenSocket上的Context复制出来一份为新连入的Socket新建一个SocketContext
PER_SOCKET_CONTEXT* pNewSocketContext = new PER_SOCKET_CONTEXT;
pNewSocketContext->m_Socket = pIoContext->m_sockAccept;
memcpy(&(pNewSocketContext->m_ClientAddr),
ClientAddr, sizeof(SOCKADDR_IN));
// 参数设置完毕,将这个Socket和完成端口绑定(这也是一个关键步骤)
if( false==this->_AssociateWithIOCP( pNewSocketContext ) )
{
RELEASE( pNewSocketContext );
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// 3. 继续,建立其下的IoContext,用于在这个Socket上投递第一个Recv数据请求
PER_IO_CONTEXT* pNewIoContext = pNewSocketContext->GetNewIoContext();
pNewIoContext->m_OpType = RECV_POSTED;
pNewIoContext->m_sockAccept = pNewSocketContext->m_Socket;
// 如果Buffer需要保留,就自己拷贝一份出来
//memcpy( pNewIoContext->m_szBuffer,pIoContext->m_szBuffer,MAX_BUFFER_LEN );
// 绑定完毕之后,就可以开始在这个Socket上投递完成请求了
if( false==this->_PostRecv( pNewIoContext) )
{
pNewSocketContext->RemoveContext( pNewIoContext );
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// 4. 如果投递成功,那么就把这个有效的客户端信息,加入到ContextList中去(需要统一管理,方便释放资源)
this->_AddToContextList( pNewSocketContext );
////////////////////////////////////////////////////////////////////////////////////////////////
// 5. 使用完毕之后,把Listen Socket的那个IoContext重置,然后准备投递新的AcceptEx
pIoContext->ResetBuffer();
return this->_PostAccept( pIoContext );
}
////////////////////////////////////////////////////////////////////
// 投递接收数据请求
bool CIOCPModel::_PostRecv( PER_IO_CONTEXT* pIoContext )
{
// 初始化变量
DWORD dwFlags = 0;
DWORD dwBytes = 0;
WSABUF *p_wbuf = &pIoContext->m_wsaBuf;
OVERLAPPED *p_ol = &pIoContext->m_Overlapped;
pIoContext->ResetBuffer();
pIoContext->m_OpType = RECV_POSTED;
// 初始化完成后,,投递WSARecv请求
int nBytesRecv = WSARecv( pIoContext->m_sockAccept,
p_wbuf, 1,
&dwBytes, &dwFlags,
p_ol, NULL );
// 如果返回值错误,并且错误的代码并非是Pending的话,那就说明这个重叠请求失败了
if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))
{
this->_ShowMessage(_T("投递第一个WSARecv失败!"));
return false;
}
return true;
}
/////////////////////////////////////////////////////////////////
// 在有接收的数据到达的时候,进行处理
bool CIOCPModel::_DoRecv( PER_SOCKET_CONTEXT* pSocketContext,
PER_IO_CONTEXT* pIoContext )
{
// 先把上一次的数据显示出现,然后就重置状态,发出下一个Recv请求
SOCKADDR_IN* ClientAddr = &pSocketContext->m_ClientAddr;
this->_ShowMessage( _T("收到 %s:%d 信息:%s"),
inet_ntoa(ClientAddr->sin_addr),
ntohs(ClientAddr->sin_port),
pIoContext->m_wsaBuf.buf );
// 然后开始投递下一个WSARecv请求
return _PostRecv( pIoContext );
}
/////////////////////////////////////////////////////
// 将句柄(Socket)绑定到完成端口中
bool CIOCPModel::_AssociateWithIOCP( PER_SOCKET_CONTEXT *pContext )
{
// 将用于和客户端通信的SOCKET绑定到完成端口中
HANDLE hTemp = CreateIoCompletionPort((HANDLE)pContext->m_Socket,
m_hIOCompletionPort,
(DWORD)pContext, 0);
if (NULL == hTemp)
{
this->_ShowMessage(_T("执行CreateIoCompletionPort()出现错误.错误代码:%d"),
GetLastError());
return false;
}
return true;
}
//====================================================================================
//
// ContextList 相关操作
//
//====================================================================================
//////////////////////////////////////////////////////////////
// 将客户端的相关信息存储到数组中
void CIOCPModel::_AddToContextList( PER_SOCKET_CONTEXT *pHandleData )
{
EnterCriticalSection(&m_csContextList);
m_arrayClientContext.Add(pHandleData);
LeaveCriticalSection(&m_csContextList);
}
////////////////////////////////////////////////////////////////
// 移除某个特定的Context
void CIOCPModel::_RemoveContext( PER_SOCKET_CONTEXT *pSocketContext )
{
EnterCriticalSection(&m_csContextList);
for( int i=0;i<m_arrayClientContext.GetCount();i++ )
{
if( pSocketContext==m_arrayClientContext.GetAt(i) )
{
RELEASE( pSocketContext );
m_arrayClientContext.RemoveAt(i);
break;
}
}
LeaveCriticalSection(&m_csContextList);
}
////////////////////////////////////////////////////////////////
// 清空客户端信息
void CIOCPModel::_ClearContextList()
{
EnterCriticalSection(&m_csContextList);
for( int i=0;i<m_arrayClientContext.GetCount();i++ )
{
delete m_arrayClientContext.GetAt(i);
}
m_arrayClientContext.RemoveAll();
LeaveCriticalSection(&m_csContextList);
}
//====================================================================================
//
// 其他辅助函数定义
//
//====================================================================================
////////////////////////////////////////////////////////////////////
// 获得本机的IP地址
CString CIOCPModel::GetLocalIP()
{
// 获得本机主机名
char hostname[MAX_PATH] = {0};
gethostname(hostname,MAX_PATH);
struct hostent FAR* lpHostEnt = gethostbyname(hostname);
if(lpHostEnt == NULL)
{
return DEFAULT_IP;
}
// 取得IP地址列表中的第一个为返回的IP(因为一台主机可能会绑定多个IP)
LPSTR lpAddr = lpHostEnt->h_addr_list[0];
// 将IP地址转化成字符串形式
struct in_addr inAddr;
memmove(&inAddr,lpAddr,4);
m_strIP = CString( inet_ntoa(inAddr) );
return m_strIP;
}
///////////////////////////////////////////////////////////////////
// 获得本机中处理器的数量
int CIOCPModel::_GetNoOfProcessors()
{
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors;
}
/////////////////////////////////////////////////////////////////////
// 在主界面中显示提示信息
void CIOCPModel::_ShowMessage(const CString szFormat,...) const
{
// 根据传入的参数格式化字符串
CString strMessage;
va_list arglist;
// 处理变长参数
va_start(arglist, szFormat);
strMessage.FormatV(szFormat,arglist);
va_end(arglist);
// 在主界面中显示
CMainDlg* pMain = (CMainDlg*)m_pMain;
if( m_pMain!=NULL )
{
pMain->AddInformation(strMessage);
TRACE( strMessage+_T("\n") );
}
}
/////////////////////////////////////////////////////////////////////
// 判断客户端Socket是否已经断开,否则在一个无效的Socket上投递WSARecv操作会出现异常
// 使用的方法是尝试向这个socket发送数据,判断这个socket调用的返回值
// 因为如果客户端网络异常断开(例如客户端崩溃或者拔掉网线等)的时候
//服务器端是无法收到客户端断开的通知的
bool CIOCPModel::_IsSocketAlive(SOCKET s)
{
int nByteSent=send(s,"",0,0);
if (-1 == nByteSent) return false;
return true;
}
///////////////////////////////////////////////////////////////////
// 显示并处理完成端口上的错误
bool CIOCPModel::HandleError( PER_SOCKET_CONTEXT *pContext,const DWORD& dwErr )
{
// 如果是超时了,就再继续等吧
if(WAIT_TIMEOUT == dwErr)
{
// 确认客户端是否还活着...
if( !_IsSocketAlive( pContext->m_Socket) )
{
this->_ShowMessage( _T("检测到客户端异常退出!") );
this->_RemoveContext( pContext );
return true;
}
else
{
this->_ShowMessage( _T("网络操作超时!重试中...") );
return true;
}
}
// 可能是客户端异常退出了
else if( ERROR_NETNAME_DELETED==dwErr )
{
this->_ShowMessage( _T("检测到客户端异常退出!") );
this->_RemoveContext( pContext );
return true;
}
else
{
this->_ShowMessage( _T("完成端口操作出现错误,线程退出。错误代码:%d"),dwErr );
return false;
}
}
全系列完。
参考链接:http://blog.csdn.net/piggyxp/article/details/6922277
系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) windows完成端口(六)