改编自小猪的完成端口教程 添加了发送 和一些错误检查
头文件
#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; // 这个网络操作所使用的Socket
WSABUF 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; // 每一个客户端连接的Socket
SOCKADDR_IN m_ClientAddr; // 客户端的地址
std::vector<_PER_IO_CONTEXT*> m_arrayIoContext; // 客户端网络操作的上下文数据,
// 也就是说对于每一个客户端Socket,是可以在上面同时投递多个IO请求的
char m_sBuffer[1024*100];
int m_nTotalLen;
IceUtil::Mutex m_IOContextMutex; // 用于IOcontext管理
IceUtil::Mutex m_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;
}
// 从数组中移除一个指定的IoContext
void 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:
// 初始化IOCP
bool _InitializeIOCP();
// 初始化Socket
bool _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);
// 初始化IOCP
if (false == _InitializeIOCP())
{
OS_LOG_INFO("initiocp failed!");
return false;
}
// 初始化Socket
if( 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;
}
/
// 初始化Socket
bool CIOCPModel::_InitializeListenSocket()
{
// AcceptEx 和 GetAcceptExSockaddrs 的GUID,用于导出函数指针
GUID GuidAcceptEx = WSAID_ACCEPTEX;
GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS;
// 服务器地址信息,用于绑定Socket
struct 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_CONTEXT
PER_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);
// 关闭监听Socket
RELEASE(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;
}
// 投递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())
{
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新建一个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;
}
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重置,然后准备投递新的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()))
{
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));
}
// 移除某个特定的Context
void 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)
{
//send
WSABUF *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;
}
问题是 :在客户端失去连接后 感觉有内存泄漏