FreeSWITCH - mod_xml_rpc源码分析八conn.c


TConn

struct _TConn {
    struct _TConn * nextOutstandingP;
        /* Link to the next connection in the list of outstanding
           connections
        */
    TServer * server;
    uint32_t buffersize;
        /* Index into the connection buffer (buffer[], below) where
           the next byte read on the connection will go.
        */
    uint32_t bufferpos;
        /* Index into the connection buffer (buffer[], below) where
           the next byte to be delivered to the user is.
        */
    uint32_t inbytes,outbytes;  
    TChannel * channelP;
    void * channelInfoP;
        /* Information about the channel, such as who is on the other end.
           Format depends on the type of channel.  The user of the connection
           is expected to know that type, because he supplied the channel
           when he created the channel.

           NULL means no channel info is available.
        */
    bool hasOwnThread;
    TThread * threadP;
    bool finished;
        /* We have done all the processing there is to do on this
           connection, other than possibly notifying someone that we're
           done.  One thing this signifies is that any thread or process
           that the connection spawned is dead or will be dead soon, so
           one could reasonably wait for it to be dead, e.g. with
           pthread_join().  Note that one can scan a bunch of processes
           for 'finished' status, but sometimes can't scan a bunch of
           threads for liveness.
        */
    const char * trace;
    TThreadProc * job;
    TThreadDoneFn * done;
    char buffer[BUFFER_SIZE];
};
TConn结构体内部可以保存TChannel和abyss_win_chaninfo两类结构体变量的指针(channelInfoP)。在之前分析socket_win.c文件时接触过这两个结构体类型。TChannel封装了客户端连接。上一篇文章分析server.c时了解到新的客户端连接上时会创建TConn类型的变量。给人的感觉就是TConn也是针对客户端连接的封装。

我们可以再次看看TChannel的声明。implP是个指向无类型的指针,即可以指向任何类型。在分析socket_win.c文件时提到过它将指向socketWin结构体变量。设计成无类型指针的目的是为了让库的核心处理框架代码可以在多个操作系统平台下都可以运作良好。vtbl保存了几个静态函数指针,包括接收数据和发送数据等。也就是说,TChannel保存的都是与特定操作系统平台相关的信息:特定操作系统平台下socket的实现函数,以及特定操作系统平台下socket及其相关对象(例如windows事件句柄)的信息。

struct _TChannel {
    uint                signature;
        /* With both background and foreground use of sockets, and
           background being both fork and pthread, it is very easy to
           screw up socket lifetime and try to destroy twice.  We use
           this signature to help catch such bugs.
        */
    void *              implP;
    struct TChannelVtbl vtbl;
};
abyss_win_chaninfo结构体定义如下。应该也能明白,这也是为了封装socket在windows下的实现,保存了sockaddr结构体内容。
struct abyss_win_chaninfo {
    size_t peerAddrLen;
    struct sockaddr peerAddr;
};

TChannel和abyss_win_chaninfo两个结构体属于客户端连接这一概念中较低层的数据,但又由于它们与特定操作系统平台相关,因此abyss库将它们单独封装成一类结构体。其他更抽象一点、与操作系统不太相关的信息,例如接收、发送缓冲区等,与TChannel结构体作为同一级别的信息组合成TConn结构体。在研究thread_windows.c文件时,也指出与线程相关的代码也是与平台特定相关的,因此也将其封装成了一个结构体TThread,针对不同平台都有各自平台下的TThread实现。


ConnCreate

void
ConnCreate(TConn **            const connectionPP,
           TServer *           const serverP,
           TChannel *          const channelP,
           void *              const channelInfoP,
           TThreadProc *       const job,
           TThreadDoneFn *     const done,
           enum abyss_foreback const foregroundBackground,
           bool                const useSigchld,
           const char **       const errorP) {
/*----------------------------------------------------------------------------
   Create an HTTP connection.

   A connection carries one or more HTTP transactions (request/response).

   *channelP transports the requests and responses.

   The connection handles those HTTP requests.

   The connection handles the requests primarily by running the
   function 'job' once.  Some connections can do that autonomously, as
   soon as the connection is created.  Others don't until Caller
   subsequently calls ConnProcess.  Some connections complete the
   processing before ConnProcess return, while others may run the
   connection asynchronously to the creator, in the background, via a
   TThread thread.  'foregroundBackground' determines which.

   'job' calls methods of the connection to get requests and send
   responses.

   Some time after the HTTP transactions are all done, 'done' gets
   called in some context.

   'channelInfoP' == NULL means no channel info supplied.
-----------------------------------------------------------------------------*/
    TConn * connectionP;

    MALLOCVAR(connectionP);

    if (connectionP == NULL)
        xmlrpc_asprintf(errorP, "Unable to allocate memory for a connection "
                        "descriptor.");
    else {
        connectionP->server       = serverP;
        connectionP->channelP     = channelP;
        connectionP->channelInfoP = channelInfoP;
        connectionP->buffer[0]    = '\0';
        connectionP->buffersize   = 0;
        connectionP->bufferpos    = 0;
        connectionP->finished     = FALSE;
        connectionP->job          = job;
        connectionP->done         = done;
        connectionP->inbytes      = 0;
        connectionP->outbytes     = 0;
        connectionP->trace        = getenv("ABYSS_TRACE_CONN");

        makeThread(connectionP, foregroundBackground, useSigchld, errorP);
    }
    *connectionPP = connectionP;
}
ConnCreate函数过程很简单,就是创建TConn变量然后再初始化各项属性。job函数指针指向的是server.c文件内的serverFunc函数。done函数指针指向的是server.c文件内的destroyChannel函数。前一个函数可以看成是与这个客户端连接相对应的线程处理主函数。后一个函数可以看成是针对此客户端连接的清理函数。初始化各项参数完毕后再依据foregroundBackground参数决定是否为此客户端连接单独创建一个线程。调用ConnCreate时始终传入的都是ABYSS_BACKGROUND,所以针对每个客户端连接都将创建一个线程。

ConnCreate函数的作用是创建TConn变量,并初始化各项属性,最后再创建一个与此相对应的线程。


ConnProcess

bool
ConnProcess(TConn * const connectionP) {
/*----------------------------------------------------------------------------
   Drive the main processing of a connection -- run the connection's
   "job" function, which should read HTTP requests from the connection
   and send HTTP responses.

   If we succeed, we guarantee the connection's "done" function will get
   called some time after all processing is complete.  It might be before
   we return or some time after.  If we fail, we guarantee the "done"
   function will not be called.
-----------------------------------------------------------------------------*/
    bool retval;

    if (connectionP->hasOwnThread) {
        /* There's a background thread to handle this connection.  Set
           it running.
        */
        retval = ThreadRun(connectionP->threadP);
    } else {
        /* No background thread.  We just handle it here while Caller waits. */
        (connectionP->job)(connectionP);
        connDone(connectionP);
        retval = TRUE;
    }
    return retval;
}
在ConnCreate函数内每个TConn变量的hasOwnThread属性值都是true。因此ConnProcess函数内始终执行的都是ThreadRun函数。在研究thread_windows.c文件时看到过此函数,它只是封装了widnows平台下的ResumeThread函数。

bool
ThreadRun(TThread * const threadP) {
    return (ResumeThread(threadP->handle) != 0xFFFFFFFF);
}
目的就是启动线程。

ConnProcess函数的作用是启动在ConnCreate函数内创建的线程。


客户端连接线程主函数

ConnCreate函数会接收server.c文件内的serverFunc函数作为每个客户端连接的线程主函数。其实对操作系统来说,这还不是那个主函数。真正的主函数是thread_windows.c文件内的threadRun函数。因为调用beginthreadex函数传入的就是这个函数。

        threadP->handle = (HANDLE)_beginthreadex(NULL,
                                                 THREAD_STACK_SIZE,
                                                 threadRun,
                                                 threadP,
                                                 CREATE_SUSPENDED,
                                                 &z);
static uint32_t WINAPI
threadRun(void * const arg) {

    struct abyss_thread * const threadP = arg;

    threadP->func(threadP->userHandle);

    threadP->threadDone(threadP->userHandle);

    return 0;
}
在threadRun函数内它依次调用了func和threadDone两个TThread的函数指针。它们分别是在调用ThreadCreate时传入的connJob和threadDone,都属于conn.c文件。

static void
connJob(void * const userHandle) {
/*----------------------------------------------------------------------------
   This is the root function for a thread that processes a connection
   (performs HTTP transactions).
-----------------------------------------------------------------------------*/
    TConn * const connectionP = userHandle;

    (connectionP->job)(connectionP);

    connectionP->finished = TRUE;
        /* Note that if we are running in a forked process, setting
           connectionP->finished has no effect, because it's just our own
           copy of *connectionP.  In this case, Parent must update his own
           copy based on a SIGCHLD signal that the OS will generate right
           after we exit.
        */

    //ThreadExit(0);
}
connJob实际调用了serverFunc函数(TConn的job函数指针)。serverFunc函数执行完毕后,即threadRun函数的threadP->func执行完毕后,会接着执行threadP->threadDone函数。TThread的threadDone函数指针指向的是conn.c文件内的threadDone函数。TConn的done函数指向的是destroyChannel函数,它在server.c文件内。
static void
connDone(TConn * const connectionP) {

    /* In the forked case, this is designed to run in the parent
       process after the child has terminated.
    */
    connectionP->finished = TRUE;

    if (connectionP->done)
        connectionP->done(connectionP);
}



static TThreadDoneFn threadDone;

static void
threadDone(void * const userHandle) {

    TConn * const connectionP = userHandle;
    
    connDone(connectionP);
}
只要是线程主循环执行完毕肯定会将TConn的finished属性置成TRUE,即线程已执行完毕可以删除。

ConnRead

ConnCreate函数会接收server.c文件内的serverFunc函数作为每个客户端连接的线程主函数。此函数放置在server.c文件内也可以,其实我认为它放置在conn.c文件内可能从概念上说更适合。既然是个线程主函数,那肯定少不了一个循环过程。循环内的一个重要函数就是ConnRead。

bool
ConnRead(TConn *  const connectionP,
         uint32_t const timeout) {
/*----------------------------------------------------------------------------
   Read some stuff on connection *connectionP from the channel.

   Don't wait more than 'timeout' seconds for data to arrive.  Fail if
   nothing arrives within that time.

   'timeout' must be before the end of time.
-----------------------------------------------------------------------------*/
    time_t const deadline = time(NULL) + timeout;

    bool cantGetData;
    bool gotData;

    cantGetData = FALSE;
    gotData = FALSE;
    
    while (!gotData && !cantGetData) {
        int const timeLeft = (int)(deadline - time(NULL));

        if (timeLeft <= 0)
            cantGetData = TRUE;
        else {
            bool const waitForRead  = TRUE;
            bool const waitForWrite = FALSE;
            
            bool readyForRead;
            bool failed;
            
            ChannelWait(connectionP->channelP, waitForRead, waitForWrite,
                        timeLeft * 1000, &readyForRead, NULL, &failed);
            
            if (failed)
                cantGetData = TRUE;
            else {
                uint32_t bytesRead;
                bool readFailed;

                ChannelRead(connectionP->channelP,
                            connectionP->buffer + connectionP->buffersize,
                            bufferSpace(connectionP) - 1,
                            &bytesRead, &readFailed);

                if (readFailed)
                    cantGetData = TRUE;
                else {
                    if (bytesRead > 0) {
                        traceChannelRead(connectionP, bytesRead);
                        connectionP->inbytes += bytesRead;
                        connectionP->buffersize += bytesRead;
                        connectionP->buffer[connectionP->buffersize] = '\0';
                        gotData = TRUE;
                    } else
                        /* Other end has disconnected */
                        cantGetData = TRUE;
                }
            }
        }
    }
    if (gotData)
        return TRUE;
    else
        return FALSE;
}
ConnRead函数的主体仍然是一个循环。循环可以继续执行下去的条件是没超过指定超时时长,以及没发生其他错误。指定一个超时时长的意义在于,如果一个连接长时间没响应,对于服务端来说是种资源消耗,不如将其删除。ChannelWait是个封装函数,实际调用的是windows平台下的wait的特定实现。具体实现在socket_win.c文件内:channelWait。ChannelRead函数与ChannelWait函数相似。获取到的数据被放置在TConn的buffer数组内。

void
ChannelWait(TChannel * const channelP,
            bool       const waitForRead,
            bool       const waitForWrite,
            uint32_t   const timems,
            bool *     const readyToReadP,
            bool *     const readyToWriteP,
            bool *     const failedP) {

    if (ChannelTraceIsActive) {
        if (waitForRead)
            fprintf(stderr, "Waiting %u milliseconds for data from "
                    "channel %p\n", timems, channelP);
        if (waitForWrite)
            fprintf(stderr, "Waiting %u milliseconds for channel %p "
                    "to be writable\n", timems, channelP);
    }
    (*channelP->vtbl.wait)(channelP, waitForRead, waitForWrite, timems,
                           readyToReadP, readyToWriteP, failedP);
}

ConnReadInit

void
ConnReadInit(TConn * const connectionP) {

    if (connectionP->buffersize > connectionP->bufferpos) {
        connectionP->buffersize -= connectionP->bufferpos;
        memmove(connectionP->buffer,
                connectionP->buffer + connectionP->bufferpos,
                connectionP->buffersize);
        connectionP->bufferpos = 0;
    } else
        connectionP->buffersize = connectionP->bufferpos = 0;

    connectionP->buffer[connectionP->buffersize] = '\0';

    connectionP->inbytes = connectionP->outbytes = 0;
}

ConnReadInit函数在每次执行完processDataFromClient函数后执行。processDataFromClient函数功能正如其函数名称所表明的那样:处理客户端发来的数据。ConnReadInit函数可以理解成为下次读操作做些初始化工作。connectionP->bufferpos可以解释为上次处理数据完毕后,还未处理数据的起始地址。

if (connectionP->buffersize > connectionP->bufferpos) {
上述这条判断语句为真,说明缓存区内还有未处理的数据。接下来的处理就是将bufferpos指针指向缓存区头部,并且将未处理的数据挪至缓存区头部。


ConnWrite

bool
ConnWrite(TConn *      const connectionP,
          const void * const buffer,
          uint32_t     const size) {

    bool failed;

    ChannelWrite(connectionP->channelP, buffer, size, &failed);

    traceChannelWrite(connectionP, buffer, size, failed);

    if (!failed)
        connectionP->outbytes += size;

    return !failed;
}
ChannelWrite函数与ChannelRead函数类似,也将调用windows平台下的特定封装。

ConnWriteFromFile

此函数相较于ConnWrite函数,多了一些从文件中读取数据的速率控制。


此文件内包含的都是与TConn结构体相关的函数。换种说法就是客户端连接相关的函数。包括连接到来时线程创建、读数据以及写数据等。

你可能感兴趣的:(FreeSWITCH - mod_xml_rpc源码分析八conn.c)