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函数决定是否要保留当前连接。














你可能感兴趣的:(freeswitch,abyss)