MFC: 多人聊天服务器 服务器端之OICPServer类


MFC: 多人聊天服务器 服务器端之OICPServer类
2010年11月10日
  / IOCPServer.h: interface for the CIOCPServer class. // ////////////////////////////////////////////////// //////////////////// #if !defined(AFX_IOCPSERVER_H__1EC64F02_5939_46BF_B121 _DEDD88DB2D1B__INCLUDED_) #define AFX_IOCPSERVER_H__1EC64F02_5939_46BF_B121_DEDD88DB 2D1B__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define BUFSIZE 1024 #pragma comment(lib, "Ws2_32.lib") #include  #include  #include  #include "buffer.h" // a前面如果不使用#号,就会报Conversion from integral type to pointer type requires reinterpret_cast, // C-style cast or function-style cast的错误 #define PRINTDEBUG(a) PrintError(#a,__FILE__,__LINE__,GetLastError()) __inline int PrintError(LPSTR linedesc, LPSTR filename, int lineno, DWORD errnum) { LPSTR lpBuffer; char errbuf[256]; DWORD numread; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, errnum, LANG_NEUTRAL, (LPTSTR)&lpBuffer, 0, NULL ); wsprintf(errbuf, "\nThe following call failed at line %d in %s:\n\n" " %s\n\nReason: %s\n", lineno, filename, linedesc, lpBuffer); WriteFile(GetStdHandle(STD_ERROR_HANDLE), errbuf, strlen(errbuf), &numread, FALSE ); return errnum; } //定义操作类型 enum IOType { OP_IOInitialize, OP_IORead, OP_IOWrite, OP_IOIdle }; //定义completion key typedef struct _CLIENTCONTEXT { SOCKET m_Socket; // 服务器建立的对应于客户端通信的socket CHAR m_ip[32]; // 客户端的ip UINT m_nPort; // 客户端的端口 //存储接/发送数据的缓冲区 /* 为什么要用到这两个缓冲区呢,因为有的时候发送数据的时候不能一次性全部发送完毕 这就需要使用一个类对缓冲区进行管理,比如delete掉前面发送的200k数据等等, 发送数据时写道writebuffer中,接收数据保存到readbuffer内,对读到数据的处理 也需要进行管理,所以使用了一个ReadBuffer */ CBuffer m_ReadBuffer; CBuffer m_WriteBuffer; //winsock取得数据的缓冲区 //wsaInBuffer这个结构体的buf属性,也就是char*就是m_byInBuffer!! WSABUF m_wsaInBuffer; CHAR m_byInBuffer[BUFSIZE]; //winsock发送数据的缓冲区,发送得数据就放在m_WriteBuffer //WSASend函数需要这个参数 WSABUF m_wsaOutBuffer; //为了防止写数据覆盖发生,设定此事件,差不多相当于互斥写吧 HANDLE m_hWriteComplete; } CLIENTCONTEXT, *LPCLIENTCONTEXT; typedef struct _OVERLAPPEDPLUS { WSAOVERLAPPED ol; //操作类型 IOType opCode; } OVERLAPPEDPLUS, *LPOVERLAPPEDPLUS; class CIOCPServer { public: CIOCPServer(); virtual ~CIOCPServer(); void Accept(); BOOL Initialize(DWORD conns, int port); //绑定在端口上工作线程 static DWORD WINAPI CompletionWorkerThread( void * lpParam); protected: //处理消息 virtual void ProcessReceiveData(LPCLIENTCONTEXT lpContext, CBuffer &buffer) = 0; //用户退出,或者连接中断,返回TRUE:已经处理过该对出事件,FALSE,没有处理过该退出事件 //子类函数必须写 virtual BOOL ClientExit(LPCLIENTCONTEXT lpContext) = 0 ; //写数据 void Send(LPCLIENTCONTEXT lpContext, CString strData); private: //根据消息overlapped的类型,处理消息 BOOL ProcessIOMessage(IOType opCode, LPCLIENTCONTEXT lpContext , DWORD dwIoSize); //通知客户连接时初始化成功 BOOL OnClientInitializing(LPCLIENTCONTEXT lpContext, DWORD dwIoSize); //通知读客户数据成功 BOOL OnClientReading(LPCLIENTCONTEXT lpContext, DWORD dwIoSize); //通知用户写数据成功 BOOL OnClientWriting(LPCLIENTCONTEXT lpContext, DWORD dwIoSize); //通知用户退出或连接中断 void FreeClientContext(LPCLIENTCONTEXT lpContext); //在端口上产生线程 void CreateWorkerThread(); //关闭完成端口 void CloseCompletionPort(); //分配连接overlappedplus LPOVERLAPPEDPLUS AllocateOverlappedPlus(IOType ioType); //分配连接进入的客户的相关信息 LPCLIENTCONTEXT AllocateContext(); //释放overlappedplus void FreeOverlappedPlus(LPOVERLAPPEDPLUS lpOlp); private: //结束端口完成事件通过设置和查询来实现互斥效果 HANDLE m_hKillEvent; //完成端口句柄 HANDLE m_hIocp; //线程数 DWORD m_dwThreads; //产生监听socket SOCKET m_sListen; }; #endif // !defined(AFX_IOCPSERVER_H__1EC64F02_5939_46BF_B121 _DEDD88DB2D1B__INCLUDED_) // IOCPServer.cpp: implementation of the CIOCPServer class. // ////////////////////////////////////////////////// //////////////////// #include "stdafx.h" #include "IOCPServer.h" ////////////////////////////////////////////////// //////////////////// // Construction/Destruction ////////////////////////////////////////////////// //////////////////// CIOCPServer::CIOCPServer() { //socket初始化 WSADATA wsd; WORD wVersionRequested = MAKEWORD(2, 2); int nResult = WSAStartup(wVersionRequested, &wsd); if ( nResult == SOCKET_ERROR ) { WSACleanup(); PRINTDEBUG(WSAGetLastError()); } //LOBYTE 就是 (byte)wsd.wVersion,取参数的后8位 //HIBYTE 把参数右移8位,转化为byte,取后8位 if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { WSACleanup(); } m_hKillEvent = CreateEvent(NULL, TRUE, FALSE, NULL); printf("socket init successful... \n"); } CIOCPServer::~CIOCPServer() { WSACleanup(); } //分配连接overlappedplus //创建一个类型为ioType的OVERLAPPED结构,内容为全0 LPOVERLAPPEDPLUS CIOCPServer::AllocateOverlappedPlus(IOType ioType) { OVERLAPPEDPLUS* pOlp = NULL; pOlp = new OVERLAPPEDPLUS; ZeroMemory(pOlp, sizeof(OVERLAPPEDPLUS)); pOlp->opCode = ioType; return pOlp; } //分配连接进入的客户的相关信息 LPCLIENTCONTEXT CIOCPServer::AllocateContext() { LPCLIENTCONTEXT lpContext = NULL; lpContext = new CLIENTCONTEXT; ZeroMemory(lpContext, sizeof(CLIENTCONTEXT)); //自己指向自己,我倒!! lpContext->m_wsaInBuffer.buf = lpContext->m_byInBuffer; lpContext->m_wsaInBuffer.len = BUFSIZE; // 大小默认为1024 //创建了一个Event,每个lpclientcontext都有一个event!!! lpContext->m_hWriteComplete = CreateEvent(NULL, FALSE, TRUE, NULL); return lpContext; } //释放overlappedplus void CIOCPServer::FreeOverlappedPlus(LPOVERLAPPEDPLUS lpOlp) { delete lpOlp; } //根据消息overlapped的类型,处理消息,返回值TRUE:继续读,FALSE,不读 //一般写事件就不让他都返回FALSE,没有必要再读了! BOOL CIOCPServer::ProcessIOMessage(IOType opCode, LPCLIENTCONTEXT lpContext , DWORD dwIoSize) { BOOL bRet = FALSE; //根据opCode确定操作 switch (opCode) { case OP_IOInitialize: //打印显示出一句话:玩家自ip 。。端口号连入 bRet = OnClientInitializing(lpContext, dwIoSize); break; case OP_IORead: //如果需要读端口,则读之 bRet = OnClientReading(lpContext, dwIoSize); break; case OP_IOWrite: //如果需要写端口,则尝试写之 bRet = OnClientWriting(lpContext, dwIoSize); break; default: printf("worker thread:unknown operation...\n"); } return bRet; } //关闭完成端口 void CIOCPServer::CloseCompletionPort( ) { //发送0字节表示关闭完成端口,completion packet的lp PostQueuedCompletionStatus(m_hIocp, 0, (DWORD) NULL, NULL); // Close the CompletionPort and stop any more requests CloseHandle(m_hIocp); } //根据系统中CPU的数目创建适当数目的线程 void CIOCPServer::CreateWorkerThread() { SYSTEM_INFO sysinfo; DWORD dwThreadId; //获得系统中有多少个CPU GetSystemInfo(&sysinfo); //在completion port上等待的线程数为:CPU*2+2 m_dwThreads = sysinfo.dwNumberOfProcessors * 2 + 2; printf("create worker thread num: %d \n", m_dwThreads); //创建m_dwThreads个线程 for ( UINT i = 0; i 返回值非0 如果出现错误了,返回0,但这时还分两种情况,一个是获得了一个 completion packet ,一种是没有completion packet , 从完成端口的I/O队列中抽取记录,只有重叠I/O动作完成的时候,队列中才有 记录,凡是调用这个函数的线程将被放入到完成端口的等待线程队列中, 因此完成端口就可以在自己的线程池中帮助我们维护这个线程。 完成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录, 该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个 字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。 当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用 GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。 完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待 GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。 线程间数据传递 线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数,或者使用全局变量。 但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。 CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的 数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,这样就可以传递更加丰富的内容。 这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。 OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到, 如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息, 可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员 变量的地址给ReadFile函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函数 返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整 的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意,如果 跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将 ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus 被返回时,我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。 CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。 */ //如果bResult == 0 则是出错了,continue,继续查询 BOOL bResult = GetQueuedCompletionStatus(pIocpServer->m_hIocp, &dwNumRead, (LPDWORD)&lpContext, &lpOverlapped, INFINITE); //获得LPOVERLAPPEDPLUS指针 lpOlp = CONTAINING_RECORD(lpOverlapped, OVERLAPPEDPLUS, ol); //显示opcode信息 printf("Event coming %d\n", lpOlp->opCode); //这里首先检查一下有没有出现错误,bError = TRUE表示出现错误了 //非timeout引起的错误, 相关信息没有从GetQueuedCompletionStatus中返回 if (!bResult && lpOlp == NULL && WAIT_TIMEOUT != WSAGetLastError()) { PRINTDEBUG(WSAGetLastError()); // 发生错误 bError = TRUE; } //错误,但是相关信息从GetQueuedCompletionStatus中返回 //可能原因之一是:客户强制退出了! else if ( !bResult && lpOlp != NULL ) { //PRINTDEBUG( WSAGetLastError() ); //释放掉lpContext pIocpServer->FreeClientContext(lpContext); //循环继续,不应该读了! continue; } //无错误,处理事件 if ( !bError ) { if ( bResult && NULL != lpOlp && NULL != lpContext ) { bEnterRead = pIocpServer->ProcessIOMessage(lpOlp->opCode, lpContext, dwNumRead); } } //如果 处理完了之后并且还需要读, 无错 && 读 //比如初始化完毕后,输出了一行数据,这时就需要继续读 if ( !bError && bEnterRead ) { //创建一个新的,内容为全0的overlapped数据结构,类型为读,这个lpOlp只不过是为WSARecv //提供一个WSAOVERLAPPED结构的地址!!! LPOVERLAPPEDPLUS lpOlp = pIocpServer->AllocateOverlappedPlus(OP_IORead); ULONG ulFlags = MSG_PARTIAL; //首先把lpContext中的InBuffer清0,然后从lpContext的m_socket中读取数据到InBuffer中 ZeroMemory( lpContext->m_wsaInBuffer.buf, lpContext->m_wsaInBuffer.len ); /* For overlapped sockets, WSARecv is used to post one or more buffers into which incoming data will be placed as it becomes available, after which the application-specified completion indication (invocation of the completion routine or setting of an event object) occurs. WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。然后 线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。 */ UINT nRetVal = WSARecv(lpContext->m_Socket, &lpContext->m_wsaInBuffer, 1, &dwNumRead, &ulFlags, &lpOlp->ol, NULL); //如果客户端强制退出了,则删除lpContext对象 if ( nRetVal == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ) { printf("CLIENT abortive exit\n"); pIocpServer->FreeClientContext(lpContext); } } pIocpServer->FreeOverlappedPlus(lpOlp); } // end of while return 0; } BOOL CIOCPServer::Initialize(DWORD conns, int port) { //socket m_sListen 的属性是WSA_FLAG_OVERLAPPED,也就是创建一个完成端口所关联的socket m_sListen = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED); //出错了,则退出 if ( m_sListen == SOCKET_ERROR ) { PRINTDEBUG(WSAGetLastError()); return FALSE; } printf("create listening socket successful... \n"); //需要绑定的本地地址 sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_addr.s_addr = htonl(INADDR_ANY); local.sin_family = AF_INET; local.sin_port = htons(port); //绑定,将监听端口绑定到本地地址 if ( bind ( m_sListen, (sockaddr*)&local, sizeof(local) ) == SOCKET_ERROR) { PRINTDEBUG(WSAGetLastError()); return FALSE; } printf("bind local socket successful... \n"); //产生一个I/O完成端口 /* 一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成, 某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后, 可以在任何时候与某个完成端口进行关联。 通常情况下,我们会在应用程序中创建一定数量的工作者线程来处理这些通知。 线程数量取决于应用程序的特定需要。理想的情况是,线程数量等于处理器的数量, 不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作, 以免线程阻塞。每个线程都将分到一定的CPU时间,在此期间该线程可以运行, 然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作,操作系统将剥夺 其未使用的剩余时间片并让其它线程开始执行。也就是说,前一个线程没有充分使用其时间片, 当发生这样的情况时,应用程序应该准备其它线程来充分利用这些时间片。 注意这里还没有关联到socket上,完成端口关联到服务器端创建的对应每个客户端的socket上 */ m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if ( m_hIocp == NULL ) { PRINTDEBUG(WSAGetLastError()); return FALSE; } printf("create completion port successful... \n"); //创建完成端口上关联的若干个线程 CreateWorkerThread(); printf("listening socket... \n"); //监听线程 listen( m_sListen, conns ); return TRUE; } void CIOCPServer::Accept() { sockaddr_in client; int sClientLength = sizeof(client); //If 第二个参数 is zero, the function tests the object's state and returns immediately. //m_hKillEvent标志着整个服务器的关闭 /* The WaitForSingleObject function returns when one of the following occurs: 1。The specified object is in the signaled state. 2。The time-out interval elapses. 返回WAIT_TIMEOUT表示The time-out interval elapsed, and the object's state is nonsignaled. The WaitForSingleObject function checks the current state of the specified object. If the object's state is nonsignaled, the calling thread enters the wait state. It uses no processor time while waiting for the object state to become signaled or the time-out interval to elapse. */ while ( WAIT_TIMEOUT == WaitForSingleObject(m_hKillEvent, 0) ) { printf("waiting for client connecting... \n"); //等待客户连接,在m_sListen端口上accept一个连接 SOCKET clientSocket = accept(m_sListen, (sockaddr*)&client, &sClientLength); if ( clientSocket == SOCKET_ERROR ) { PRINTDEBUG(WSAGetLastError()); continue; } printf("a new client comming... \n"); //设置completion key, 创建一个内容为全0,类型为参数的一个completion key LPCLIENTCONTEXT lpContext = AllocateContext(); //设置完成键的socket属性为上面服务器端刚刚创建的clientsocket lpContext->m_Socket = clientSocket; //完成键的ip也可以设置了!! sprintf(lpContext->m_ip,inet_ntoa(client.sin_addr)) ; //端口也知道了!!!hoho lpContext->m_nPort = client.sin_port; printf("create completion port key successful... \n"); //completion port 与对应新联入的客户端的socket关联起来 //把接收的服务器端的那个clientsocket和完成端口m_hIocp关联起来 if ( !CreateIoCompletionPort((HANDLE)clientSocket, m_hIocp, (DWORD)lpContext, // 完成键指针,可以用来放一些程序相关的数据 0)) { PRINTDEBUG(WSAGetLastError()); closesocket( lpContext->m_Socket ); delete lpContext; continue; } //初始化客户连接 //创建一个内容为全0,类型为OP_IOInitialize(0)的一个OVERLAPPED //为的是供 PostQueuedCompletionStatus当作参数用 OVERLAPPEDPLUS *pOlp = AllocateOverlappedPlus(OP_IOInitialize); //发送一个事件到消息队列,等待线程来取走 BOOL bSuccess = PostQueuedCompletionStatus( m_hIocp, 0, (DWORD) lpContext, &pOlp->ol); if ( (!bSuccess && GetLastError( ) != ERROR_IO_PENDING)) { PRINTDEBUG(WSAGetLastError()); closesocket( lpContext->m_Socket ); delete lpContext; continue; } printf("associate completion port and new client successful... \n"); } // end of while //关闭完成端口 CloseCompletionPort(); //关闭m_hKillEvent事件 CloseHandle(m_hKillEvent); } //客户连接时初始化 BOOL CIOCPServer::OnClientInitializing(LPCLIENTCONTEXT lpContext, DWORD dwIoSize) { // memset(lpContext->m_ip, 0, sizeof(lpContext->m_ip)); printf("socket init from :%s:%d\n", lpContext->m_ip, lpContext->m_nPort); return TRUE; } //读客户数据,receive,如果接收到数据长度为0,则表示,客户端连接关闭 BOOL CIOCPServer::OnClientReading(LPCLIENTCONTEXT lpContext, DWORD dwIoSize) { if ( dwIoSize == 0 ) { FreeClientContext(lpContext); return FALSE; } //输出读到的用户数据 m_wsaInBuffer.buf 保存了用户传来的数据 printf("recv: %s\n", lpContext->m_wsaInBuffer.buf); //输出数据的长度 printf("recv len: %d\n", dwIoSize); //把m_byInBuffer写入到ReadBuffer中,这里已经读到数据了 lpContext->m_ReadBuffer.Write(lpContext->m_byInBuffe r,dwIoSize); //处理数据交给子类去管吧,注意这里是ReadBuffer保存了接收到的数据 //注意,读到数据之后才调用子类的这个函数处理!!!!!!! ProcessReceiveData(lpContext, lpContext->m_ReadBuffer); return TRUE; } //写事件完成,但是CBuffer中的数据不一定都发送了! BOOL CIOCPServer::OnClientWriting(LPCLIENTCONTEXT lpContext, DWORD dwIoSize) { ULONG ulFlags = MSG_PARTIAL; //删除已经发送了的数据, 在CBuffer类的WriteBuffer中删除前dwIoSize个字节的数据 lpContext->m_WriteBuffer.Delete(dwIoSize); //分数据都发送了和数据部分发送两种情况进行处理 //有可能发送完一部分之后直接就返回了,所以这里要考虑一下两种情况 if ( lpContext->m_WriteBuffer.GetBufferLen() == 0 ) { //数据都发送了! //清除缓存 lpContext->m_WriteBuffer.ClearBuffer(); // 写事件完成了,可以允许下个线程写了 SetEvent(lpContext->m_hWriteComplete); printf("WRITE event completed \n"); } else // 如果刚开始发送或者还有数据要发送 { LPOVERLAPPEDPLUS pOverlap = AllocateOverlappedPlus(OP_IOWrite); //wsaOutBuffer的buf保存了发送的数据,它是Char*类型的 lpContext->m_wsaOutBuffer.buf = lpContext->m_WriteBuffer.GetBuffer(); lpContext->m_wsaOutBuffer.len = lpContext->m_WriteBuffer.GetBufferLen(); printf("data to sent: %s , length:%d\n", lpContext->m_wsaOutBuffer.buf, lpContext->m_wsaOutBuffer.len); //WSASend成功完成时返回0 //It can be used in conjunction with overlapped sockets to perform overlapped send operations. /* For overlapped sockets (created using WSASocket with flag WSA_FLAG_OVERLAPPED) sending information uses overlapped I/O, unless both lpOverlapped and lpCompletionRoutine are NULL. In that case, the socket is treated as a nonoverlapped socket. A completion indication will occur, invoking the completion of a routine or setting of an event object, when the buffer(s) have been consumed by the transport. If the operation does not complete immediately, the final completion status is retrieved through the completion routine or WSAGetOverlappedResult. 4. WSASend 和 WSARecv 默认情况下, 每一个 socket 在系统底层都拥有一个发送和接收缓冲区. 我们发送数据时, 实际上是把数据复制到发送缓冲区中, 然后 WSASend 返回. 但是如果发送缓冲区已经满了, 那么我们在 WSASend 中指定的缓冲区就会被锁定到系统的非分页内存池中, WSASend 返回 WSA_IO_PENDING. 一旦网络空闲下来, 数据将会从我们提交的缓冲区中直接被发送出去,与 " 我们的缓冲区->发送缓冲区->网络 " 相比节省了一次复制操作. WSARecv 也是一样, 接收数据时直接把接收缓冲区中的数据复制出来. 如果接收缓冲区中没有数据, 我们在 WSARecv 中指定的缓冲区就会被锁定到系统的非分页内存池以等待数据, WSARecv 返回 WSA_IO_PENDING. 如果网络上有数据来了, 这些数据将会被直接保存到我们提供的缓冲区中. 如果一个服务器同时连接了许多客户端, 对每个客户端又调用了许多 WSARecv, 那么大量的内存将会被锁定 到非分页内存池. 锁定这些内存时是按照页面边界来锁定的, 也就是说即使你 WSARecv 的缓存大小是 1 字节, 被锁定的内存也将会是 4k. 非分页内存池是由整个系统共用的, 如果用完的话最坏的情况就是系统崩溃. 一个解决办法是, 使用大小为 0 的缓冲区调用 WSARecv. 等到调用成功时再换用非阻塞的 recv 接收到来的数据, 直到它返回 WSAEWOULDBLOCK 表明数据已经全部读完. 在这个过程中没有任何内存需要被锁定, 但坏处是效率稍低. */ int nRetVal = WSASend(lpContext->m_Socket, &lpContext->m_wsaOutBuffer, 1, &lpContext->m_wsaOutBuffer.len, ulFlags, &pOverlap->ol, NULL); if ( nRetVal == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ) { FreeClientContext(lpContext); } } return FALSE;//不等待读了! } void CIOCPServer::FreeClientContext(LPCLIENTCONTEXT lpContext) { //ClientExit 函数由子类重写处理之,做一些关闭之前的处理 if ( ClientExit(lpContext) ) { //如果该用户的退出事件已经处理过,自己返回即可 return ; } //由于在刚开始接收到连接时把服务器端创建的处理客户端通信的socket //关联到了lpContext中,这里需要关闭处理之 //取消在lpContext的m_Socket上的IO操作 CancelIo((HANDLE) lpContext->m_Socket); //关闭lpContext关联的socket closesocket( lpContext->m_Socket ); lpContext->m_Socket = INVALID_SOCKET; //删除lpContext对象 delete lpContext; } //发送消息,这个在子类中调用,来发送消息 void CIOCPServer::Send(LPCLIENTCONTEXT lpContext, CString strData) { //将需要发送的数据添加入发送缓冲区 lpContext->m_WriteBuffer.Write(strData); printf("Waiting for WRITE event\n"); //等待m_hWriteComplete事件被设置,在全部发送完毕之后该事件被设置 WaitForSingleObject(lpContext->m_hWriteComplete, INFINITE); //准备发送数据, lpContext->m_wsaOutBuffer.buf = lpContext->m_WriteBuffer.GetBuffer(); lpContext->m_wsaOutBuffer.len = lpContext->m_WriteBuffer.GetBufferLen(); //这里的属性IO--Write表示创建了一个想写端口的包!!! LPOVERLAPPEDPLUS lpOverlap = AllocateOverlappedPlus(OP_IOWrite); //The PostQueuedCompletionStatus function posts an I/O completion packet to an I/O completion port. //The I/O completion packet will satisfy an outstanding call to the GetQueuedCompletionStatus function. //工作者线程调用GetQueuedCompletionStatus来不断查询 //发送一个数据包给IO完成端口,个人感觉这里应该是通知完成端口,我这里有消息需要写了 //然后完成端口调用onclientwriting,因为lpOverlap的属性opcode是iowrite PostQueuedCompletionStatus(m_hIocp, 0, (DWORD) lpContext, &lpOverlap->ol); }

猜你喜欢

转载自gdvzq90e.iteye.com/blog/1363344
今日推荐