gh0st的IOCP模型分析
在分析了那麼多IOCP相關api之後想把IOCP模型分析下,本人菜鳥一個,高手勿笑。
gh0st是單文件型別的程式框架。 文件型別的都是從theApp開始的。theApp是一個全域性變數。 那我們就先看一下CGh0stApp這個類的初始化函式 BOOL CGh0stApp::InitInstance()
下面很大一部分是生成的框架。我給大家指出來,就沒必要再看這些了
直到
if (!ProcessShellCommand(cmdInfo))
return FALSE;
都是框架。不去看。分析下面的。
((CMainFrame*) m_pMainWnd)->Activate(nPort, nMaxConnection);
這句是呼叫CMainFrame類的Activate函式。 m_pMainWnd是單文件類的主介面指標,也是框架類指標。就是CMainFrame類 ,接下來我們就去Activate函式裡面看看 。
m_iocpServer = new CIOCPServer; /// 這裡呼叫了IOCPserver建構函式
// 開啟IOCP伺服器, 初始化例程
if (m_iocpServer->Initialize(NotifyProc, this, 100000, nPort))
進入 Initialize 函式看下:
bool CIOCPServer::Initialize(NOTIFYPROC pNotifyProc, CMainFrame* pFrame, int nMaxConnections, int nPort)
{
//// 建立套接字
m_socListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
// 建立事件 處理網路IO
m_hEvent = WSACreateEvent();
/// 在 m_socListen 套接字上接收 FD_ACCEPT 事件,關聯事件 和套接字
int nRet = WSAEventSelect(m_socListen,m_hEvent,FD_ACCEPT);
// 繫結 套接字
nRet = bind(m_socListen, (LPSOCKADDR)&saServer, sizeof(struct sockaddr));
// Set the socket to listen
nRet = listen(m_socListen, SOMAXCONN);
/// 開啟監聽執行緒 ListenThreadProc
m_hThread = (HANDLE)_beginthreadex(NULL, 0, ListenThreadProc, (void*) this, 0, &dwThreadId);
if (m_hThread != INVALID_HANDLE_VALUE)
{
//// 初始化完成埠
InitializeIOCP();
}
}
讓我們看下 監聽執行緒 ListenThreadProc 和 InitializeIOCP 函式都做了什麼。
首先看監聽執行緒:
unsigned CIOCPServer::ListenThreadProc(LPVOID lParam)
{
while(1)
{
DWORD dwRet;
/// 在這裡阻塞等待客戶端連線
dwRet = WSAWaitForMultipleEvents(1, &pThis->m_hEvent, FALSE,100, FALSE);
/// 列舉發生的事件
int nRet = WSAEnumNetworkEvents(pThis->m_socListen, pThis->m_hEvent, &events);
///處理accept 事件
if (events.lNetworkEvents & FD_ACCEPT)
{
if (events.iErrorCode[FD_ACCEPT_BIT] == 0)
pThis->OnAccept();
}
} // while....
return 0; // Normal Thread Exit Code...
}
void CIOCPServer::OnAccept()
{
SOCKADDR_IN SockAddr;
SOCKET clientSocket;
int nRet;
int nLen;
/// 接收新的socket 描述符
nLen = sizeof(SOCKADDR_IN);
clientSocket = accept(m_socListen, (LPSOCKADDR)&SockAddr,&nLen);
// 建立ClientContext 結構體 來和完成埠繫結
ClientContext* pContext = AllocateContext();
pContext->m_Socket = clientSocket;
pContext->m_wsaInBuffer.buf = (char*)pContext->m_byInBuffer;
pContext->m_wsaInBuffer.len = sizeof(pContext->m_byInBuffer);
/// 注意這裡把 通過 accept 得到的客戶端套接字 SockAddr 與 完成埠結合
AssociateSocketWithCompletionPort(clientSocket, m_hCompletionPort, (DWORD) pContext)
/// 這裡觸發第一個 IO 完成請求
OVERLAPPEDPLUS *pOverlap = new OVERLAPPEDPLUS(IOInitialize);
BOOL bSuccess = PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) pContext, &pOverlap->m_ol);
/// 空操作
m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_CLIENT_CONNECT);
// 投遞一個 recv 接收請求 ,到客戶端套接字
PostRecv(pContext);
}
至此 ListenThreadProc 一個迴圈已經走完,接著看下InitializeIOCP
函式都做了些什麼
bool CIOCPServer::InitializeIOCP(void)
{
SOCKET s;
DWORD i;
UINT nThreadID;
SYSTEM_INFO systemInfo;
/// 建立被所有執行緒使用的完成埠,注意這裡是完成埠。
/// 跟前面的建立的事件來接受 accept 還不一樣
m_hCompletionPort = CreateIoCompletionPort( (HANDLE)s, NULL, 0, 0 );
/// 這裡我們建立兩倍於 處理器的執行緒數量,因為每個執行緒不是時時刻刻都在工作,
/// 還有處於阻塞狀態,所以執行緒個數最好比處理器個數多一些
for ( i = 0; i < nWorkerCnt; i++ )
{
hWorker = (HANDLE)_beginthreadex(NULL,0,ThreadPoolFunc, (void*) this,0, &nThreadID);
}
return true;
}
可以看出這個函式主要是建立一個完成埠,建立兩倍於處理器數量的 工作執行緒。再看下工作執行緒池 ThreadPoolFunc
都做些什麼(只分析主幹,細枝末節略過):
unsigned CIOCPServer::ThreadPoolFunc (LPVOID thisContext)
{
HANDLE hCompletionPort = pThis->m_hCompletionPort;
for (BOOL bStayInPool = TRUE; bStayInPool && pThis->m_bTimeToKill == false; )
{
// Get a completed IO request.
BOOL bIORet = GetQueuedCompletionStatus(hCompletionPort, &dwIoSize, (LPDWORD) &lpClientContext, &lpOverlapped, INFINITE);
DWORD dwIOError = GetLastError();
pOverlapPlus = CONTAINING_RECORD(lpOverlapped, OVERLAPPEDPLUS, m_ol);
if (!bError)
{
if(bIORet && NULL != pOverlapPlus && NULL != lpClientContext)
{
try
{
pThis->ProcessIOMessage(pOverlapPlus->m_ioType, lpClientContext, dwIoSize);
}
catch (...) {}
}
}
}
return 0;
}
可以看出這個函式首先呼叫 GetQueuedCompletionStatus 阻塞一直 等到從完成埠取出一個成功I/O操作的完成包,然後呼叫 ProcessIOMessage 處理。
如果把巨集定義:
BEGIN_IO_MSG_MAP()
IO_MESSAGE_HANDLER(IORead, OnClientReading)
IO_MESSAGE_HANDLER(IOWrite, OnClientWriting)
IO_MESSAGE_HANDLER(IOInitialize, OnClientInitializing)
END_IO_MSG_MAP()
展開就會發現上面其實就是通過 m_ioType 來區分分別呼叫 OnClientReading ,OnClientWriting 的。
bool CIOCPServer::OnClientReading(ClientContext* pContext, DWORD dwIoSize)
{
CLock cs(CIOCPServer::m_cs, "OnClientReading");
{
if (dwIoSize == 0) ///判斷是否是斷開
{
/// 移除客戶端
RemoveStaleClient(pContext, FALSE);
return false;
}
/// 判斷是否 是"gh0st" 標誌
if (dwIoSize == FLAG_SIZE && memcmp(pContext->m_byInBuffer, m_bPacketFlag, FLAG_SIZE) == 0)
{
// 重新傳送
Send(pContext, pContext->m_ResendWriteBuffer.GetBuffer(), pContext->m_ResendWriteBuffer.GetBufferLen());
// 必須再投遞一個接收請求
PostRecv(pContext);
return true;
}
/// 寫入資料到緩衝區
pContext->m_CompressionBuffer.Write(pContext->m_byInBuffer,dwIoSize);
/// 通知主框架處理 NC_RECEIVE
m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_RECEIVE);
// Check real Data
while (pContext->m_CompressionBuffer.GetBufferLen() > HDR_SIZE)
{
///資料包 解壓縮
if (nRet == Z_OK) /// 資料包正確
{
///通知 主框架處理 NC_RECEIVE_COMPLETE
m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_RECEIVE_COMPLETE);
}
}
// 投遞接收
PostRecv(pContext);
}
return true;
}
看下 OnClientWriting 函式:
bool CIOCPServer::OnClientWriting(ClientContext* pContext, DWORD dwIoSize)
{
try
{
if (pContext->m_WriteBuffer.GetBufferLen() == 0) ///資料是否傳送完畢
{
pContext->m_WriteBuffer.ClearBuffer();
// Write complete
SetEvent(pContext->m_hWriteComplete);
return true; // issue new read after this one
}
else
{
OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite);
///投遞 傳送 請求
int nRetVal = WSASend(pContext->m_Socket,
&pContext->m_wsaOutBuffer,
1,
&pContext->m_wsaOutBuffer.len,
ulFlags,
&pOverlap->m_ol,
NULL);
}
}catch(...){}
return false; // issue new read after this one
}
至此大體框架分析完畢。
對於IOCP要使用WSASocket建立支援重疊IO的套接字,對於這種套接字 要用WSAAccept 來等待客戶端的連線,這個函式是阻塞的。但gh0st裡面沒有用 WSAAccept 而是選擇了事件模型。
每當 accept 到一個客戶端套接字的時候,就會呼叫函式 CreateIoCompletionPort把完成埠 hCompletionPort 與accept返回的套接字和CompletionKey(完成鍵)associate然後執行緒池所有執行緒在等待這個 hCompletionPort ,後面 WSASend ,WSARecv 操作的都是accept 到的那個套接字。
如果很多客戶端連線過來之後,完成埠 hCompletionPort 會與很多個套接字associate,作業系統會維持他們之間的關係,當有一個套接字上面有IO事件之後,GetQueuedCompletionStatus就會返回,從 lpOverlapped 結構體知道是一次讀還是寫事件。
關於這一點MSDN文件上有說明:
Multiple file handles can be associated with a single I/O completion port by calling CreateIoCompletionPort multiple times with the same I/O completion port handle in the ExistingCompletionPort parameter and a different file handle in the FileHandle parameter each time
在服務端起初呼叫的 WSARecv ,投遞一個讀請求 是很有用的,否則 完成埠佇列沒有請求,以後對完成埠 hCompletionPort 的請求都不會返回。
對於服務端的 投遞 傳送請求 WSASend ,客戶端即便沒有接受 recv, 這個函式也會觸發 GetQueuedCompletionStatus。正如 IOCP知識點及疑惑 文中分析的一樣 ,這只是一個本地的過程。 但是對於 WSARecv ,需要客戶端send 之後才會返回 ,這是個CS互動的過程。
關於IOCP兩篇很好的參考文章:
IOCP知識點及疑惑 這篇文章分析的很詳細,深入。
碼字不容易,覺得好請打賞下:
相關文章
- IOCP模型總結(轉)模型
- IOCP 完成埠
- IOCP 檔案拷貝
- IOCP 系列函式講解函式
- HttpServer: 基於IOCP模型且整合Openssl的輕量級高效能web伺服器HTTPServer模型Web伺服器
- NET平臺下TCP實現IOCP例子TCP
- C#高效能Socket伺服器SocketAsyncEventArgs的實現(IOCP)C#伺服器
- 倒立擺模型分析模型
- Swoole 程式模型分析模型
- 教你解密Gh0st 1.0遠控木馬VIP版配置資訊解密
- 使用者行為分析模型實踐(四)—— 留存分析模型模型
- Redis網路模型的原始碼分析Redis模型原始碼
- LNMP架構下的程式模型分析LNMP架構模型
- 分析模式:可複用的物件模型模式物件模型
- 資料分析一定要懂的模型——購物籃模型模型
- 使用者行為分析模型實踐(一)—— 路徑分析模型模型
- ONNX模型分析與使用模型
- 需求分析與Y模型模型
- 資料分析八大模型:同期群模型大模型
- 九種常見的資料分析模型模型
- PLSA模型的再理解以及原始碼分析模型原始碼
- 運營人必須掌握的分析模型!帕累託模型詳解模型
- webrtc執行緒模型分析Web執行緒模型
- RxJava 執行緒模型分析RxJava執行緒模型
- google protocol buffer——protobuf的基本使用和模型分析GoProtocol模型
- Redis執行緒模型的原理分析蒼癘Redis執行緒模型
- Hadoop的Server及其執行緒模型分析HadoopServer執行緒模型
- Hadoop 的 Server 及其執行緒模型分析HadoopServer執行緒模型
- 安迪·葛洛夫的六力分析模型(轉載)模型
- IT系統的業務模型分析與系統建模模型
- C++20協程例項:攜程化的IOCP服務端/客戶端C++服務端客戶端
- oracle 12c rac 在aix 7.1 上安裝需要注意的IOCP問題OracleAI
- 大資料分析模型有哪些大資料模型
- 資料模型與資料分析模型
- NABC模型進行需求分析模型
- C++物件模型:編譯分析C++物件模型編譯
- 幾種常見的軟體開發模型分析模型
- 15種最常用的資料分析方法和模型模型