|
今天繼續(xù)沿著狀態(tài)轉(zhuǎn)換序列圖講解狀態(tài)機,這次到了CON_STATE_READ狀態(tài), 首先看看connection_state_machine函數(shù)部分的代碼:
// 讀 case CON_STATE_READ_POST: case CON_STATE_READ: if (srv->srvconf.log_state_handling) { log_error_write(srv, __FILE__, __LINE__, "sds", "state for fd", con->fd, connection_get_state(con->state)); }
connection_handle_read_state(srv, con); break;
可以看到, CON_STATE_READ_POST狀態(tài)和CON_STATE_READ狀態(tài)調(diào)用的同一段代碼,不過我們今天講解的CON_STATE_READ狀態(tài),CON_STATE_READ_POST狀態(tài)在后面講解.
上面的代碼調(diào)用了connection_handle_read_state函數(shù),進入這個函數(shù)內(nèi)部,分為幾個部分進行分析:
if (con->is_readable) { con->read_idle_ts = srv->cur_ts;
// -1:出錯 -2:對方關(guān)閉連接 0:成? switch(connection_handle_read(srv, con)) { case -1: return -1; case -2: is_closed = 1; break; default: break; } }
這里調(diào)用函數(shù)connection_handle_read, 來看看這個函數(shù)的實現(xiàn):
// -1:出錯 -2:對方關(guān)閉連接 0:成功 static int connection_handle_read(server *srv, connection *con) { int len; buffer *b; int toread;
if (con->conf.is_ssl) { return connection_handle_read_ssl(srv, con); }
#if defined(__WIN32) b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, 4 * 1024); len = recv(con->fd, b->ptr, b->size - 1, 0); #else // 獲取有多少數(shù)據(jù)可讀 if (ioctl(con->fd, FIONREAD, &toread)) { log_error_write(srv, __FILE__, __LINE__, "sd", "unexpected end-of-file:", con->fd); return -1; } // 根據(jù)數(shù)據(jù)量準(zhǔn)備緩沖區(qū) b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, toread + 1); // 讀數(shù)據(jù) len = read(con->fd, b->ptr, b->size - 1); #endif
if (len < 0) { con->is_readable = 0;
// Non-blocking I/O has been selected using O_NONBLOCK and no data // was immediately available for reading. if (errno == EAGAIN) return 0; if (errno == EINTR) { /* we have been interrupted before we could read */ con->is_readable = 1; return 0; }
if (errno != ECONNRESET) { /* expected for keep-alive */ log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno); }
connection_set_state(srv, con, CON_STATE_ERROR);
return -1; } else if (len == 0) { // 當(dāng)讀入數(shù)據(jù) = 0時 表示對端關(guān)閉了連接 con->is_readable = 0; /* the other end close the connection -> KEEP-ALIVE */
/* pipelining */
return -2; } else if ((size_t)len < b->size - 1) { /* we got less then expected, wait for the next fd-event */
con->is_readable = 0; }
// 記錄讀入的數(shù)據(jù)量 未使用的數(shù)據(jù)第一個字節(jié)為0 b->used = len; b->ptr[b->used++] = '\0';
con->bytes_read += len; #if 0 dump_packet(b->ptr, len); #endif
return 0; }
簡單的說, 該函數(shù)首先調(diào)用ioctl獲取fd對應(yīng)的緩沖區(qū)中有多少可讀的數(shù)據(jù), 然后調(diào)用chunkqueue_get_append_buffer和buffer_prepare_copy函數(shù)準(zhǔn)備好所需的緩沖區(qū), 準(zhǔn)備好緩沖區(qū)之后, 調(diào)用read函數(shù)讀取緩沖區(qū)中的數(shù)據(jù).對read函數(shù)的調(diào)用結(jié)果進行區(qū)分, 小于0表示出錯, 返回-1;等于0表示關(guān)閉了連接, 返回-2;假如讀取的數(shù)據(jù)長度比預(yù)期的小, 那么就等待下一次繼續(xù)讀數(shù)據(jù), 最后將已經(jīng)讀入的數(shù)據(jù)緩沖區(qū)最后一個字節(jié)置'\0',返回0.
繼續(xù)回到函數(shù)connection_handle_read_state中, 看接下來的代碼:
// 這一段循環(huán)代碼用于更新read chunk隊列,沒有使用的chunk都歸入未使用chunk鏈中 /* the last chunk might be empty */ for (c = cq->first; c;) { if (cq->first == c && c->mem->used == 0) { // 如果第一個chunk是空的并且沒有使用過 /* the first node is empty */ /* and it is empty, move it to unused */
// 則chunk隊列的第一個chunk為下一個chunk cq->first = c->next; // 第一個chunk為NULL, 那么最后一個chunk為NULL if (cq->first == NULL) cq->last = NULL;
// 更新chunk隊列中對于未使用chunk的記錄 c->next = cq->unused; cq->unused = c; cq->unused_chunks++;
// 重新指向第一個chunk c = cq->first; } else if (c->next && c->next->mem->used == 0) { chunk *fc; // 如果下一個chunk存在而且未使用過 /* next node is the last one */ /* and it is empty, move it to unused */
// 將這個chunk從隊列中分離出去, 同時fc指向這個未使用的chunk fc = c->next; c->next = fc->next;
// 將這個未使用的chunk(fc所指)保存到未使用chunk鏈中 fc->next = cq->unused; cq->unused = fc; cq->unused_chunks++;
/* the last node was empty */ // 如果c的下一個chunk是空的, 那么chunk隊列的最后一個chunk就是c了 if (c->next == NULL) { cq->last = c; }
// 繼續(xù)往下走 c = c->next; } else { // 繼續(xù)往下走 c = c->next; } }
每個connection結(jié)構(gòu)體中, 有一個read_queue成員, 該成員是chunkqueue類型的, 一個connection讀入的數(shù)據(jù)都會保存在這個成員中, 由于一直沒有詳細介紹chunkqueue結(jié)構(gòu)體及其使用, 這里不對上面的過程進行詳細的分析, 只需要知道chunkqueue結(jié)構(gòu)體內(nèi)部使用的是鏈表保存數(shù)據(jù), 上面這段代碼遍歷這個鏈表, 將未使用的部分抽取下來放到未使用chunkqueue中.
繼續(xù)看下面的代碼, 下面的代碼根據(jù)狀態(tài)是CON_STATE_READ還是CON_STATE_READ_POST進行了區(qū)分, 同樣的,目前僅關(guān)注CON_STATE_READ狀態(tài)部分:
case CON_STATE_READ: // 如果是可讀狀態(tài) /* if there is a \r\n\r\n in the chunkqueue * * scan the chunk-queue twice * 1. to find the \r\n\r\n * 2. to copy the header-packet * */
last_chunk = NULL; last_offset = 0;
// 遍歷read chunk隊列 for (c = cq->first; !last_chunk && c; c = c->next) { buffer b; size_t i;
b.ptr = c->mem->ptr + c->offset; b.used = c->mem->used - c->offset;
// 遍歷當(dāng)前chunk中的每一個字符 for (i = 0; !last_chunk && i < b.used; i++) { char ch = b.ptr[i]; size_t have_chars = 0;
// 判斷當(dāng)前字符 switch (ch) { case '\r': // 如果當(dāng)前字符是'\r' /* we have to do a 4 char lookup */ // 該chunk還剩余多少個字符 have_chars = b.used - i - 1; if (have_chars >= 4) { // 如果當(dāng)前剩余字符大于等于4, 判斷緊跟著的4個字符是不是"\r\n\r\n", 如果是就退出循環(huán) /* all chars are in this buffer */
if (0 == strncmp(b.ptr + i, "\r\n\r\n", 4)) { /* found */ last_chunk = c; last_offset = i + 4;
break; } } else { // 否則就去查看下一個chunk, 看看是不是和這個chunk一起形成了"\r\n\r\n" chunk *lookahead_chunk = c->next; size_t missing_chars; /* looks like the following chars are not in the same chunk */
missing_chars = 4 - have_chars;
if (lookahead_chunk && lookahead_chunk->type == MEM_CHUNK) { /* is the chunk long enough to contain the other chars ? */
if (lookahead_chunk->mem->used > missing_chars) { if (0 == strncmp(b.ptr + i, "\r\n\r\n", have_chars) && 0 == strncmp(lookahead_chunk->mem->ptr, "\r\n\r\n" + have_chars, missing_chars)) {
last_chunk = lookahead_chunk; last_offset = missing_chars;
break; } } else { /* a splited \r \n */ break; } } }
break; } } }
這段代碼用于在讀入數(shù)據(jù)中查找"\r\n\r\n",熟悉http協(xié)議的人知道, 這代表著一個http請求的結(jié)束,也就是說, 上面的代碼用于判斷是否已經(jīng)接受了一個完整的http請求.但是有一個細節(jié)部分需要注意, 前面說過chunkqueue內(nèi)部是使用一個鏈表來存放數(shù)據(jù),比方說這個鏈表中有兩個節(jié)點, 一個節(jié)點存放一字節(jié)的數(shù)據(jù), 一個節(jié)點存放了十字節(jié)的數(shù)據(jù),這時候可能會出現(xiàn)這樣的情況:假如在一個節(jié)點存放的數(shù)據(jù)中找到了字符'\r',而該節(jié)點剩下的數(shù)據(jù)不足以存放"\r\n\r\n"字符串剩余的字符, 也就是說, 不足4個字節(jié), 那么查找"\r\n\r\n"的過程就要延續(xù)到下一個節(jié)點繼續(xù)進行查找.比如在一個節(jié)點中最后部分找到了"\r", 那么就要在下一個節(jié)點的數(shù)據(jù)起始位置中查找"\n\r\n".
繼續(xù)看下面的代碼:
/* found */ // 讀取到了請求的結(jié)尾, 現(xiàn)在將請求字符串放到request字段中 if (last_chunk) { buffer_reset(con->request.request);
for (c = cq->first; c; c = c->next) { buffer b;
b.ptr = c->mem->ptr + c->offset; b.used = c->mem->used - c->offset;
if (c == last_chunk) { b.used = last_offset + 1; }
buffer_append_string_buffer(con->request.request, &b);
if (c == last_chunk) { c->offset += last_offset;
break; } else { /* the whole packet was copied */ c->offset = c->mem->used - 1; } }
// 設(shè)置狀態(tài)為讀取請求結(jié)束 connection_set_state(srv, con, CON_STATE_REQUEST_END); } else if (chunkqueue_length(cq) > 64 * 1024) { // 讀入的數(shù)據(jù)太多, 出錯 log_error_write(srv, __FILE__, __LINE__, "s", "oversized request-header -> sending Status 414");
con->http_status = 414; /* Request-URI too large */ con->keep_alive = 0; connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); } break;
如果前面查找到了"\r\n\r\n", 那么函數(shù)就進入這個部分.這部分代碼做的事情就是復(fù)制http請求頭到connection結(jié)構(gòu)體中request成員中.需要注意的是如果沒有查找到"\r\n\r\n",并且緩沖區(qū)數(shù)據(jù)長度大于64*1024, 也就是64K字節(jié), 那么就返回414錯誤, 也就是說, 對于lighttpd而言, 一般的http請求不能超過64K字節(jié).
這個過程就分析到這里,簡單的總結(jié)一下:首先從緩沖區(qū)中讀取數(shù)據(jù), 然后查找"\r\n\r\n"字符串, 判斷是否已經(jīng)讀取了完整的http請求, 如果是的話就復(fù)制下來, 最后進入CON_STATE_REQUEST_END狀態(tài), 這是下一節(jié)分析的內(nèi)容.
|