关于一款开源远程控制软件(gh0st)的源码分析(一)
作者:互联网
2020注定是不平凡的一年,前有冠状病毒的肆虐,全国上下都笼罩在一种紧张而又不安的氛围下;因为疫情的严重性,使我被迫享受了学生时代的超长假期。闲来无事,就分析了一下那些静静地躺在我磁盘里的开源软件源码。
gh0st这款软件,主要用途是用于远程操作另一台计算机(包括远程文件的拷贝、远程视频连接等),功能有点类似于QQ中的远程桌面。该软件由两个工程文件组成,分别为gh0st_Client和gh0st_Server,如图1所示:
从gh0st_server开始,启动VS调试,程序运行起来后,中断所有调试(DEBUG——>BreakAll),打开Threads窗口和CallStack窗口,如图2所示:
gh0st_server代表该软件的服务端,服务端启动时开启了10个线程(这和本地处理器个数有关),其中一个为主线程(MainThread),负责注册窗口、创建窗口、初始化线程池与内存池资源及IOCP(完成端口模型);主线程完成资源的初始化,便创建工作线程(ListenThreadProc)并监听客户端的连接请求。
bool CIOCPServer::Initialize(NOTIFYPROC pNotifyProc, CMainFrame* pFrame, int nMaxConnections, int nPort)
{
m_pNotifyProc = pNotifyProc;
m_pFrame = pFrame;
m_nMaxConnections = nMaxConnections;
m_socListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (m_socListen == INVALID_SOCKET)
{
TRACE(_T("Could not create listen socket %ld\n"), WSAGetLastError());
return false;
}
// 创建Event事件,用于线程间的同步
m_hEvent = WSACreateEvent();
if (m_hEvent == WSA_INVALID_EVENT)
{
TRACE(_T("WSACreateEvent() error %ld\n"), WSAGetLastError());
closesocket(m_socListen);
return false;
}
// The listener is ONLY interested in FD_ACCEPT
// That is when a client connects to or IP/Port
// Request async notification
/*
WSAEventSelect模型是WindowsSockets提供的一个有用异步I/O模型。
该模型允许在一个或者多个套接字上接收以事件为基础的网络事件通知。
Windows Sockets应用程序在创建套接字后,调用WSAEventSelect()函数,将一个事件对象与网络事件集合关联在一起。
当网络事件发生时,应用程序以事件的形式接收网络事件通知。
WSAEventSelect模型简单易用,也不需要窗口环境。
该模型唯一的缺点是有最多等待64个事件对象的限制(Linux中SELECT模型最多可以同时处理128个事件对象),当套接字连接数量增加时,就必须创建多个线程来处理I/O,也就是所谓的线程池。
*/
//将m_hEvent事件绑定吧到m_socListen套接字上,用于监听FD_ACCEPT类型的事件
int nRet = WSAEventSelect(m_socListen, m_hEvent,FD_ACCEPT);
if (nRet == SOCKET_ERROR)
{
TRACE(_T("WSAAsyncSelect() error %ld\n"), WSAGetLastError());
closesocket(m_socListen);
return false;
}
SOCKADDR_IN saServer;
// Listen on our designated Port#
saServer.sin_port = htons(nPort);
// Fill in the rest of the address structure
saServer.sin_family = AF_INET;
saServer.sin_addr.s_addr = INADDR_ANY;
// bind our name to the socket
nRet = bind(m_socListen, (LPSOCKADDR)&saServer, sizeof(struct sockaddr));
if (nRet == SOCKET_ERROR)
{
DWORD dwErr = GetLastError();
closesocket(m_socListen);
return false;
}
// Set the socket to listen
nRet = listen(m_socListen, SOMAXCONN);
if (nRet == SOCKET_ERROR)
{
TRACE(_T("listen() error %ld\n"), WSAGetLastError());
closesocket(m_socListen);
return false;
}
UINT dwThreadId = 0;
//创建一个监听线程专门用来监听客户端的连接请求
m_hThread =
(HANDLE)_beginthreadex(NULL, // Security
0, // Stack size - use default
ListenThreadProc, // Thread fn entry point
(void*) this,
0, // Init flag
&dwThreadId); // Thread address
if (m_hThread != INVALID_HANDLE_VALUE)
{
//初始化IOCP模型
InitializeIOCP();
m_bInit = true;
return true;
}
return false;
}
因为此时客户端并没有发起连接远程主机的请求,所以主线程创建完工作线程——ListenThreadProc,ListenThreadProc便一直处于while(1)循环中,反复判断m_hkillEvent事件是否有信号(主线程和工作线程之间通过Event事件实现同步)、是否有IO事件的产生。
unsigned CIOCPServer::ListenThreadProc(LPVOID lParam)
{
CIOCPServer* pThis = reinterpret_cast<CIOCPServer*>(lParam);
WSANETWORKEVENTS events;
while (1)
{
***if (WaitForSingleObject(pThis->m_hKillEvent, 100) == WAIT_OBJECT_0)
break;***
dwRet = WSAWaitForMultipleEvents(1,
&pThis->m_hEvent,
FALSE,
100,
FALSE);
//超时的话,则继续下一轮的循环
if (dwRet == WSA_WAIT_TIMEOUT)
continue;
int nRet = WSAEnumNetworkEvents(pThis->m_socListen,
pThis->m_hEvent,
&events);
if (nRet == SOCKET_ERROR)
{
TRACE(_T("WSAEnumNetworkEvents error %ld\n"), WSAGetLastError());
break;
}
if (events.lNetworkEvents & FD_ACCEPT)
{
//如果发生了可读事件,那么就开始调用OnAccept函数执行数据的读取
if (events.iErrorCode[FD_ACCEPT_BIT] == 0)
pThis->OnAccept();
else
{
TRACE(_T("Unknown network event error %ld\n"), WSAGetLastError());
break;
}
}
}
return 0;
}
当gh0st_server发出关闭窗口的命令(也即主线程发起退出线程的请求),m_hkillEvent将被设置成受信状态。
void CIOCPServer::Stop()
{
::SetEvent(m_hKillEvent);
//等待子线程退出,主线程才关闭线程句柄m_thread和事件句柄m_hKillEvent
WaitForSingleObject(m_hThread, INFINITE);
CloseHandle(m_hThread);
CloseHandle(m_hKillEvent);
}
当工作线程执行到如下代码处:
if (WaitForSingleObject(pThis->m_hKillEvent, 100) == WAIT_OBJECT_0)
break
等待100毫秒,工作线程退出while循环,如此设计的好处在于:主线程退出,工作线程(子线程)能够及时地进行资源的回收,从而增强了程序的健壮性。
再回头看gh0st_server中IOCPServer这个类,该类是gh0st_server的核心,网络事件的检测、IO请求、网络数据的收发都依赖该类来实现,该类的成员变量和成员方法声明如下:
#define NC_CLIENT_CONNECT 0x0001
#define NC_CLIENT_DISCONNECT 0x0002
#define NC_TRANSMIT 0x0003
#define NC_RECEIVE 0x0004
#define NC_RECEIVE_COMPLETE 0x0005 // 完整接收
class CLock
{
public:
CLock(CRITICAL_SECTION& cs, const CString& strFunc)
{
m_strFunc = strFunc;
m_pcs = &cs;
Lock();
}
~CLock()
{
Unlock();
}
void Unlock()
{
LeaveCriticalSection(m_pcs);
TRACE(_T("LC %d %s\n") , GetCurrentThreadId() , m_strFunc);
}
void Lock()
{
TRACE(_T("EC %d %s\n") , GetCurrentThreadId(), m_strFunc);
EnterCriticalSection(m_pcs);
}
protected:
CRITICAL_SECTION* m_pcs;
CString m_strFunc;
};
//声明IO事件
enum IOType
{
IOInitialize,
IORead,
IOWrite,
IOIdle
};
class OVERLAPPEDPLUS
{
public:
OVERLAPPED m_ol;
IOType m_ioType;
OVERLAPPEDPLUS(IOType ioType) {
ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
m_ioType = ioType;
}
};
//客户端context类,包括客户端socket、收发缓冲区
struct ClientContext
{
SOCKET m_Socket;
// Store buffers
CBuffer m_WriteBuffer;
//自定义的内存池类CBuffer
CBuffer m_CompressionBuffer; // 接收到的压缩的数据
CBuffer m_DeCompressionBuffer; // 解压后的数据
CBuffer m_ResendWriteBuffer; // 上次发送的数据包,接收失败时重发时用
int m_Dialog[2]; // 放对话框列表用,第一个int是类型,第二个是CDialog的地址
int m_nTransferProgress;
// Input Elements for Winsock
WSABUF m_wsaInBuffer;
BYTE m_byInBuffer[8192];
// Output elements for Winsock
WSABUF m_wsaOutBuffer;
HANDLE m_hWriteComplete;
// Message counts... purely for example purposes
LONG m_nMsgIn;
LONG m_nMsgOut;
BOOL m_bIsMainSocket; // 是不是主socket
ClientContext* m_pWriteContext;
ClientContext* m_pReadContext;
};
template<>
inline UINT AFXAPI HashKey(CString & strGuid)
{
return HashKey( (LPCTSTR) strGuid);
}
#include "Mapper.h"
typedef void (CALLBACK* NOTIFYPROC)(LPVOID, ClientContext*, UINT nCode);
typedef CList<ClientContext*, ClientContext*& > ContextList;
class CMainFrame;
class CIOCPServer
{
public:
void DisconnectAll();
CIOCPServer();
virtual ~CIOCPServer();
NOTIFYPROC m_pNotifyProc;
CMainFrame* m_pFrame;
bool Initialize(NOTIFYPROC pNotifyProc, CMainFrame* pFrame, int nMaxConnections, int nPort);
//标准的windows线程函数的申明,加static就是为了使得线程函数不能有类的this指针,因为线程函数是按照stdcall的方式进行调用
static unsigned __stdcall ListenThreadProc(LPVOID lpVoid);
static unsigned __stdcall ThreadPoolFunc(LPVOID WorkContext);
static CRITICAL_SECTION m_cs;
void Send(ClientContext* pContext, LPBYTE lpData, UINT nSize);
void PostRecv(ClientContext* pContext);
bool IsRunning();
void Shutdown();
void ResetConnection(ClientContext* pContext);
LONG m_nCurrentThreads;
LONG m_nBusyThreads;
UINT m_nSendKbps; // 发送即时速度
UINT m_nRecvKbps; // 接受即时速度
UINT m_nMaxConnections; // 最大连接数
protected:
void InitializeClientRead(ClientContext* pContext);
BOOL AssociateSocketWithCompletionPort(SOCKET device, HANDLE hCompletionPort, DWORD dwCompletionKey);
void RemoveStaleClient(ClientContext* pContext, BOOL bGraceful);
void MoveToFreePool(ClientContext *pContext);
ClientContext* AllocateContext();
LONG m_nWorkerCnt;
bool m_bInit;
bool m_bDisconnectAll;
BYTE m_bPacketFlag[5];
void CloseCompletionPort();
void OnAccept();
bool InitializeIOCP(void);
void Stop();
ContextList m_listContexts;
ContextList m_listFreePool;
WSAEVENT m_hEvent;
SOCKET m_socListen;
HANDLE m_hKillEvent;
HANDLE m_hThread;
HANDLE m_hCompletionPort;
bool m_bTimeToKill;
CCpuUsage m_cpu;
LONG m_nKeepLiveTime; // 心跳超时
// Thread Pool Tunables
LONG m_nThreadPoolMin;
LONG m_nThreadPoolMax;
LONG m_nCPULoThreshold;
LONG m_nCPUHiThreshold;
CString GetHostName(SOCKET socket);
void CreateStream(ClientContext* pContext);
BEGIN_IO_MSG_MAP()
IO_MESSAGE_HANDLER(IORead, OnClientReading)
IO_MESSAGE_HANDLER(IOWrite, OnClientWriting)
IO_MESSAGE_HANDLER(IOInitialize, OnClientInitializing)
END_IO_MSG_MAP()
bool OnClientInitializing (ClientContext* pContext, DWORD dwSize = 0);
bool OnClientReading (ClientContext* pContext, DWORD dwSize = 0);
bool OnClientWriting (ClientContext* pContext, DWORD dwSize = 0);
};
当有网络事件产生时,如何去收发数据、如何解包等关键业务逻辑将在下一节中进行讲解。
梦溪笔谈之C++ 发布了4 篇原创文章 · 获赞 0 · 访问量 464 私信 关注标签:控制软件,gh0st,pContext,void,源码,线程,bool,socListen,ClientContext 来源: https://blog.csdn.net/weixin_39380632/article/details/104163404