读懂源码系列-FileZilla Server 设计原则分析-入口分析(2)
程序员文章站
2022-07-12 12:58:52
...
1.预备知识
FileZilla Server ftp 服务器是作为 Windows 服务运行的。我们来看下安装好的服务,通过 Win + R,输入 services.msc 找到 FileZilla Server FTP server:
可以看到名为 FileZilla Server 的服务,指向了新编译生成的 *\Debug\FileZilla Server.exe 可执行文件。如果点击启动,那么 FTP 服务器就会正式运行。
Windows 操作系统不但提供了 services.msc 服务管理界面,而且还提供了 sc.exe 命令行工具,使得我们可以安装、删除、启动、停止和配置服务。
此外,还有如下 API 接口,可以完成上述功能:
OpenSCManager
CreateService
DeleteService
OpenService
StartService
ControlService
2.入口分析
FileZilla Server 项目源码,把 Windows 服务管理功能和 FTP 服务器本身功能结合在一起了。
首先,找到包含 WinMain 函数的文件 Service.cpp。入口函数可以分为两个部分:
1.根据命令行选项,完成自身的安装、删除、启动、停止和配置服务;
2.作为服务程序入口 等待 StartServiceCtrlDispatcher 函数返回;
注意 StartServiceCtrlDispatcher 这个函数,使得当前调用进程与服务控制管理器(SCM)进程 services.exe 建立连接,并使调用进程的主线程成为服务控制派遣线程,服务控制派遣线程之后将处理 SCM 发送的服务控制请求,直到所有服务都停止,派遣线程才返回。
接下来看以下代码段:
const SERVICE_TABLE_ENTRY servicetable[] =
{
{ServiceName,(LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL,NULL}
};
BOOL success;
success = StartServiceCtrlDispatcher(servicetable);
SERVICE_TABLE_ENTRY 可以指定服务进程的服务名和服务入口,对于每一个服务入口,都会新建一个线程,用于执行服务入口。本服务只有一个入口,就是 ServiceMain。因此,主线程调用 StartServiceCtrlDispatcher 函数后,服务进程有两个线程:
1.主线程,进入 StartServiceCtrlDispatcher ,并且在之后处理 SCM 的控制命令;
2.新建服务线程,执行 ServiceMain;
根据以上分析,执行流程就进入了 ServiceMain 函数:
1.注册 SCM 控制命令处理函数 ServiceCtrlHandler ,这个函数由主线程执行,可以处理服务停止等 SCM命令;
void ServiceCtrlHandler(DWORD nControlCode)
{
BOOL success;
switch (nControlCode) {
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
nServiceCurrentStatus = SERVICE_STOP_PENDING;
success = UpdateServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 3000);
KillService();
return;
case 128:
SendReloadConfig();
break;
default:
break;
}
UpdateServiceStatus(nServiceCurrentStatus, NO_ERROR, 0, 0, 0);
}
2.新建线程 ServiceExecutionThread ,作为 FTP 服务器的主线程;DWORD ServiceExecutionThread(LPDWORD param)
{
// initialize Winsock library
BOOL res = TRUE;
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
int nResult = WSAStartup(wVersionRequested, &wsaData);
if (nResult != 0)
res = FALSE;
else if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
res = FALSE;
}
if (!res)
{
SetEvent(killServiceEvent);
UpdateServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
return 0;
}
CServer *pServer = new CServer;
VERIFY(pServer->Create());
if (!nServiceRunning)
PostQuitMessage(0);
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
delete pServer;
WSACleanup();
SetEvent(killServiceEvent);
return 0;
}
可以看到, FTP 主线程,是一个 Windows 消息循环。这个也是我们接下来主要分析的部分。
3.等待服务结束事件
3.结构总结
我们通过忽略所有细节,分析出了 FTP 服务器的整体结构。结构如下:
从外围代码来看,服务程序有3个线程:
1.主线程,直到服务处于停止状态,才从 StartServiceCtrlDispatcher 函数返回,并负责执行 SCM 发送的命令;
2.服务入口线程,执行 ServiceMain函数。函数体里边会等待一个事件,以及等待 FTP 主线执行完毕,才会返回;
3.FTP 主线程,初始化服务器,并进入Windows 消息循环。消息循环结束后,线程结束。
所以,我们对 FTP 服务器的分析,将集中于 FTP主线程部分。