大漠落日

while(!dead) study++;
posts - 46, comments - 126, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

FileZilla Server源码分析(2)

Posted on 2010-06-03 17:31 乱78糟 阅读(2537) 评论(0)  编辑 收藏 引用 所属分类: 开源
上一节讲述的基本都是些做辅助的代码,本节分析诸多socket类的父类CAsyncSocketEx和相关的Layer类。

PS:拼音打字错别字很多- -。

从CAsyncSocketEx和CAsyncSocketExLayer类文件开头注释部分写到:

如何使用?
-----------
和MFC的CAsyncSocket非常像,如果不需要强化CAsyncSocket,那么在需要使用的时候只需替换掉CAsyncSocket即可。

为什么这个类快一些?
-------------------
CAsyncSocketEx只是在分发通知事件消息的时候稍微快一点。
首先来了解一下CAsyncSocket是如何工作的。对每个线程使用CAsyncSocket就会相应有一个窗口被创建。CAsyncSocket利用那个窗口的句柄调用WSAsyncSocket 。直到这儿,CAsyncSocket和它的工作方式是一样的。但是CAsyncSocket对一个线程中的所有sockets仅使用一个windows消息(WM_SOCKET_NOTIFY)。当这个窗口收到 WM_SOCKET_NOTIFY 时,wParam参数包含socket句柄并且这个窗口使用map来查找一个CAsyncSocket实例。CAsyncSocketEx原理不同于此。它的辅助窗口(helper window)使用一定范围内不同的window消息(WM_USER到OXBFFF)并且对每个socket传递不同的消息给WSAAsyncSelect,当这个指定范围内消息被接收的时候,CAsyncSocketEx使用这个消息的索引减去WM_USER的值配合指向
CAsyncSocketEx实例数组的指针来查找。如你所见,CAsyncSocketEx以更加高效的方式来使用辅助窗口,因为它不需要使用缓慢的maps来查找自己的实例。然后,速度增加的并不多,但是当同时使用大量的sockets的时候它的效果可能很明显。

请注意这个变动并没有影响到原始数据吞吐效率,CAsyncSocketEx仅仅是分发通知消息的时候更加快速而已。

CAsyncSocketEx还提供了什么?
---------------------------
CAsyncSocketEx提供了一个灵活的层系统。一个例子就是代理层。创建一个代理层实例,配置并将其加入到CAsyncSocketEx实例的层链(layer chain),之后,你就能够通过代理进行连接。
好处:你不需要做很多变动就可以使用层系统。
另一个层就是当前正在开发(注:目前已经完成,作者忘记修正这个注释了)的SSL层用来进行SSL加密连接。

从作者的注释中我们可以大致了解这些类的功能和原理,源码我也仅挑若干个人比较感兴趣的部分稍微分析一下。

CAsyncSocketEx类和cAsyncSocketExLayer类互为友元类。
CAsyncSocketEx类中有大量的#ifndef NOLAYERS ... #endif ,FileZillaServer工程中没有定义NOLAYERS,所以这些代码都要被编译。
#ifndef NOLAYERS
    
//Layer chain
    CAsyncSocketExLayer *m_pFirstLayer;
    CAsyncSocketExLayer 
*m_pLastLayer;

    friend CAsyncSocketExLayer;

    
//Called by the layers to notify application of some events
    virtual int OnLayerCallback(std::list<t_callbackMsg>& callbacks);
#endif //NOLAYERS
上面那一段代码是为了构造了作者注释中所说的层链,每个CAsyncSocketEx实例可以通过调用AddLayer 函数添加一个层。
成员变量m_pendingCallbacks保存的是所有等待被调用的回调信息。当WindowProc 参数message等于WM_USER+2的时候,调用OnLayerCallback, CAsyncSocketEx该虚成员函数仅做了清理工作,实际任务需要派生类去派生处理。

相对于FD_READ作者又定义了#define FD_FORCEREAD (1<<15) 来跳过检测是否有数据等待。现在用伪代码描述一下WindowProc函数的工作:
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   
if (message>=WM_SOCKETEX_NOTIFY)
   {
      
//根据message-(WM_USER+3)的值查找socket,WM_USER+3 == WM_SOCKETEX_NOTIFY
      if (!pSocket->m_pFirstLayer)
          
//分发通知消息,例如FD_READ,FD_CONNECT等
       else
          
//分发通知消息给最底层,即 pSocket->m_pLastLayer->CallEvent(nEvent, nErrorCode);
    }
    
else if (message == WM_USER)
        
//处理某一层发送的通知事件
    else if (message == WM_USER+1)
        
//通知连接的状态,即调用虚函数OnConnect
    else if (message == WM_USER + 2)
        
//处理等待的回调信息
    else if (message == WM_TIMER)
        
//这种情况因为收到FD_CLOSE事件时仍然有数据未读取,导致调用 pSocket->ResendCloseNotify()重发关闭消息,这个函数启动了定时器。
       
//重发FD_CLOSE消息通知socket关闭
}
CAsyncSocketEx类中虚函数,如OnAcceptOnSendOn打头的函数用于通知特定事件的发生状态,派生类如果需要获得这些状态信息,就可以自己派生这些函数。

全局的m_spAsyncSocketExThreadDataList则定义了一个t_AsyncSocketExThreadData(即分发线程)的链表,也就是说FileZilla可以有多个分发线程,每个分发线程对应多个socket,即CAsyncSocketEx。

举一个实际的场景:
在FileZillaServer启动时,缺省监听了两个端口:21和admin端口,因此就有两个socket,即两个CAsyncSocketEx。这两个CAsyncSocketEx共用一个分发线程:t_AsyncSocketExThreadData
当有用户通过FTP连接上server并通过get/mget命令下载文件时,这时FTP服务器会启动一个传输线程在一个临时端口进行监听,这时会增加一个CAsyncSocketEx,同时也增加一个负责这个CAsyncSocketEx的分发线程,因此m_spAsyncSocketExThreadDataList里也会增加一个结点。
这时的状况是:一个m_spAsyncSocketExThreadDataList链,两个t_AsyncSocketExThreadData,三个 CAsyncSocketEx。

下一节分析核心代码。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理