|
@@ -0,0 +1,818 @@
|
|
|
+#include "StdAfx.h"
|
|
|
+#include "IOCPModel.h"
|
|
|
+//#include "MainDlg.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;}}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+CIOCPModel::CIOCPModel(void):
|
|
|
+ m_nThreads(0),
|
|
|
+ m_hShutdownEvent(NULL),
|
|
|
+ m_hIOCompletionPort(NULL),
|
|
|
+ m_phWorkerThreads(NULL),
|
|
|
+ m_strIP(DEFAULT_IP),
|
|
|
+ m_nPort(DEFAULT_PORT),
|
|
|
+ m_pMain(NULL),
|
|
|
+ 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;
|
|
|
+
|
|
|
+ pIOCPModel->_ShowMessage(_T("工作者线程启动,ID: %d."),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 )
|
|
|
+ {
|
|
|
+ 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))
|
|
|
+ {
|
|
|
+ pIOCPModel->_ShowMessage( _T("客户端 %s:%d 断开连接."),inet_ntoa(pSocketContext->m_ClientAddr.sin_addr), ntohs(pSocketContext->m_ClientAddr.sin_port) );
|
|
|
+
|
|
|
+ // 释放掉对应的资源
|
|
|
+ pIOCPModel->_RemoveContext( pSocketContext );
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ switch( pIoContext->m_OpType )
|
|
|
+ {
|
|
|
+ // Accept
|
|
|
+ case ACCEPT_POSTED:
|
|
|
+ {
|
|
|
+
|
|
|
+ // 为了增加代码可读性,这里用专门的_DoAccept函数进行处理连入请求
|
|
|
+ pIOCPModel->_DoAccpet( pSocketContext, pIoContext );
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ // RECV
|
|
|
+ case RECV_POSTED:
|
|
|
+ {
|
|
|
+ // 为了增加代码可读性,这里用专门的_DoRecv函数进行处理接收请求
|
|
|
+ pIOCPModel->_DoRecv( pSocketContext,pIoContext );
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ // SEND
|
|
|
+ // 这里略过不写了,要不代码太多了,不容易理解,Send操作相对来讲简单一些
|
|
|
+ case SEND_POSTED:
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // 不应该执行到这里
|
|
|
+ TRACE(_T("_WorkThread中的 pIoContext->m_OpType 参数异常.\n"));
|
|
|
+ break;
|
|
|
+ } //switch
|
|
|
+ }//if
|
|
|
+ }//if
|
|
|
+
|
|
|
+ }//while
|
|
|
+
|
|
|
+ TRACE(_T("工作者线程 %d 号退出.\n"),nThreadNo);
|
|
|
+
|
|
|
+ // 释放线程参数
|
|
|
+ RELEASE(lpParam);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+//====================================================================================
|
|
|
+//
|
|
|
+// 系统初始化和终止
|
|
|
+//
|
|
|
+//====================================================================================
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// 初始化WinSock 2.2
|
|
|
+bool CIOCPModel::LoadSocketLib()
|
|
|
+{
|
|
|
+ WSADATA wsaData;
|
|
|
+ int nResult;
|
|
|
+ nResult = WSAStartup(MAKEWORD(2,2), &wsaData);
|
|
|
+ // 错误(一般都不可能出现)
|
|
|
+ if (NO_ERROR != nResult)
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("初始化WinSock 2.2失败!\n"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+//////////////////////////////////////////////////////////////////
|
|
|
+// 启动服务器
|
|
|
+bool CIOCPModel::Start(unsigned int port)
|
|
|
+{
|
|
|
+ // 初始化线程互斥量
|
|
|
+ InitializeCriticalSection(&m_csContextList);
|
|
|
+
|
|
|
+ // 建立系统退出的事件通知
|
|
|
+ m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
+
|
|
|
+ // 初始化IOCP
|
|
|
+ if (false == _InitializeIOCP())
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("初始化IOCP失败!\n"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("\nIOCP初始化完毕\n."));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化Socket
|
|
|
+ if( false==_InitializeListenSocket(port) )
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("Listen Socket初始化失败!\n"));
|
|
|
+ this->_DeInitialize();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("Listen Socket初始化完毕."));
|
|
|
+ }
|
|
|
+
|
|
|
+ this->_ShowMessage(_T("系统准备就绪,等候连接....\n"));
|
|
|
+
|
|
|
+ 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();
|
|
|
+
|
|
|
+ this->_ShowMessage(_T("停止监听\n"));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+////////////////////////////////
|
|
|
+// 初始化完成端口
|
|
|
+bool CIOCPModel::_InitializeIOCP()
|
|
|
+{
|
|
|
+ // 建立第一个完成端口
|
|
|
+ m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );
|
|
|
+
|
|
|
+ if ( NULL == m_hIOCompletionPort)
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("建立完成端口失败!错误代码: %d!\n"), WSAGetLastError());
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ TRACE(" 建立 _WorkerThread %d 个.\n", m_nThreads );
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/////////////////////////////////////////////////////////////////
|
|
|
+// 初始化Socket
|
|
|
+bool CIOCPModel::_InitializeListenSocket(unsigned int port)
|
|
|
+{
|
|
|
+ // 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)
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("初始化Socket失败,错误代码: %d.\n"), WSAGetLastError());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ TRACE("WSASocket() 完成.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将Listen Socket绑定至完成端口中
|
|
|
+ if( NULL== CreateIoCompletionPort( (HANDLE)m_pListenContext->m_Socket, m_hIOCompletionPort,(DWORD)m_pListenContext, 0))
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("绑定 Listen Socket至完成端口失败!错误代码: %d/n"), WSAGetLastError());
|
|
|
+ RELEASE_SOCKET( m_pListenContext->m_Socket );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ TRACE("Listen Socket绑定完成端口 完成.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充地址信息
|
|
|
+ ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));
|
|
|
+ ServerAddress.sin_family = AF_INET;
|
|
|
+ // 这里可以绑定任何可用的IP地址,或者绑定一个指定的IP地址
|
|
|
+ ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
+ //ServerAddress.sin_addr.s_addr = inet_addr(m_strIP.GetString());
|
|
|
+ ServerAddress.sin_port = htons(port);
|
|
|
+
|
|
|
+ // 绑定地址和端口
|
|
|
+ if (SOCKET_ERROR == bind(m_pListenContext->m_Socket, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress)))
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("bind()函数执行错误! 错误代码: %d/n"), WSAGetLastError());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ TRACE("bind() 完成.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始进行监听
|
|
|
+ if (SOCKET_ERROR == listen(m_pListenContext->m_Socket,SOMAXCONN))
|
|
|
+ {
|
|
|
+ this->_ShowMessage(_T("Listen()函数执行出现错误; 错误代码: %d/n"), WSAGetLastError());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ TRACE("Listen() 完成.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用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->_ShowMessage(_T("WSAIoctl 未能获取AcceptEx函数指针。错误代码: %d\n"), WSAGetLastError());
|
|
|
+ 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->_ShowMessage(_T("WSAIoctl 未能获取GuidGetAcceptExSockAddrs函数指针。错误代码: %d\n"), WSAGetLastError());
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this->_ShowMessage( _T("投递 %d 个AcceptEx请求完毕"),MAX_POST_ACCEPT );
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ 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 );
|
|
|
+ if ( Global::g_bEnableLog )
|
|
|
+ {
|
|
|
+ // 解析Json字符串;
|
|
|
+ Global::TLog tlog;
|
|
|
+ cJSON *pJson = cJSON_Parse(Global::DeCode_URLUNICODE(pIoContext->m_wsaBuf.buf).c_str());
|
|
|
+ if ( pJson )
|
|
|
+ {
|
|
|
+ tlog.report_type = cJSON_GetObjectItem(pJson, _T("ReportType")) ? cJSON_GetObjectItem(pJson, _T("ReportType"))->valuestring : "";
|
|
|
+ tlog.report_data = cJSON_GetObjectItem(pJson, _T("prinMsg")) ? cJSON_GetObjectItem(pJson, _T("prinMsg"))->valuestring : "";
|
|
|
+ if ( _tcscmp(tlog.report_type.c_str(), _T("printLog")) == 0 )
|
|
|
+ {
|
|
|
+ Global::WritePythonLog(tlog.report_data.c_str());
|
|
|
+ Global::g_time = time(NULL);
|
|
|
+ Global::g_lastTime = COleDateTime::GetCurrentTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ cJSON_Delete(pJson);
|
|
|
+ pJson = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
+ // 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 );
|
|
|
+
|
|
|
+ if ( Global::g_bEnableLog )
|
|
|
+ {
|
|
|
+ // 解析Json字符串;
|
|
|
+ Global::TLog tlog;
|
|
|
+ cJSON *pJson = cJSON_Parse(Global::DeCode_URLUNICODE(pIoContext->m_wsaBuf.buf).c_str());
|
|
|
+ if ( pJson )
|
|
|
+ {
|
|
|
+ tlog.report_type = cJSON_GetObjectItem(pJson, _T("ReportType")) ? cJSON_GetObjectItem(pJson, _T("ReportType"))->valuestring : "";
|
|
|
+ tlog.report_data = cJSON_GetObjectItem(pJson, _T("prinMsg")) ? cJSON_GetObjectItem(pJson, _T("prinMsg"))->valuestring : "";
|
|
|
+ if ( _tcscmp(tlog.report_type.c_str(), _T("printLog")) == 0 )
|
|
|
+ {
|
|
|
+ Global::WritePythonLog(tlog.report_data.c_str());
|
|
|
+ Global::g_time = time(NULL);
|
|
|
+ Global::g_lastTime = COleDateTime::GetCurrentTime();
|
|
|
+ }
|
|
|
+ cJSON_Delete(pJson);
|
|
|
+ pJson = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 然后开始投递下一个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);
|
|
|
+
|
|
|
+#if 0
|
|
|
+ // 在主界面中显示
|
|
|
+ CMainDlg* pMain = (CMainDlg*)m_pMain;
|
|
|
+ if( m_pMain!=NULL )
|
|
|
+ {
|
|
|
+ pMain->AddInformation(strMessage);
|
|
|
+ TRACE( strMessage+_T("\n") );
|
|
|
+ }
|
|
|
+#else
|
|
|
+ Global::WriteTextLog(strMessage);
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+/////////////////////////////////////////////////////////////////////
|
|
|
+// 判断客户端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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|