首先說明,下面的代碼僅是一個IOCP的demo,很多地方的設計非常差,當然也有一些設計還算可以:)。此篇僅供對IOCP有些了解但又不深入的、需要一個稍微完整示例的、對網絡編程感興趣的同學參考。點擊這里下載代碼
整個程序的流程如下:
流程完全是無阻塞的,主線程里,將收到的消息全都一次性取出后,然后派發。所有欲發送的消息都緩存起來,等到更新的時候一起發送。有些地方代碼沒有完善,比如斷開連接后,socket、內存等資源的關閉回收。要注意MAXRECEIVEDBUFFLENGTH這個宏,它是定義每個socket消息收發時緩沖區的大小。如果很大,會非常吃內存的。我在這里也沒有做粘包、分包的情況處理。工程還帶了一個C#寫的測試客戶端。在我的機器上,能有12W的連接。
關鍵代碼如下:
void MyIOCP::Execute() { PerHandleData* pPerHandleData = nullptr; PerIOData* pPerIOData = nullptr; LPOVERLAPPED lpOverLapped = nullptr; DWORD byteTransferred; BOOL bRet = FALSE; while (true) { bRet = GetQueuedCompletionStatus(mCompletionPort, &byteTransferred, (PULONG_PTR)&pPerHandleData, /*(LPOVERLAPPED*)&pPerIOData*/&lpOverLapped, INFINITE); pPerIOData = (PerIOData*)CONTAINING_RECORD(lpOverLapped, PerIOData, overlapped); if (bRet == FALSE) { if (pPerIOData == nullptr) { // 退出線程。服務器要關了。。 break; } // 對方主動斷開了 continue; } // 處理成功的完成端口請求 switch (pPerIOData->operationType) { case CDH::E_INVALID: { } break; case CDH::E_ACCEPT: { /************************************************************************/ /* inet_ntoa(ClientAddr->sin_addr) 是客戶端IP地址 ntohs(ClientAddr->sin_port) 是客戶端連入的端口 inet_ntoa(LocalAddr ->sin_addr) 是本地IP地址 ntohs(LocalAddr -AZ>sin_port) 是本地通訊的端口 pIoContext->m_wsaBuf.buf 是存儲客戶端發來第一組數據的緩沖區 */ /************************************************************************/ SOCKADDR_IN* ClientAddr = NULL; SOCKADDR_IN* LocalAddr = NULL; int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN); mlpfnGetAcceptExSockAddrs(pPerIOData->buffer, 0, sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, (LPSOCKADDR*)&LocalAddr, &localLen, (LPSOCKADDR*)&ClientAddr, &remoteLen); WrapSocket* connectedSocket = GetSocketAndEraseFromTheMap(pPerIOData->remoteSocket, WRAPESOCKETTYPE::E_READYTOBECONNECTED); if (connectedSocket == nullptr) { closesocket(pPerIOData->remoteSocket); continue; } if (!AssociateDeviceWithCompletionPort(connectedSocket)) { closesocket(pPerIOData->remoteSocket); continue; } // 添加到已經連接列表 InsertWrapSocketToMap(connectedSocket, WRAPESOCKETTYPE::E_CONNECTED); PostReceive(connectedSocket); // 這次消耗了一個准備好了的socket, 現在再生成一個socket待連接。 PostAcceptEx(connectedSocket->GetPerHandleData().hasListenSocket); } break; case CDH::E_RECV: { int socket = pPerHandleData->selfSocket; WrapSocket* wrapSocket = nullptr; GetSocketForSendOrRecvData(socket, wrapSocket); if (wrapSocket != nullptr) { wrapSocket->GetPerIODataReceive().bufferLen = byteTransferred; Receive(wrapSocket); } } break; case CDH::E_SEND: { int socket = pPerHandleData->selfSocket; WrapSocket* wrapSocket = nullptr; GetSocketForSendOrRecvData(socket, wrapSocket); if (wrapSocket != nullptr) { wrapSocket->SendingData(false); } } break; case CDH::E_CONNECT: break; default: break; } } }
注意CDH::E_RECV,收到消息后,將消息緩存,然后直接又進行投遞PostReceive(),這其實是非常不好的。應該分情況來選擇是否立即投遞。
以上,整個代碼,基本是用C++的格式寫C代碼。希望以后能有機會與大家共同分享一個比較完整的、面向對象風格的IOCP。