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实现。
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变量,并初始化各项属性,最后再创建一个与此相对应的线程。
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,即线程已执行完毕可以删除。
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); }
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指针指向缓存区头部,并且将未处理的数据挪至缓存区头部。
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平台下的特定封装。
此文件内包含的都是与TConn结构体相关的函数。换种说法就是客户端连接相关的函数。包括连接到来时线程创建、读数据以及写数据等。