读懂源码系列-FileZilla Server 设计原则分析-socket 事件响应流程(3)

来源:互联网 发布:中日韩围棋 知乎 编辑:程序博客网 时间:2024/06/05 23:04

1. 网络模型初探

        FTP 服务器会监听指定的 21 端口,等待用户连接,这是所谓的控制连接。当需要数据传输时,客户端和服务器之间会再建立一个数据连接。
        本期文章暂不涉及 FTP 协议的处理过程,重点在于梳理 FileZilla Server 的网络模型。
接上一期,FTP 服务器初始化线程代码。该线程创建了 CServer 类,执行流程来到 CServer::Create 函数(节选):
bool CServer::Create(){//Create windowRegisterClassEx(&wndclass);m_hWnd = CreateWindow(_T("FileZilla Server Helper Window"), _T("FileZilla Server Helper Window"), 0, 0, 0, 0, 0, 0, 0, 0, GetModuleHandle(0));if (!m_hWnd)return false;SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG)this);hMainWnd = m_hWnd;//ListenSocketif (CreateListenSocket()) {m_nServerState = STATE_ONLINE;ShowStatus(_T("Server online."), 0);}elseShowStatus(_T("Server not online."), 1);//AdminListenSocketCreateAdminListenSocket();return true;}
        忽略其他初始化工作,我们来关注监听 socket 的建立,以此探究 FTP 服务器的网络模型。依次跟踪以下代码:
CServer::Create()CServer::CreateListenSocket()CServer::CreateListenSocket(CStdString ports, bool ssl)CAsyncSocketEx::Create(UINT nSocketPort, int nSocketType, long lEvent, LPCTSTR lpszSocketAddress, int nFamily, bool reusable)
        可以发现其实 CListenSocket 是 CAsyncSocketEx 的派生类。在 CAsyncSocketEx::Create 里,进行了如下工作:
                1.创建原始 socket;
                2.将原始 socket 存入辅助窗口 CAsyncSocketExHelperWindow;
                3.调用 WSAAsyncSelect 将 socket 和辅助窗口关联并指定发生感兴趣的 socket 事件时,使用特定的消息通知窗口关联的过程;
        其中第 2 点是最关键的,依次跟踪以下代码,发现每个 socket 关联了一个索引: 
CAsyncSocketEx::AttachHandle(SOCKET hSocket, int family)CAsyncSocketExHelperWindow::AddSocket(CAsyncSocketEx *pSocket, int &nSocketIndex)
        而第 3 点的巧妙之处在于,为位置为 index 的 socket 指定的消息为 WM_SOCKETEX_NOTIFY + index。因此,每个 socket 都直接对应一条自定义消息,消息分发速度较MFC CAsyncSocket 快(未验证,该项目声称。出处见 AsyncSocketEx.h 文件注释部分)。
        此外,CAsyncSocketExHelperWindow 最重要的就是里边的窗口过程。发生 socket 事件时,由辅助窗口的窗口过程处理。发生 FD_ACCEPT 事件时,该窗口过程调用 CAsyncSocketEx::OnAccept(int) 虚函数。

        同时,为了保证每个线程只有一个 CAsyncSocketExHelperWindow,在 CAsyncSocketEx 类内有以下两个成员:
//Pointer to the data of the local threadstruct t_AsyncSocketExThreadData{CAsyncSocketExHelperWindow *m_pHelperWindow{};int nInstanceCount{};std::list<CAsyncSocketEx*> layerCloseNotify;} *m_pLocalAsyncSocketExThreadData{};//List of the data structures for all threadsstatic thread_local t_AsyncSocketExThreadData* thread_local_data;
       并且,CAsyncSocketEx 类具有以下初始化代码:
bool CAsyncSocketEx::InitAsyncSocketExInstance(){//Check if already initializedif (m_pLocalAsyncSocketExThreadData) {return true;}DWORD id = GetCurrentThreadId();// Get thread specific dataif (!thread_local_data) {thread_local_data = new t_AsyncSocketExThreadData;thread_local_data->m_pHelperWindow = new CAsyncSocketExHelperWindow(thread_local_data);}m_pLocalAsyncSocketExThreadData = thread_local_data;++m_pLocalAsyncSocketExThreadData->nInstanceCount;return true;}
        因此,这就保证了所有同一个线程里的 CAsyncSocketEx 对象,共享一个 t_AsyncSocketExThreadData 结构体,即共享一个 辅助窗口。
        举例来说,bool CServer::Create() 代码里,至少创建了两个 CAsyncSocketEx 对象as1,as2。as1 创建时,由于整个线程还未有 CAsyncSocketEx 对象。因此thread_local_data = 0,新建了辅助窗口。而 as2 创建时,thread_local_data  != 0,此时实例数量为2。整体图解如下:

2.总结

        通过忽略具体的处理流程,我们找到了 socket 事件的响应流程。那么具体如何处理 socket 时间呢,这就涉及到具体的执行线程了。将由下一节讲述。本章有意思的地方有:
        1.通过 static thread_local 关键字设计同一线程内所有对象共享的数据
        2.通过索引加快消息分发
阅读全文
0 0
原创粉丝点击