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知识点及疑惑 这篇文章分析的很详细,深入。

理解I/O完成端口模型

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
IOCP是一种高效的I/O多路复用模型,适用于Windows系统上的网络编程。下面是一个简单的使用IOCP模型的C代码示例,实现了一个简单的回显服务器: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #include <windows.h> #define BUFSIZE 1024 typedef struct _PER_HANDLE_DATA { SOCKET Socket; SOCKADDR_STORAGE ClientAddr; } PER_HANDLE_DATA, *LPPER_HANDLE_DATA; typedef struct _PER_IO_DATA { OVERLAPPED Overlapped; WSABUF DataBuf; char Buffer[BUFSIZE]; int OperationType; } PER_IO_DATA, *LPPER_IO_DATA; DWORD WINAPI WorkerThread(LPVOID lpParam); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET ListenSocket, AcceptSocket; SOCKADDR_STORAGE LocalAddr, ClientAddr; DWORD Flags; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_DATA PerIoData; DWORD RecvBytes, SendBytes; DWORD BytesTransferred; DWORD ThreadId; HANDLE CompletionPort, ThreadHandle; int i; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed with error: %d\n", WSAGetLastError()); return 1; } // 创建完成端口 CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (CompletionPort == NULL) { printf("CreateIoCompletionPort failed with error: %d\n", GetLastError()); return 1; } // 创建监听套接字 ListenSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %d\n", WSAGetLastError()); return 1; } // 绑定地址并监听 memset(&LocalAddr, 0, sizeof(LocalAddr)); LocalAddr.ss_family = AF_INET6; ((SOCKADDR_IN6*)&LocalAddr)->sin6_port = htons(12345); ((SOCKADDR_IN6*)&LocalAddr)->sin6_addr = in6addr_any; if (bind(ListenSocket, (SOCKADDR*)&LocalAddr, sizeof(LocalAddr)) == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } // 创建工作线程 for (i = 0; i < 2; i++) { ThreadHandle = CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &ThreadId); if (ThreadHandle == NULL) { printf("CreateThread failed with error: %d\n", GetLastError()); return 1; } CloseHandle(ThreadHandle); } // 接受连接并关联到完成端口 while (1) { AcceptSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddr, NULL); if (AcceptSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WS

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值