读懂源码系列-FileZilla Server 设计原则分析-socket 事件处理流程(4)

来源:互联网 发布:淘宝商品信用卡套现 编辑:程序博客网 时间:2024/06/06 01:07

1.前言

        上一篇讲到 socket 发生 FD_ACCEPT 事件时,处理流程到达辅助窗口的窗口过程。那么 FD_ACCEPT 事件是如何处理的呢?本篇带领大家一探究竟。

2.处理流程

        首先跟踪如下函数:
static LRESULT CALLBACK CAsyncSocketExHelperWindow::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)void CListenSocket::OnAccept(int nErrorCode)BOOL CAsyncSocketEx::Accept( CAsyncSocketEx& rConnectedSocket, SOCKADDR* lpSockAddr /*=NULL*/, int* lpSockAddrLen /*=NULL*/ )void CServerThread::AddSocket(SOCKET sockethandle, bool ssl)BOOL CThread::PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam)int CServerThread::OnThreadMessage(UINT Msg, WPARAM wParam, LPARAM lParam)void CServerThread::AddNewSocket(SOCKET sockethandle, bool ssl)
        第 1 行的函数,是辅助窗口的窗口过程。当 FTP 服务器的监听端口发生 FD_ACCEPT 事件时,辅助窗口调用 CAsyncSocketEx::OnAccept(int ) 虚函数,但监听 socket 已将该虚函数覆盖。因此,处理流畅来到第 2 行。
        第 2 行的函数,主要做了两个工作。第一工作是第 3 行,实际上接受客户端的连接,得到一个已连接的套接字 sockethandle;第二个工作是第 4 行,将 sockethandle 交由某个 CServerThread 线程处理。
        第 4 ~ 7 行的函数,即是线程如何处理 sockethandle 的过程。
void CServerThread::AddSocket(SOCKET sockethandle, bool ssl){PostThreadMessage(WM_FILEZILLA_THREADMSG, ssl ? FTM_NEWSOCKET_SSL : FTM_NEWSOCKET, (LPARAM)sockethandle);}
        注意,此处的 PostThreadMessage 只有 3 个参数,并不是 Win32 SDK 里的函数。而是如下函数:
BOOL CThread::PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam){BOOL res=::PostThreadMessage(m_dwThreadId, message, wParam, lParam);;ASSERT(res);return res;}
        可以看到,FTP 服务器使用了投递线程消息的方式,去处理已连接的套接字。那么该线程必有 Windows 消息循环。
        CThread 是 CServerThread 的基类,CThread 是线程的包装器。可以发现,以下函数就是线程中的消息循环:
DWORD CThread::Run(){InitInstance();SetEvent(m_hEventStarted);m_started = true;MSG msg;while (GetMessage(&msg, 0, 0, 0)) {// Since we do not handle keyboard events in the thread, don't translate messages.if (!msg.hwnd)OnThreadMessage(msg.message, msg.wParam, msg.lParam);DispatchMessage(&msg);}DWORD res = ExitInstance();delete this;return res;}
        这个函数,使用了设计模式中的模版方法。在进入消息循环之前,派生类可以覆盖 InitInstance 虚函数完成指定的初始化任务;而在退出消息循环之后,派生类可以覆盖 ExitInstance 虚函数完成指定的析构任务。
        重点看消息循环,当通过 ::PostThreadMessage 向指定线程投递消息时,调用 GetMessage 得到的消息 msg,其 msg.hwnd == NULL。因为该消息不属于任何窗口,而此后 DispatchMessage 也无法调用指定窗口的窗口过程。
        所以,处理流程来到了 CServerThread::OnThreadMessage---->CServerThread::AddNewSocket。在 AddNewSocket 函数中,我们看到已连接的套接字 sockethandle 与一个 CControlSocket 对象关联起来。没错,CControlSocket 是 CAsyncSocketEx 的派生类。此时,已连接的套接字,就与这个线程里唯一的辅助窗口关联起来。当客户端通过这个套接字发送命令到服务器时,系统发送 FD_READ 可读通知到该线程的消息队列,而  CThread::Run 中的 DispatchMessage 将把该消息发送给辅助窗口的窗口程序处理。
        至此,sokcet 事件 FD_ACCPET 的大致处理过程已经分析完毕。示意图如下:


        线程的选择其实也是一大学问,涉及到负载均衡问题。这里先不展开。下面来看一下,服务器线程池的建立。

3.服务器线程 CServerThread

       可以看到,服务器中的 CServerThread 是服务器线程池中的线程。在 FTP 主线中,有一个主窗口,其句柄值为 hMainWnd。线程池中的所有线程,通过 PostMessage 与主线程通信。那主线程中,如何区别是哪个线程发送的消息呢?答案就在  CServerThread 的创建代码中:
//Create the threadsint num = (int)m_pOptions->GetOptionVal(OPTION_THREADNUM);for (int i = 0; i < num; ++i) {int index = GetNextThreadNotificationID();CServerThread *pThread = new CServerThread(WM_FILEZILLA_SERVERMSG + index);m_ThreadNotificationIDs[index] = pThread;if (pThread->Create(THREAD_PRIORITY_NORMAL, CREATE_SUSPENDED)) {pThread->ResumeThread();m_ThreadArray.push_back(pThread);}}
        每个 CServerThread 创建时,都得到了一个关联的通知ID = WM_FILEZILLA_SERVERMSG + index,其中 index 是这个线程在主线程中的存储位置索引。当特定线程使用 PostMessage 向主线程传递消息时,把 ID 作为消息值,即:
PostMessage(hMainWnd, ID, 0, 0)
       当主线程收到消息时,把 ID 值减去 WM_FILEZILLA_SERVERMSG 即可得到是哪个线程发送的消息。

4.总结

        至此,我们得出了 FTP 服务器的整体通信机制:
        已客户端连接服务器为例。首先,FTP服务器创建了主窗口 hMainWnd 用于处理全局性的任务。然后当监听 socket 创建的时候,辅助窗口 hHelperWnd 就建立了起来。
在每个拥有 CAsyncSocketEx 对象的线程中,都有辅助窗口,用于处理所有 socket 通知。
        当客户端连接服务器时,hHelperWnd 收到 FD_ACCEPT 通知,并调用 accept 建立控制套接字。并把这个控制套接字关联到某个 CServerThread 线程。这样 ControlSocket 上的所有通知就由这个指定的 CServerThread 线程处理了。

阅读全文
0 0
原创粉丝点击