FreeSWITCH - mod_xml_rpc源碼分析七server.c

下面分析過程中函數先後次序,採用mod_xml_rpc.c文件內SWITCH_MODULE_RUNTIME_FUNCTION(mod_xml_rpc_runtime)函數裏abyss庫函數的出現順序。

ServerCreate

abyss_bool
ServerCreate(TServer *       const serverP,
             const char *    const name,
             xmlrpc_uint16_t const portNumber,
             const char *    const filesPath,
             const char *    const logFileName)
這應該是使用abyss庫的客戶代碼首要調用的函數。函數內代碼很容易理解,首先調用createServer進行初始化操作,如果初始化成功則再調用setNamePathLog函數初始化主目錄以及日誌文件等信息。客戶代碼內的TServer定義其實沒什麼實質內容,內部只是一個指向_TServer的指針。這個結構體內部的信息都只由庫代碼訪問。

typedef struct {
    /* Before Xmlrpc-c 1.04, the internal server representation,
       struct _TServer, was exposed to users and was the only way to
       set certain parameters of the server.  Now, use the (new)
       ServerSet...() functions.  Use the HAVE_ macros to determine
       which method you have to use.
    */
    struct _TServer * srvP;
} TServer;

createServer

在這個函數內,果然看到了爲_TServer變量申請內存初始化變量的代碼。創建完變量後,並沒有做其他更多的處理,只是爲三個函數指針賦上了相應的函數。HandlerCreate和HandlerDefaultBuiltin函數均在handler.c函數內定義。

setNamePathLog

ServerCreate函數內另一個被調用的函數是setNamePathLog。這個函數內一次調用了另三個函數:ServerSetName、ServerSetFilesPath和ServerSetLogFileName。第一個函數只是爲_TServer變量的內部name屬性賦值。ServerSetLogFileName函數的作用也只是給_TServer變量的logfilename賦值。ServerSetFilesPath函數是對另一個函數的封裝:HandlerSetFilesPath。並且向HandlerSetFilesPath函數傳遞了_TServer的builtinHandlerP函數指針。這個函數指針值在createServer函數內被賦值,指向的是HandlerCreate。HandlerSetFilesPath也是在handler.c文件中定義。突然發現剛纔分析代碼時出錯了,createServer函數內並不是將HandlerCreate函數賦給_TServer結構體內的屬性,而是調用HandlerCreate函數創建一個BIHandler變量。這個被創建的BIHandler變量將賦給_TServer結構體內的builtinHandlerP屬性。BIHandler結構體見下圖:

struct BIHandler {
    const char * filesPath;
    TList defaultFileNames;
    MIMEType * mimeTypeP;
        /* NULL means to use the global MIMEType object */
};
HandlerCreate函數內會初始化這個結構體內部的TList。這個結構體在分析data.c代碼時曾經研究過。HandlerSetFilesPath這個函數其實也不復雜,只是將主目錄賦給BIHandler結構體的filesPath屬性。


ServerInit

客戶代碼調用完ServerCreate函數後,就應該接着調用ServerInit函數了。第一步就是要創建一個TChanSwitch變量,並賦給_TServer的chanSwitchP屬性。爲了達到這個目的,最終調用的是ChanSwitchWinCreate函數,此函數在socket_win.c文件內,曾經研究過。這個函數不但創建了TChanSwitch變量,而且還生成了一個socket,並且與特定的port端口綁定成功。第一步完成後,接着就是調用ChanSwitchListen函數。

void
ChanSwitchListen(TChanSwitch * const chanSwitchP,
                 uint32_t      const backlog,
                 const char ** const errorP) {

    if (SwitchTraceIsActive)
        fprintf(stderr, "Channel switch %p listening.\n", chanSwitchP);

    (*chanSwitchP->vtbl.listen)(chanSwitchP, backlog, errorP);
}
代碼顯示最終訪問了vtbl下的listen函數。這個listen實際指向的是socket_win.c代碼內的chanSwitchListen靜態函數。

ServerAddHandler

這個函數最主要的輸入參數是處理函數。首先,依據這個傳入的函數創建一個URIHandler2的結構體變量,並將handleReq1屬性指向這個函數。然後將這個URIHandler2變量放入_TServer的handlers這個TList鏈表結構體內。

static URIHandler2 *
createHandler(URIHandler const function) {

    URIHandler2 * handlerP;

    MALLOCVAR(handlerP);
    if (handlerP != NULL) {
        handlerP->init       = NULL;
        handlerP->term       = NULL;
        handlerP->userdata   = NULL;
        handlerP->handleReq2 = NULL;
        handlerP->handleReq1 = function;
    }
    return handlerP;
}


ServerRun

static void 
serverRun2(TServer * const serverP) {

    struct _TServer * const srvP = serverP->srvP;
    outstandingConnList * outstandingConnListP;

    createOutstandingConnList(&outstandingConnListP);

    while (!srvP->terminationRequested)
        acceptAndProcessNextConnection(serverP, outstandingConnListP);

    waitForNoConnections(outstandingConnListP);
    
    destroyOutstandingConnList(outstandingConnListP);
}



void 
ServerRun(TServer * const serverP) {

    struct _TServer * const srvP = serverP->srvP;

    if (!srvP->chanSwitchP)
        TraceMsg("This server is not set up to accept connections "
                 "on its own, so you can't use ServerRun().  "
                 "Try ServerRunConn() or ServerInit()");
    else
        serverRun2(serverP);
}
ServerRun函數簡單的封裝了下serverRun2函數,執行流程最終會調用serverRun2。在實際接收請求連接的客戶端前,將創建outstandingConnList類型的變量。outstandingConnList這個類型,以及createOutstandingConnList函數都不復雜。但這裏出現的TConn之前確實沒看到過,感覺與TChannel應該有些概念上相似。conn.c文件內有TConn的定義。
typedef struct {

    TConn * firstP;
    unsigned int count;
        /* Redundant with 'firstP', for quick access */
} outstandingConnList;



static void
createOutstandingConnList(outstandingConnList ** const listPP) {

    outstandingConnList * listP;

    MALLOCVAR_NOFAIL(listP);

    listP->firstP = NULL;  /* empty list */
    listP->count = 0;

    *listPP = listP;
}

outstandingConnList類型的變量創建完畢後,將它與TServer變量一併交給acceptAndProcessNextConnection函數。從字面上來理解這個函數,它的作用應該就是接收一個新的連接。while循環的作用就是當TServer沒有被設置成關閉始終都可以接收來自客戶端的連接請求。函數餘下的部分就是TServer關閉後的清理工作。waitForNoConnections的作用就是清理掉所有已建立好的連接(由於牽扯到TConn類型,所以內部實現還不甚明瞭)。destroyOutstandingConnList函數只是釋放outstandingConnList類型的變量佔用的內存空間。ServerRun函數有一點要注意,因爲它有一個幾乎是無效循環過程,所以調用此函數的代碼必須處於一個單獨的線程內。接下來繼續分析acceptAndProcessNextConnection函數。首先調用了ChanSwitchAccept函數,這個函數在分析chanswitch.c文件時提到過,它最終調用的是socket_win.c文件內的chanSwitchAccept函數。如果一切正常,將創建好TChannel變量以及abyss_win_chaninfo結構體變量。然後再利用ConnCreate創建TConn類型的變量。TConn變量創建好後,會將它放入outstandingConnListP鏈表內,然後再調用ConnProcess啓動針對此連接的讀寫處理,我想這應該是啓動一個線程。

static void
acceptAndProcessNextConnection(
    TServer *             const serverP,
    outstandingConnList * const outstandingConnListP) {

    struct _TServer * const srvP = serverP->srvP;

    TConn * connectionP;
    const char * error;
    TChannel * channelP;
    void * channelInfoP;
        
    ChanSwitchAccept(srvP->chanSwitchP, &channelP, &channelInfoP, &error);
    
    if (error) {
        TraceMsg("Failed to accept the next connection from a client "
                 "at the channel level.  %s", error);
        xmlrpc_strfree(error);
    } else {
        if (channelP) {
            const char * error;

            freeFinishedConns(outstandingConnListP);
            
            waitForConnectionCapacity(outstandingConnListP);
            
            ConnCreate(&connectionP, serverP, channelP, channelInfoP,
                       &serverFunc, &destroyChannel, ABYSS_BACKGROUND,
                       srvP->useSigchld,
                       &error);
            if (!error) {
                addToOutstandingConnList(outstandingConnListP,
                                         connectionP);
                ConnProcess(connectionP);
                /* When connection is done (which could be later, courtesy
                   of a background thread), destroyChannel() will
                   destroy *channelP.
                */
            } else {
                xmlrpc_strfree(error);
                ChannelDestroy(channelP);
                free(channelInfoP);
            }
        } else {
            /* Accept function was interrupted before it got a connection */
        }
    }
}
創建TConn之前還調用了另兩個函數:freeFinishedConns和waitForConnectionCapacity。前一個函數的作用是清理已經處於關閉狀態的的連接,後一個函數的作用是不讓連接數超過預設值。


使用規則

經過上述幾步分析,可以獲知客戶代碼大致的使用規則應該是:

1、調用ServerCreate初始化;

2、調用ServerInit建立內部的socket,開始偵聽;

3、調用ServerAddHandller註冊客戶代碼特定的處理函數;

4、調用ServerRun啓動內部處理框架。


但這些代碼感覺還不足以構成一個完整的處理過程。不知道新的客戶端連接上後讀寫數據是如何運作的,連接斷開是如何偵測到的,等等。上面的分析有兩個函數沒有仔細研究過:ConnCreate和ConnProcess,它們屬於conn.c文件內。如果想知道上述提到的這些內容,可以直接轉至下一篇文檔。


serverFunc

如果轉去看過了conn.c內的ConnCreate和ConnProcess兩個函數分析,可以知道本文件內的serverFunc是與客戶端連接相關的線程主函數。

static void
serverFunc(void * const userHandle) {
/*----------------------------------------------------------------------------
   Do server stuff on one connection.  At its simplest, this means do
   one HTTP request.  But with keepalive, it can be many requests.
-----------------------------------------------------------------------------*/
    TConn *           const connectionP = userHandle;
    struct _TServer * const srvP = connectionP->server->srvP;

    unsigned int requestCount;
        /* Number of requests we've handled so far on this connection */
    bool connectionDone;
        /* No more need for this HTTP connection */

    requestCount = 0;
    connectionDone = FALSE;

    while (!connectionDone) {
        bool success;
        
        /* Wait to read until timeout */
        success = ConnRead(connectionP, srvP->keepalivetimeout);

        if (!success)
            connectionDone = TRUE;
        else {
            bool const lastReqOnConn =
                requestCount + 1 >= srvP->keepalivemaxconn;

            bool keepalive;
            
            processDataFromClient(connectionP, lastReqOnConn, srvP->timeout,
                                  &keepalive);
            
            ++requestCount;

            if (!keepalive)
                connectionDone = TRUE;
            
            /**************** Must adjust the read buffer *****************/
            ConnReadInit(connectionP);
        }
    }
}
這個函數最主要的就是一個循環過程。結束這個循環的條件是connectionDone變量值是FALSE。即,如果處理過程未顯示連接已結束那麼循環將一直持續下去。循環分三部分:讀取數據、處理數據和調整內部接收數據緩存區。讀取數據由ConnRead函數完成。處理數據由processDataFromClient函數完成。調整緩存區由ConnReadInit函數完成。在下一篇文章中會仔細分析ConnRead函數。ConnRead函數執行完畢後,獲取到的數據被放置在TConn的buffer數組內。

在調用processDataFromClient函數前,看到有這麼一條語句。

            bool const lastReqOnConn =
                requestCount + 1 >= srvP->keepalivemaxconn;

_TServer的keepalivemaxconn屬性在_TServer被創建時賦值爲30。這個屬性從字面上理解應該就是最多保持30個連接。但又發現這是一個serverFunc函數內的變量,不是全局變量,且是在每次收到數據和處理後累加一。因此判斷它的含義應該是單個客戶端連接最多隻能處理30個請求。難道超過了這個請求數後就不再處理了,刪除這個連接?serverFunc函數內代碼還無法完全解釋上述疑問。


processDataFromClient

static void
processDataFromClient(TConn *  const connectionP,
                      bool     const lastReqOnConn,
                      uint32_t const timeout,
                      bool *   const keepAliveP) {

    TSession session = {0};  /* initilization, an afforadble alternative to random memory being misinterpreted! */

    RequestInit(&session, connectionP);

    session.serverDeniesKeepalive = lastReqOnConn;
        
    RequestRead(&session, timeout);

    if (session.status == 0) {
        if (session.version.major >= 2)
            ResponseStatus(&session, 505);
        else if (!RequestValidURI(&session))
            ResponseStatus(&session, 400);
        else
            runUserHandler(&session, connectionP->server->srvP);
    }

    assert(session.status != 0);

    if (session.responseStarted)
        HTTPWriteEndChunk(&session);
    else
        ResponseError(&session);

    *keepAliveP = HTTPKeepalive(&session);

    SessionLog(&session);

    RequestFree(&session);
}
這個函數內出現的很多函數都存在於http.c文件內。RequestInit函數的作用只是初始化TSession變量。接着用lastReqOnConn爲TSession的serverDeniesKeepalive賦值。這個值在分析serverFunc時曾提到過。接着就是調用RequestRead函數。此函數的前部有一段說明文字可以幫助理解函數的作用。
/*----------------------------------------------------------------------------
   Read the headers of a new HTTP request (assuming nothing has yet been
   read on the session).<span style="font-family: Arial, Helvetica, sans-serif;">讀取一個新的HTTP請求的headers(假定有關這個session的任何讀操作還未執行)。</span>


   Update *sessionP with the information from the headers.<span style="font-family: Arial, Helvetica, sans-serif;">用headers裏的信息更新sessionP變量。</span>

   Leave the connection positioned to the body of the request, ready
   to be read by an HTTP request handler (via SessionRefillBuffer() and
   SessionGetReadData()).<span style="font-family: Arial, Helvetica, sans-serif;">讓connection定位(應該指的是connection的內部緩存區的定位)到請求的boby部分(應該指的是HTTP請求的body區域)。後續使用HTTP請求處理器(SessionRefillBuffer和SessionGetReadData函數)將從這裏開始處理。</span>

-----------------------------------------------------------------------------*/
這一段文字簡明扼要的表明了這個函數的處理。RequestRead函數的第二個輸入參數是個超時值,這個數值在_TServer創建時就設定好了,是15。現在還不知道在RequestRead函數內這個數值的用途是什麼。RequestRead函數內首先調用readRequestHeader函數找到請求行的位置。然後再使用parseRequestLine函數解析請求行,得到版本號、主機以及端口等信息。接着用這些解析出的數據填充TSession的requestInfo屬性(initRequestInfo)。如果在解析RequestLine時發現後面還有其他header,那麼在initRequestInfo後繼續使用readAndProcessHeaders函數解析出其餘的headers。至此,RequestRead函數執行完畢,執行又回到processDataFromClient函數內。

在檢測完版本以及URI的有效性驗證這兩步後,調用runUserHandler函數。runUserHandler函數將反向依次調用之前ServerAddHandler添加的處理函數。如果無用戶自定義的處理函數,或者自定義的處理函數未處理,那麼就將調用缺省處理函數。runUserHandler函數執行完畢後,processDataFromClient函數的最後一部分處理是向客戶端返回數據。數據返回後再調用HTTPKeepalive函數決定是否要保留當前連接。














發佈了82 篇原創文章 · 獲贊 2 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章