完成端口

来源:互联网 发布:apache velocity 教程 编辑:程序博客网 时间:2024/05/29 17:05

改编自小猪的完成端口教程 添加了发送 和一些错误检查

////头文件

#ifndef _IOCPMODULE_H_20161209_#define _IOCPMODULE_H_20161209_// winsock 2 的头文件和库#include <winsock2.h>#include <MSWSock.h>#pragma comment(lib,"ws2_32.lib")#include "VpPublic.h"// 如果确实客户端发来的每组数据都比较少,那么就设置得小一些,省内存#define MAX_BUFFER_LEN        1024  // 默认端口#define DEFAULT_PORT          12345    // 默认IP地址#define DEFAULT_IP            ("127.0.0.1")#include <assert.h>#include <vector>#include <string>#include <map>using namespace std;class CCDAPacket;//////////////////////////////////////////////////////////////////// 在完成端口上投递的I/O操作的类型typedef enum _OPERATION_TYPE  {  ACCEPT_POSTED,                     // 标志投递的Accept操作SEND_POSTED,                       // 标志投递的是发送操作RECV_POSTED,                       // 标志投递的是接收操作NULL_POSTED                        // 用于初始化,无意义}OPERATION_TYPE;//====================================================================================////单IO数据结构体定义(用于每一个重叠操作的参数)////====================================================================================typedef struct _PER_IO_CONTEXT{OVERLAPPED     m_Overlapped;                               // 每一个重叠网络操作的重叠结构(针对每一个Socket的每一个操作,都要有一个)              SOCKET         m_sockAccept;                               // 这个网络操作所使用的SocketWSABUF         m_wsaBuf;                                   // WSA类型的缓冲区,用于给重叠操作传参数的char           m_szBuffer[MAX_BUFFER_LEN];                 // 这个是WSABUF里具体存字符的缓冲区OPERATION_TYPE m_OpType;                                   // 标识网络操作的类型(对应上面的枚举)// 初始化_PER_IO_CONTEXT(){ZeroMemory(&m_Overlapped, sizeof(m_Overlapped));  ZeroMemory( m_szBuffer,MAX_BUFFER_LEN );m_sockAccept = INVALID_SOCKET;m_wsaBuf.buf = m_szBuffer;m_wsaBuf.len = MAX_BUFFER_LEN;m_OpType     = NULL_POSTED;}// 释放掉Socket~_PER_IO_CONTEXT(){/*if( m_sockAccept!=INVALID_SOCKET ){closesocket(m_sockAccept);m_sockAccept = INVALID_SOCKET;}*/}// 重置缓冲区内容void ResetBuffer(){ZeroMemory( m_szBuffer,MAX_BUFFER_LEN );}} PER_IO_CONTEXT, *PPER_IO_CONTEXT;//====================================================================================////单句柄数据结构体定义(用于每一个完成端口,也就是每一个Socket的参数)////====================================================================================typedef struct _PER_SOCKET_CONTEXT{  SOCKET      m_Socket;                                  // 每一个客户端连接的SocketSOCKADDR_IN m_ClientAddr;                              // 客户端的地址std::vector<_PER_IO_CONTEXT*> m_arrayIoContext;             // 客户端网络操作的上下文数据,                                                       // 也就是说对于每一个客户端Socket,是可以在上面同时投递多个IO请求的charm_sBuffer[1024*100];intm_nTotalLen;IceUtil::Mutex      m_IOContextMutex;            // 用于IOcontext管理IceUtil::Mutexm_oDataParseMutex;//用数据buff// 初始化_PER_SOCKET_CONTEXT(){m_Socket = INVALID_SOCKET;memset(&m_ClientAddr, 0, sizeof(m_ClientAddr)); memset(m_sBuffer,0,sizeof(m_sBuffer));m_nTotalLen = 0;}// 释放资源~_PER_SOCKET_CONTEXT(){if( m_Socket!=INVALID_SOCKET ){closesocket( m_Socket );    m_Socket = INVALID_SOCKET;}IceUtil::Mutex::Lock guard(m_IOContextMutex);for (int i = 0;i<m_arrayIoContext.size();i++){delete m_arrayIoContext[i];}m_arrayIoContext.clear();std::string sip = inet_ntoa(m_ClientAddr.sin_addr);int nPort = m_ClientAddr.sin_port;OS_LOG_INFO("客户端 资源释放 IP:"<< sip.c_str()<<" Port:"<<nPort );}_PER_IO_CONTEXT* GetNewIoContext(){IceUtil::Mutex::Lock guard(m_IOContextMutex);_PER_IO_CONTEXT* p = new _PER_IO_CONTEXT;m_arrayIoContext.push_back( p );return p;}// 从数组中移除一个指定的IoContextvoid RemoveContext( _PER_IO_CONTEXT* pContext ){//assert( pContext!=NULL && m_arrayIoContext.size()>0 );if( pContext ==NULL || m_arrayIoContext.size()==0 ){OS_LOG_INFO("移除失败: arraysize:"<<m_arrayIoContext.size());return ;}IceUtil::Mutex::Lock guard(m_IOContextMutex);std::vector<_PER_IO_CONTEXT*>::iterator it_find = m_arrayIoContext.begin();for (; it_find != m_arrayIoContext.end();it_find++){if (pContext == *it_find){delete pContext;pContext = NULL;m_arrayIoContext.erase(it_find);//OS_LOG_INFO("成功移除!");break;}}}void ParsePacket(PER_IO_CONTEXT* pIoContext);} PER_SOCKET_CONTEXT, *PPER_SOCKET_CONTEXT;//====================================================================================////CIOCPModel类定义////====================================================================================// 工作者线程的线程参数class CIOCPModel;typedef struct _tagThreadParams_WORKER{CIOCPModel* pIOCPModel;                                   // 类指针,用于调用类中的函数int         nThreadNo;                                    // 线程编号} THREADPARAMS_WORKER,*PTHREADPARAM_WORKER; // CIOCPModel类class CIOCPModel{public:CIOCPModel(void);~CIOCPModel(void);public:// 启动服务器bool Start();//停止服务器void Stop();// 加载Socket库bool LoadSocketLib();// 卸载Socket库,彻底完事void UnloadSocketLib() { WSACleanup(); }// 获得本机的IP地址std::string  GetLocalIP();// 设置监听端口void SetPort( const int& nPort ) { m_nPort=nPort; }protected:// 初始化IOCPbool _InitializeIOCP();// 初始化Socketbool _InitializeListenSocket();// 最后释放资源void _DeInitialize();// 投递Accept请求bool _PostAccept( PER_IO_CONTEXT* pAcceptIoContext ); // 投递接收数据请求bool _PostRecv( PER_IO_CONTEXT* pIoContext );// 在有客户端连入的时候,进行处理bool _DoAccpet( PER_SOCKET_CONTEXT* pSocketContext, PER_IO_CONTEXT* pIoContext );// 在有接收的数据到达的时候,进行处理bool _DoRecv( PER_SOCKET_CONTEXT* pSocketContext, PER_IO_CONTEXT* pIoContext );// 将客户端的相关信息存储到数组中void _AddToContextList( PER_SOCKET_CONTEXT *pSocketContext );// 将客户端的信息从数组中移除void _RemoveContext( PER_SOCKET_CONTEXT *pSocketContext );// 清空客户端信息void _ClearContextList();//判断指定socketcontext 是否还在链表中bool IsSocketInContextList(PER_SOCKET_CONTEXT* pSocketContext);// 将句柄绑定到完成端口中bool _AssociateWithIOCP( PER_SOCKET_CONTEXT *pContext);// 处理完成端口上的错误bool HandleError( PER_SOCKET_CONTEXT *pContext,const DWORD& dwErr );// 线程函数,为IOCP请求服务的工作者线程static DWORD WINAPI _WorkerThread(LPVOID lpParam);// 获得本机的处理器数量int _GetNoOfProcessors();// 判断客户端Socket是否已经断开bool _IsSocketAlive(SOCKET s);bool _PostSend(PER_IO_CONTEXT* pIoContext);bool _DoSend(PER_SOCKET_CONTEXT* pSocketContext,PER_IO_CONTEXT* pIoContext);public://外部接口//int SendPacket(unsigned int ip, unsigned short port, CCDAPacket& oPacket);int SendPacket(unsigned int ip, unsigned short port, char* pBuff ,int nBufflen);private:HANDLE                       m_hShutdownEvent;              // 用来通知线程系统退出的事件,为了能够更好的退出线程HANDLE                       m_hIOCompletionPort;           // 完成端口的句柄HANDLE*                      m_phWorkerThreads;             // 工作者线程的句柄指针int                     m_nThreads;                    // 生成的线程数量std::string                  m_strIP;                       // 服务器端的IP地址int                          m_nPort;                       // 服务器端的监听端口IceUtil::Mutex          m_ContextListMutext;               // 用于Worker线程同步的互斥量//std::vector<PER_SOCKET_CONTEXT*>  m_arrayClientContext;          // 客户端Socket的Context信息        std::map<SOCKET,PER_SOCKET_CONTEXT*>  m_arrayClientContext;          // 客户端Socket的Context信息        PER_SOCKET_CONTEXT*          m_pListenContext;              // 用于监听的Socket的Context信息LPFN_ACCEPTEX                m_lpfnAcceptEx;                // AcceptEx 和 GetAcceptExSockaddrs 的函数指针,用于调用这两个扩展函数LPFN_GETACCEPTEXSOCKADDRS    m_lpfnGetAcceptExSockAddrs; };#endif

cplusplus

#include "IOCPModel.h"#include "VpPublic.h"#include "base/baseUtil.h"#include "CDAPacket/CdaPacket.h"// 每一个处理器上产生多少个线程(为了最大限度的提升服务器性能,详见配套文档)#define WORKER_THREADS_PER_PROCESSOR 2// 同时投递的Accept请求的数量(这个要根据实际的情况灵活设置)#define MAX_POST_ACCEPT              10// 传递给Worker线程的退出信号#define EXIT_CODE                    NULL// 释放指针和句柄资源的宏// 释放指针宏#define RELEASE(x)                      {if(x != NULL ){delete x;x=NULL;}}// 释放句柄宏#define RELEASE_HANDLE(x)               {if(x != NULL && x!=INVALID_HANDLE_VALUE){ CloseHandle(x);x = NULL;}}// 释放Socket宏#define RELEASE_SOCKET(x)               {if(x !=INVALID_SOCKET) { closesocket(x);x=INVALID_SOCKET;}}void _PER_SOCKET_CONTEXT::ParsePacket(PER_IO_CONTEXT* pIoContext){if (pIoContext == NULL){OS_LOG_INFO("pIoContext FAILED!");return ;}memcpy( m_sBuffer,pIoContext->m_szBuffer,pIoContext->m_Overlapped.InternalHigh);m_nTotalLen +=  pIoContext->m_Overlapped.InternalHigh;while (1){if (m_nTotalLen < CDA_HEAD_LEN){//OS_LOG_INFO("长度未够 totallen:"<< m_nTotalLen);break;}int nPacketLen = 0;char* pBuff = m_sBuffer;DECODE_INT(pBuff,nPacketLen);if (nPacketLen < CDA_HEAD_LEN || nPacketLen > m_nTotalLen){OS_LOG_INFO("数据包解析出错!");break;}CCDAPacket* pPacket = CCDAPacket::NewPacket(m_sBuffer);if (pPacket == NULL){OS_LOG_INFO("new packet error");break;}else{int nDecodelen = pPacket->DecodePacket(m_sBuffer,nPacketLen);if (nPacketLen != nDecodelen){OS_LOG_INFO("DECODE PACKET ERROR");delete pPacket;pPacket = NULL;break;}pPacket->m_pSocketContext = this;//为检查心跳超时 打印if (pPacket->m_uMsgId == CDA_REPORT_GPS_REQ){//直接打印ip 端口用以区别std::string sip = inet_ntoa(m_ClientAddr.sin_addr);int nPort = m_ClientAddr.sin_port;int nSeqNo = pPacket->m_uSeqNo;OS_LOG_INFO("收到GPS IP:"<< sip.c_str()<<" Port:"<< nPort<<" nSeqNo:"<<nSeqNo);}CCDAPacket::OnPacket(pPacket);if (m_nTotalLen >= nPacketLen){memmove(m_sBuffer,m_sBuffer+nPacketLen,m_nTotalLen-nPacketLen);m_nTotalLen -= nPacketLen;}else{OS_LOG_INFO("数据包出错");break;}}}}CIOCPModel::CIOCPModel(void):m_nThreads(0),m_hShutdownEvent(NULL),m_hIOCompletionPort(NULL),m_phWorkerThreads(NULL),m_strIP(DEFAULT_IP),m_nPort(DEFAULT_PORT),m_lpfnAcceptEx( NULL ),m_pListenContext( NULL ){}CIOCPModel::~CIOCPModel(void){// 确保资源彻底释放this->Stop();}///////////////////////////////////////////////////////////////////// 工作者线程:  为IOCP请求服务的工作者线程///////////////////////////////////////////////////////////////////DWORD WINAPI CIOCPModel::_WorkerThread(LPVOID lpParam){    THREADPARAMS_WORKER* pParam = (THREADPARAMS_WORKER*)lpParam;CIOCPModel* pIOCPModel = (CIOCPModel*)pParam->pIOCPModel;int nThreadNo = (int)pParam->nThreadNo;OVERLAPPED           *pOverlapped = NULL;PER_SOCKET_CONTEXT   *pSocketContext = NULL;DWORD                dwBytesTransfered = 0;// 循环处理请求,知道接收到Shutdown信息为止while (WAIT_OBJECT_0 != WaitForSingleObject(pIOCPModel->m_hShutdownEvent, 0)){BOOL bReturn = GetQueuedCompletionStatus(pIOCPModel->m_hIOCompletionPort,&dwBytesTransfered,(PULONG_PTR)&pSocketContext,&pOverlapped,INFINITE);// 如果收到的是退出标志,则直接退出if ( EXIT_CODE==(DWORD)pSocketContext ){OS_LOG_INFO("收到退出标志:EXIT_CODE");break;}// 判断是否出现了错误if( !bReturn )  {  DWORD dwErr = GetLastError();// 显示一下提示信息if( !pIOCPModel->HandleError( pSocketContext,dwErr ) ){//break;}continue;  }  else  {  // 读取传入的参数PER_IO_CONTEXT* pIoContext = CONTAINING_RECORD(pOverlapped, PER_IO_CONTEXT, m_Overlapped);  // 判断是否有客户端断开了if((0 == dwBytesTransfered) && ( RECV_POSTED==pIoContext->m_OpType || SEND_POSTED==pIoContext->m_OpType))  {  // 释放掉对应的资源std::string sip = inet_ntoa(pSocketContext->m_ClientAddr.sin_addr);int nPort = pSocketContext->m_ClientAddr.sin_port;OS_LOG_INFO("客户端主动断开连接 IP:"<< sip.c_str()<<" Port:"<< nPort);pIOCPModel->_RemoveContext( pSocketContext ); continue;  }  else{switch( pIoContext->m_OpType )  {  case ACCEPT_POSTED:{ pIOCPModel->_DoAccpet( pSocketContext, pIoContext );}break;case RECV_POSTED:{pIOCPModel->_DoRecv( pSocketContext,pIoContext );}break;case SEND_POSTED:{pIOCPModel->_DoSend(pSocketContext,pIoContext);}break;default:OS_LOG_INFO("_WorkThread中的 pIoContext->m_OpType 参数异常");break;}}}}OS_LOG_INFO("工作者线程 %d 号退出."<< nThreadNo);// 释放线程参数RELEASE(lpParam);return 0;}//====================================================================================////    系统初始化和终止////====================================================================================bool CIOCPModel::LoadSocketLib(){ GetLocalIP();return true;}////////////////////////////////////////////////////////////////////启动服务器bool CIOCPModel::Start(){// 建立系统退出的事件通知m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);// 初始化IOCPif (false == _InitializeIOCP()){OS_LOG_INFO("initiocp failed!");return false;}// 初始化Socketif( false==_InitializeListenSocket() ){OS_LOG_INFO("initlistensocket failed!");this->_DeInitialize();return false;}return true;}//////////////////////////////////////////////////////////////////////开始发送系统退出消息,退出完成端口和线程资源void CIOCPModel::Stop(){if( m_pListenContext!=NULL && m_pListenContext->m_Socket!=INVALID_SOCKET ){// 激活关闭消息通知SetEvent(m_hShutdownEvent);for (int i = 0; i < m_nThreads; i++){// 通知所有的完成端口操作退出PostQueuedCompletionStatus(m_hIOCompletionPort, 0, (DWORD)EXIT_CODE, NULL);}// 等待所有的客户端资源退出WaitForMultipleObjects(m_nThreads, m_phWorkerThreads, TRUE, INFINITE);// 清除客户端列表信息this->_ClearContextList();// 释放其他资源this->_DeInitialize();}}////////////////////////////////// 初始化完成端口bool CIOCPModel::_InitializeIOCP(){// 建立第一个完成端口m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );OS_LOG_INFO("IOCP handle:"<<m_hIOCompletionPort);if ( NULL == m_hIOCompletionPort){OS_LOG_INFO("IOCP Create Failed");return false;}// 根据本机中的处理器数量,建立对应的线程数m_nThreads = WORKER_THREADS_PER_PROCESSOR * _GetNoOfProcessors();// 为工作者线程初始化句柄m_phWorkerThreads = new HANDLE[m_nThreads];// 根据计算出来的数量建立工作者线程DWORD nThreadID;for (int i = 0; i < m_nThreads; i++){THREADPARAMS_WORKER* pThreadParams = new THREADPARAMS_WORKER;pThreadParams->pIOCPModel = this;pThreadParams->nThreadNo  = i+1;m_phWorkerThreads[i] = ::CreateThread(0, 0, _WorkerThread, (void *)pThreadParams, 0, &nThreadID);}return true;}/////////////////////////////////////////////////////////////////// 初始化Socketbool CIOCPModel::_InitializeListenSocket(){// AcceptEx 和 GetAcceptExSockaddrs 的GUID,用于导出函数指针GUID GuidAcceptEx = WSAID_ACCEPTEX;  GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; // 服务器地址信息,用于绑定Socketstruct sockaddr_in ServerAddress;// 生成用于监听的Socket的信息m_pListenContext = new PER_SOCKET_CONTEXT;// 需要使用重叠IO,必须得使用WSASocket来建立Socket,才可以支持重叠IO操作m_pListenContext->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);if (INVALID_SOCKET == m_pListenContext->m_Socket) {OS_LOG_INFO("创建listen socket 失败");return false;}// 将Listen Socket绑定至完成端口中HANDLE handle = CreateIoCompletionPort( (HANDLE)m_pListenContext->m_Socket, m_hIOCompletionPort,(DWORD)m_pListenContext, 0);if( NULL== handle)  {  OS_LOG_INFO("完成端口绑定失败!");RELEASE_SOCKET( m_pListenContext->m_Socket );return false;}// 填充地址信息ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));ServerAddress.sin_family = AF_INET;                     ServerAddress.sin_addr.s_addr = inet_addr(m_strIP.c_str());         ServerAddress.sin_port = htons(m_nPort);                          // 绑定地址和端口if (SOCKET_ERROR == bind(m_pListenContext->m_Socket, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress))) {OS_LOG_INFO("listen socket bind failesd!");return false;}// 开始进行监听if (SOCKET_ERROR == listen(m_pListenContext->m_Socket,SOMAXCONN)){OS_LOG_INFO("listen failed!");return false;}// 调用WSAIotl 可使用 重叠IO// 使用AcceptEx函数,因为这个是属于WinSock2规范之外的微软另外提供的扩展函数// 所以需要额外获取一下函数的指针,// 获取AcceptEx函数指针DWORD dwBytes = 0;  if(SOCKET_ERROR == WSAIoctl(m_pListenContext->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), &m_lpfnAcceptEx, sizeof(m_lpfnAcceptEx), &dwBytes, NULL, NULL))  {  this->_DeInitialize();return false;  }  // 获取GetAcceptExSockAddrs函数指针,也是同理if(SOCKET_ERROR == WSAIoctl(m_pListenContext->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidGetAcceptExSockAddrs,sizeof(GuidGetAcceptExSockAddrs), &m_lpfnGetAcceptExSockAddrs, sizeof(m_lpfnGetAcceptExSockAddrs),   &dwBytes, NULL, NULL))  {  this->_DeInitialize();return false; }  // 为AcceptEx 准备参数,然后投递AcceptEx I/O请求for( int i=0;i<MAX_POST_ACCEPT;i++ ){// 新建一个IO_CONTEXTPER_IO_CONTEXT* pAcceptIoContext = m_pListenContext->GetNewIoContext();if( false==this->_PostAccept( pAcceptIoContext ) ){m_pListenContext->RemoveContext(pAcceptIoContext);return false;}}return true;}//////////////////////////////////////////////////////////////最后释放掉所有资源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);// 关闭监听SocketRELEASE(m_pListenContext);}//====================================================================================////    投递完成端口请求////====================================================================================//////////////////////////////////////////////////////////////////// 投递Accept请求bool CIOCPModel::_PostAccept( PER_IO_CONTEXT* pAcceptIoContext ){if( INVALID_SOCKET == m_pListenContext->m_Socket ){OS_LOG_INFO("post accept socket NULL");return false;}// 准备参数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 )  {  return false;  } // 投递AcceptExif(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())  {  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);  //////////////////////////////////////////////////////////////////////////////////////////////////////// 2. 这里需要注意,这里传入的这个是ListenSocket上的Context,这个Context我们还需要用于监听下一个连接// 所以我还得要将ListenSocket上的Context复制出来一份为新连入的Socket新建一个SocketContextPER_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;}std::string sip =  inet_ntoa(ClientAddr->sin_addr);OS_LOG_INFO("Client connected from:IP "<<sip.c_str()<<" PORT:"<<ClientAddr->sin_port);///////////////////////////////////////////////////////////////////////////////////////////////////// 3. 继续,建立其下的IoContext,用于在这个Socket上投递第一个Recv数据请求PER_IO_CONTEXT* pNewIoContext = pNewSocketContext->GetNewIoContext();pNewIoContext->m_OpType       = RECV_POSTED;pNewIoContext->m_sockAccept   = pNewSocketContext->m_Socket;// 如果Buffer需要保留,就自己拷贝一份出来//处理第一个包pNewSocketContext->ParsePacket(pIoContext);// 绑定完毕之后,就可以开始在这个Socket上投递完成请求了if( false==this->_PostRecv( pNewIoContext) ){pNewSocketContext->RemoveContext( pNewIoContext );return false;}/////////////////////////////////////////////////////////////////////////////////////////////////// 4. 如果投递成功,那么就把这个有效的客户端信息,加入到ContextList中去(需要统一管理,方便释放资源)this->_AddToContextList( pNewSocketContext );////////////////////////////////////////////////////////////////////////////////////////////////// 5. 使用完毕之后,把Listen Socket的那个IoContext重置,然后准备投递新的AcceptExpIoContext->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())){return false;}return true;}/////////////////////////////////////////////////////////////////// 在有接收的数据到达的时候,进行处理bool CIOCPModel::_DoRecv( PER_SOCKET_CONTEXT* pSocketContext, PER_IO_CONTEXT* pIoContext ){// 先把上一次的数据显示出现,然后就重置状态,发出下一个Recv请求SOCKADDR_IN* ClientAddr = &pSocketContext->m_ClientAddr;if (IsSocketInContextList(pSocketContext)){//打解包pSocketContext->ParsePacket(pIoContext);}// 然后开始投递下一个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){return false;}return true;}//====================================================================================////    ContextList 相关操作////====================================================================================//////////////////////////////////////////////////////////////// 将客户端的相关信息存储到数组中void CIOCPModel::_AddToContextList( PER_SOCKET_CONTEXT *pHandleData ){IceUtil::Mutex::Lock guard(m_ContextListMutext);//m_arrayClientContext.push_back(pHandleData);m_arrayClientContext.insert(std::make_pair(pHandleData->m_Socket,pHandleData));}//////////////////////////////////////////////////////////////////移除某个特定的Contextvoid CIOCPModel::_RemoveContext( PER_SOCKET_CONTEXT *pSocketContext ){IceUtil::Mutex::Lock guard(m_ContextListMutext);//vector<PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.begin();//for (;it_find != m_arrayClientContext.end();it_find++)//{//if (*it_find == pSocketContext)//{//RELEASE(pSocketContext);//m_arrayClientContext.erase(it_find);//break;//}//}std::map<SOCKET,PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.find(pSocketContext->m_Socket);if (it_find != m_arrayClientContext.end()){RELEASE(pSocketContext);m_arrayClientContext.erase(it_find);}if (m_arrayClientContext.size() == 0){OS_LOG_INFO("客户端已经完全丢失连接");}}////////////////////////////////////////////////////////////////// 清空客户端信息void CIOCPModel::_ClearContextList(){IceUtil::Mutex::Lock guard(m_ContextListMutext);//for(int i = 0;i<m_arrayClientContext.size();i++)//{//delete m_arrayClientContext[i];//}//m_arrayClientContext.clear();std::map<SOCKET,PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.begin();for (;it_find != m_arrayClientContext.end();it_find++){delete it_find->second;}m_arrayClientContext.clear();}///////////////////////////////////////////////////////////////////判断指定的socket 是否在维护链表中bool CIOCPModel::IsSocketInContextList(PER_SOCKET_CONTEXT* pSocketContext){IceUtil::Mutex::Lock guard(m_ContextListMutext);//vector<PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.begin();//for (;it_find != m_arrayClientContext.end(); it_find++)//{//if (*it_find == pSocketContext)//{//return true;//}//}std::map<SOCKET,PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.find(pSocketContext->m_Socket);if (it_find != m_arrayClientContext.end()){return true;}return false;}//====================================================================================////       其他辅助函数定义////====================================================================================////////////////////////////////////////////////////////////////////// 获得本机的IP地址std::string 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 =  inet_ntoa(inAddr) ;return m_strIP;}///////////////////////////////////////////////////////////////////// 获得本机中处理器的数量int CIOCPModel::_GetNoOfProcessors(){SYSTEM_INFO si;GetSystemInfo(&si);return si.dwNumberOfProcessors;}/////////////////////////////////////////////////////////////////////// 判断客户端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) ){std::string sip = inet_ntoa(pContext->m_ClientAddr.sin_addr);int nPort = pContext->m_ClientAddr.sin_port;OS_LOG_INFO("客户端异常退出1 IP:"<< sip.c_str()<<" Port:"<< nPort);this->_RemoveContext( pContext );return true;}else{return true;}}  // 可能是客户端异常退出了else if( ERROR_NETNAME_DELETED==dwErr ){std::string sip = inet_ntoa(pContext->m_ClientAddr.sin_addr);int nPort = pContext->m_ClientAddr.sin_port;OS_LOG_INFO("客户端异常退出2 IP:"<< sip.c_str()<<" Port:"<< nPort);this->_RemoveContext( pContext );return true;}else{if (dwErr == 995){OS_LOG_INFO("遗留堵塞任务,该socketcontext 因超时已被删除:"<<dwErr);} else{OS_LOG_INFO("other error!:"<<dwErr);}return false;}}bool CIOCPModel::_PostSend(PER_IO_CONTEXT* pIoContext){//sendWSABUF *p_wsbuf = &pIoContext->m_wsaBuf;OVERLAPPED* p_ol = &pIoContext->m_Overlapped;int nBufferlen = pIoContext->m_wsaBuf.len;DWORD dwBytes,dwFlags = 0;int nBytesSend = WSASend(pIoContext->m_sockAccept,p_wsbuf,1,&dwBytes,dwFlags,p_ol,NULL);if ((SOCKET_ERROR == nBytesSend) && (WSA_IO_PENDING != WSAGetLastError())){//投递Send 失败int nRet = WSAGetLastError();OS_LOG_INFO("PostSend failed,nRet:"<<nRet);return false;}return true;}bool CIOCPModel::_DoSend(PER_SOCKET_CONTEXT* pSocketContext,PER_IO_CONTEXT* pIoContext){if (pSocketContext != NULL && pIoContext != NULL && IsSocketInContextList(pSocketContext)){pSocketContext->RemoveContext(pIoContext);return true;}else{OS_LOG_INFO("客户端已经断开连接:");}return false;} /*int CIOCPModel::SendPacket(unsigned int ip, unsigned short port, CCDAPacket& oPacket){EnterCriticalSection(&m_csContextList);int nRet = 0;vector<PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.begin();for (;it_find != m_arrayClientContext.end();it_find++){if ((*it_find)->m_ClientAddr.sin_addr.s_addr == ip && (*it_find)->m_ClientAddr.sin_port == port){PER_IO_CONTEXT* pIoContext = (*it_find)->GetNewIoContext(); pIoContext->m_sockAccept = (*it_find)->m_Socket;pIoContext->m_OpType = SEND_POSTED;oPacket.EncodePacket(pIoContext->m_szBuffer,oPacket.len());pIoContext->m_wsaBuf.buf = pIoContext->m_szBuffer;pIoContext->m_wsaBuf.len = oPacket.len();memset(&(pIoContext->m_Overlapped), 0, sizeof( OVERLAPPED ) );nRet = _PostSend(pIoContext);break;}}LeaveCriticalSection(&m_csContextList);return nRet;}*/int CIOCPModel::SendPacket(unsigned int ip, unsigned short port, char* pBuff ,int nBufflen){IceUtil::Mutex::Lock guard(m_ContextListMutext);int nRet = 0;//vector<PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.begin();//for (;it_find != m_arrayClientContext.end();it_find++)//{std::map<SOCKET,PER_SOCKET_CONTEXT*>::iterator it_find = m_arrayClientContext.begin();for (;it_find != m_arrayClientContext.end();it_find++){if (it_find->second->m_ClientAddr.sin_addr.s_addr == ip && it_find->second->m_ClientAddr.sin_port == port){PER_IO_CONTEXT* pIoContext = it_find->second->GetNewIoContext(); pIoContext->m_sockAccept = it_find->second->m_Socket;pIoContext->m_OpType = SEND_POSTED;//oPacket.EncodePacket(pIoContext->m_szBuffer,oPacket.len());memcpy(pIoContext->m_szBuffer,pBuff,nBufflen);pIoContext->m_wsaBuf.buf = pIoContext->m_szBuffer;//pIoContext->m_wsaBuf.len = oPacket.len();pIoContext->m_wsaBuf.len = nBufflen;memset(&(pIoContext->m_Overlapped), 0, sizeof( OVERLAPPED ) );nRet = _PostSend(pIoContext);delete []pBuff;break;}}return nRet;}
问题是 :在客户端失去连接后 感觉有内存泄漏


0 0
原创粉丝点击