下面分析過程中函數先後次序,採用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函數決定是否要保留當前連接。