下面分析过程中函数先后次序,采用mod_xml_rpc.c文件内SWITCH_MODULE_RUNTIME_FUNCTION(mod_xml_rpc_runtime)函数里abyss库函数的出现顺序。
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;
在这个函数内,果然看到了为_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属性。
客户代码调用完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静态函数。
这个函数最主要的输入参数是处理函数。首先,依据这个传入的函数创建一个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; }
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文件内。如果想知道上述提到的这些内容,可以直接转至下一篇文档。
如果转去看过了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函数内代码还无法完全解释上述疑问。
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函数决定是否要保留当前连接。