Posted on 2011-12-04 10:46
eamon 阅读(241)
评论(0) 编辑 收藏 引用 所属分类:
Filezilla Server
Filezilla Server采用标准的Windows系统服务模式,入口代码放在Service.cpp 主要用户处理用户对该系统服务的控制,如启动、停止、重启等。由于采用的不是桌面应用程序方式,给软件调试带来一定的困难。
Service.cpp:系统服务程序入口
WinMain是整个服务的入口函数,在WinMain里依次完成了下面几项任务:
1、参数解析,根据参数类型设置相应的操作如安装、卸载、启动、停止。SetServiceName设置服务名,SetServiceDisplayName设置在服务管理器中显示的名字。系统还提供了一种Compact模式,通过CompatMain执行系统入口,该函数仅提供启动、停止、设置管理员端口和重新加载配置四种功能。
2、初始化某些数据。通过LoadServiceName加载服务名和显示名到全局变量中。如果入口参数设置为设置管理端口,则通过SetAdminPort 设置管理员端口, 如果为重载配置,使用 ReloadConfig()函数完成重载配置,该函数调用了SendReloadConfig函数,枚举所有顶层窗口,并向本程序的窗口发送WM_FILEZILLA_RELOADCONFIG消息,然后由SCM(服务控制管理器)向本系统服务发送128号消息。[此处似乎重复处理了,服务在处理128号消息时又会调用SendReloadConfig函数,可能是为了处理服务没有启动就重载配置的情况]
3、由SCM(服务控制管理器)启动服务,入口为ServiceMain函数;如果服务不存在,进入步骤4
4、根据参数设置服务,例如安装、启动、卸载等。
服务启动时,通过StartServiceCtrlDispatcher 将服务的控制权转移到ServiceMain,它注册ServiceCtrlHandler来处理服务收到的消息,在回调函数ServiceCtrlHandler中自定义了128号控制码,用于向窗口"FileZilla Server Helper Window"发送重新读取配置的消息WM_FILEZILLA_RELOADCONFIG。serviceMain注册Handler成功之后,创建了 killServiceEvent 事件。然后启动工作线程ServiceExecutionThread,并将服务的状态调整为SERVICE_RUNNING,然后等待killServiceEvent 事件和工作线程结束允许,并将状态调整为SERVICE_STOPPED,程序结束。
工作线程ServiceExecutionThread首先初始化Winsock网络库,如果初始化不成功就设置killServiceEvent 事件,将状态调整为SERVICE_STOPPED,并返回结束该线程和整个程序的执行。然后,创建了CServer对象,创建消息循环将实际流程交由CServer,之后进入线程的消息循环并等待killServiceEvent信号以退出线程终止服务。
服务状态的更新都是通过UpdateServiceStatus函数实现,该函数设置SERVICE_STATUS变量的值,调用SetServiceStatus设置服务的状态,设置不成功就调用KillService向主窗口发送WM_CLOSE关闭消息。主窗口结束运行后,工作线程的消息循环结束,并设置killServiceEvent 结束程序,服务。
Server.cpp:真正的流程处理类
构造函数创建了一个CAdminInterface类的指针,并将自身作为参数传递给这个指针的构造函数,并将所在线程的优先级设置为THREAD_PRIORITY_ABOVE_NORMAL。
Create()创建一个类名和窗口民均为"FileZilla Server Helper Window"的窗口,这是整个程序的主窗口。并将this指针附到窗口上,方便处理发送给该窗口的消息。然后将窗口句柄赋给全局变量hMainWnd。随后初始化 option、log 和 banmanager三个指针。根据配置文件创建一定数量的处理线程,每个线程都有一个WM_FILEZILLA_SERVERMSG+ID的通知ID,GetNextThreadNotificationID获得下个一个可用的ID号。用于区分不同线程发送的消息。包括两个定时器,m_nTimerID和m_nBanTimerID,在OnTimer函数中处理。再往下调用了CreateListenSocket函数,这个函数根据Options类中获取的port、bindip、enablessl等参数创建监听ftp客户端连接的CListenSocket对象指针,并保存到m_ListenSocketList中。最后调用CreateAdminListenSocket函数创建监听admin客户端的socket,并存入m_AdminListenSocketList中。
OnClose(); 首先关闭所有m_ListenSocketList中的监听端口,如果m_ThreadArray和m_ClosedThreads均为空,即所有处理线程都已关闭,则销毁主窗口,退出。否则,给两个链表的线程发送FTM_GOOFFLINE的消息,让其下线。
GetHwnd:返回创建窗口的句柄。
void OnTimer(UINT nIDEvent);处理两个定时器消息,1234(m_nTimerID)和1235(m_nBanTimerID)分别于10秒和60秒触发,m_nTimerID用于检测管理端连接的超时,日志文件是否超过限制,m_nBanTimerID用于检测禁止用户的解禁。
unsigned int GetNextThreadNotificationID();查找下一个可用的通知ID号,m_ThreadNotificationIDs数组包含所有已经创建的线程的指针。首先查找有没有可用的,没有则在数组最后添加一个空的对象,并返回它的索引号。
void FreeThreadNotificationID(CServerThread *pThread);删除某个线程的编号记录,将对于的指针设置为0。这里并没有用delete删除线程的指针,因为在线程退出时它已经自动删除了。
void SendState();将服务器的状态m_nServerState通过m_pAdminInterface发送给管理端窗口。
ShowStatus,它的任务是将信息发送给admin窗口和记录到log中,他是两个重载的函数,其中一个包括消息的时间。
下面是几个比较重要的处理函数,包括消息处理、创建监听器、处理管理端发回的命令。
WindowProc 处理发送给主窗口的消息,WM_CLOSE、WM_TIMER消息均调用相应成员函数处理,WM_FILEZILLA_RELOADCONFIG自定义消息调用COptions和CPermissions的ReloadConfig函数重新加载配置文件的参数设置。WM_DESTROY退出主处理流程,首先给所有线程发送WM_QUIT消息,并等待线程退出,然后,关闭并删除m_AdminListenSocketList中的所有监听器,删除COption指针,关闭m_nTimerID定时器,并向系统发送PostQuitMessage退出主窗口的消息循环(此处没有关闭m_nBanTimerID定时器,对m_AdminListenSocketList也没有处理,不知道为什么??)。线程发送过来的消息交给OnServerMessage函数处理。
LRESULT OnServerMessage(CServerThread *pThread, WPARAM wParam, LPARAM lParam);处理线程发送给主窗口的消息。pThread为发送消息线程的指针。这个函数比较复杂,慢慢分析。
BOOL ToggleActive(int nServerState);变换服务器的状态。在defs.h 中定义了服务器的6个状态。包括在线、离线、锁定等。如果nServerState为GOOFFLINE_NOW 则关闭所有的ListenSocket,清空Listensocket列表,向ClosedThread发送FTM_GOOFFLINE消息,向m_ThreadArray中的每个ServerThread发送FTM_GOOFFLINE消息,并将其加入到ClosedThread列表。清空m_ThreadArray列表,并从服务器状态中去除ONLINE位。如果ClosedThread不为空则在服务器状态中添加GOOFFLINE_NOW 位。如果nServerState为GOOFFLINE_LOGOUT则关闭所有的ListenSocket,清空Listensocket列表,向ClosedThread发送FTM_GOOFFLINE消息,向m_ThreadArray中的每个ServerThread发送FTM_GOOFFLINE消息,并将其加入到ClosedThread列表。清空m_ThreadArray列表,并从服务器状态中去除ONLINE位。如果ClosedThread不为空则在服务器状态中添加GOOFFLINE_LOGOUT 位。如果nServerState为GOOFFLINE_WAITTRANSFER则关闭所有的ListenSocket,清空Listensocket列表,向ClosedThread发送FTM_GOOFFLINE消息,向m_ThreadArray中的每个ServerThread发送FTM_GOOFFLINE消息,并将其加入到ClosedThread列表。清空m_ThreadArray列表,并从服务器状态中去除ONLINE位。如果ClosedThread不为空则在服务器状态中添加GOOFFLINE_WAITTRANSFER位。如果nServerState为ONLINE先看ListenSocketList是否为空,为空则创建监听Socket,然后创建ServerThread,然后设置ListenSocketList中的m_bLocked属性位。最后设置ClosedThread为“等待直到退出”状态,并更改服务器的状态变量,调用SendState函数发送服务器状态。
BOOL ProcessCommand(CAdminSocket *pAdminSocket, int nID, unsigned char *pData, int nDataLength);AdminInterface调用该函数,处理AdminSocket接收到的命令,以后慢慢分析。
BOOL CreateListenSocket();创建FTP监听Socket,首先确定端口,如果 配置文件没有设置则采用SSL端口,如果都没有设置则返回错误信息。此处可以设置多个端口。对每个端口(包括SSL端口)和设置的IP绑定都要创建一个CListenSocket,继承自CAsyncSocketEx类,参数包括CServer指针和SSL标志,然后开始监听,对监听或创建失败的端口和IP通过ShowStatus显示给管理客户端并记录到日志。
BOOL CreateAdminListenSocket();创建管理监听Socket,涉及到的配置文件选项包括:管理员Ip绑定,端口CAdminListenSocket 继承自CAsyncSocketEx类,包含一个CAdminInterface的指针。首先如果绑定IP为*,则监听所有的本机的网络地址,否则只监听127.0.0.1的本地管理Socket,端口为配置文件中设置的端口。如果创建失败,则创建一个127.0.0.1的Socket,端口由系统分配。创建成功则将端口保存到变量nAdminPort中,通过对话框提示用户。创建成功以后开始监听,并将指针添加到列表中。最后,对IPBinding中的每一个IP创建Socket并监听,添加到列表中,无法创建的地址则通过对话框显示。
int DoCreateAdminListenSocket(UINT port, LPCTSTR addr, int family);
下面看一下几个线程类:CServerThread