lighttpd1.4.18代碼分析(六)--處理連接fd的流程
現(xiàn)在開始講解lighttpd如何處理連接fd.
在第四節(jié),已經(jīng)講解了如何處理服務(wù)器負(fù)責(zé)監(jiān)聽連接的fd,處理連接fd的流程在前半部分與監(jiān)聽fd大體相同:在通過監(jiān)聽fd接收一個新的連接之后,服務(wù)器將這個fd加入到事件處理器中, 設(shè)置它所感興趣的IO事件類型, 當(dāng)IO狀態(tài)發(fā)生變化時, 事件處理器獲取被觸發(fā)的fd,回調(diào)函數(shù),事件等,然后通過已經(jīng)注冊的回調(diào)函數(shù)進(jìn)行處理.
這里的區(qū)別就在于回調(diào)函數(shù)的不同上.對于監(jiān)聽fd而言, 該回調(diào)函數(shù)是network.c文件中的network_server_handle_fdevent函數(shù),這個在第四節(jié)中已經(jīng)做了分析;與之對應(yīng)的,連接fd的回調(diào)函數(shù)是connections.c文件中的connection_handle_fdevent函數(shù).
為什么需要這些狀態(tài)?因為lighttpd中采用了所謂"狀態(tài)機(jī)"去處理連接,而這些狀態(tài)就是狀態(tài)機(jī)中的各種不同狀態(tài).
在lighttpd的官方文檔中, 對其使用的狀態(tài)機(jī)有一篇文檔,在這里:
http://redmine.lighttpd.net/wiki/lighttpd/Docs:InternalHTTPStates
我覺得里面的這幅圖非常的直觀,學(xué)習(xí)過編譯原理的人一看就可以知道這是狀態(tài)機(jī)的轉(zhuǎn)換圖:

現(xiàn)在回到本章的主題中, lighttpd如何處理連接fd.
前面給出的處理連接fd的回調(diào)函數(shù),最開始地方有一段代碼:
joblist_append(srv, con);
這個函數(shù)將一個連接connection結(jié)構(gòu)體放入到j(luò)oblist中, 后面的部分根據(jù)不同的情況設(shè)置connection中的state字段,調(diào)用的是connection_set_state函數(shù).
現(xiàn)在回到server.c函數(shù)中, 第四節(jié)中已經(jīng)結(jié)合處理監(jiān)聽fd的流程講解了這段函數(shù):
在server.c文件中, 緊跟著這段代碼的是:
connection_state_machine函數(shù)是一個非常重要的函數(shù), 它就是處理連接fd的狀態(tài)機(jī), 在后面將詳細(xì)分析這個函數(shù).
現(xiàn)在回顧一下lighttpd處理連接fd的大體框架:前面的流程與處理監(jiān)聽fd大體相同,在與處理連接fd相關(guān)的回調(diào)函數(shù)中, 首先將需要處理的fd相關(guān)的connection加入到j(luò)oblist中, 設(shè)置它的state, 后面再輪詢joblist, 進(jìn)入狀態(tài)機(jī)進(jìn)行處理.
這個過程可能值得商榷, 比如為什么在回調(diào)函數(shù)中需要將一個connection指針加入到j(luò)oblist中, 后面再一個循環(huán)輪詢joblist中的connection,這樣不是顯得效率低下嗎?我們需要注意的是,前面提到的IO事件狀態(tài)和connection中的成員state是不同的!第一個輪詢過程(IO事件處理器的輪詢)是根據(jù)哪些fd的IO發(fā)生了變化被觸發(fā)而去調(diào)用回調(diào)函數(shù), 而后面的循環(huán)(joblist的輪詢)中的connection則不一定都是IO發(fā)生變化的!
打一個比方, 一個連接到來, 此時我們把它放入到事件處理器中, 當(dāng)它可讀時被觸發(fā), 也就是在第一個輪詢中被觸發(fā);如果在處理的時候出了問題, 不能繼續(xù), 此時我們只需要保存它當(dāng)前的state字段, 在下一次操作中, 由于它的IO沒有發(fā)生變化, 那么將不會在第一個輪詢也就是IO事件處理器中被處理, 而只會在輪詢joblist時被處理, 只需要它的state字段是正確的, 放到狀態(tài)機(jī)處理函數(shù)中就可以繼續(xù)下去.
在第四節(jié),已經(jīng)講解了如何處理服務(wù)器負(fù)責(zé)監(jiān)聽連接的fd,處理連接fd的流程在前半部分與監(jiān)聽fd大體相同:在通過監(jiān)聽fd接收一個新的連接之后,服務(wù)器將這個fd加入到事件處理器中, 設(shè)置它所感興趣的IO事件類型, 當(dāng)IO狀態(tài)發(fā)生變化時, 事件處理器獲取被觸發(fā)的fd,回調(diào)函數(shù),事件等,然后通過已經(jīng)注冊的回調(diào)函數(shù)進(jìn)行處理.
這里的區(qū)別就在于回調(diào)函數(shù)的不同上.對于監(jiān)聽fd而言, 該回調(diào)函數(shù)是network.c文件中的network_server_handle_fdevent函數(shù),這個在第四節(jié)中已經(jīng)做了分析;與之對應(yīng)的,連接fd的回調(diào)函數(shù)是connections.c文件中的connection_handle_fdevent函數(shù).
// 這個函數(shù)是處理接受鏈接的函數(shù), 與network_server_handle_fdevent對應(yīng)
handler_t connection_handle_fdevent(void *s, void *context, int revents) {
server *srv = (server *)s;
connection *con = context;
// 添加到server的joblist中
joblist_append(srv, con);
// 可讀
if (revents & FDEVENT_IN) {
con->is_readable = 1;
}
// 可寫
if (revents & FDEVENT_OUT) {
con->is_writable = 1;
/* we don't need the event twice */
}
// 既不可讀也不可寫, 可能是出錯了
if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
/* looks like an error */
/* FIXME: revents = 0x19 still means that we should read from the queue */
if (revents & FDEVENT_HUP) {
if (con->state == CON_STATE_CLOSE) {
con->close_timeout_ts = 0;
} else {
/* sigio reports the wrong event here
*
* there was no HUP at all
*/
#ifdef USE_LINUX_SIGIO
if (srv->ev->in_sigio == 1) {
log_error_write(srv, __FILE__, __LINE__, "sd",
"connection closed: poll() -> HUP", con->fd);
} else {
connection_set_state(srv, con, CON_STATE_ERROR);
}
#else
connection_set_state(srv, con, CON_STATE_ERROR);
#endif
}
} else if (revents & FDEVENT_ERR) {
#ifndef USE_LINUX_SIGIO
log_error_write(srv, __FILE__, __LINE__, "sd",
"connection closed: poll() -> ERR", con->fd);
#endif
connection_set_state(srv, con, CON_STATE_ERROR);
} else {
log_error_write(srv, __FILE__, __LINE__, "sd",
"connection closed: poll() -> ???", revents);
}
}
// 如果連接的狀態(tài)是READ, 那么處理read狀態(tài)
if (con->state == CON_STATE_READ ||
con->state == CON_STATE_READ_POST) {
connection_handle_read_state(srv, con);
}
// 如果連接狀態(tài)是WRITE并卻寫緩沖區(qū)隊列中不為空 并且該連接是可寫的, 就去處理寫狀態(tài)
if (con->state == CON_STATE_WRITE &&
!chunkqueue_is_empty(con->write_queue) &&
con->is_writable) {
if (-1 == connection_handle_write(srv, con)) {
connection_set_state(srv, con, CON_STATE_ERROR);
log_error_write(srv, __FILE__, __LINE__, "ds",
con->fd,
"handle write failed.");
} else if (con->state == CON_STATE_WRITE) {
con->write_request_ts = srv->cur_ts;
}
}
// 如果連接的狀態(tài)是關(guān)閉
if (con->state == CON_STATE_CLOSE) {
/* flush the read buffers */
int b;
// 檢查讀緩沖區(qū)中是否還有數(shù)據(jù)
if (ioctl(con->fd, FIONREAD, &b)) {
log_error_write(srv, __FILE__, __LINE__, "ss",
"ioctl() failed", strerror(errno));
}
// 如果還有數(shù)據(jù), 打印錯誤log, 并且讀入數(shù)據(jù)
if (b > 0) {
char buf[1024];
log_error_write(srv, __FILE__, __LINE__, "sdd",
"CLOSE-read()", con->fd, b);
/* */
read(con->fd, buf, sizeof(buf));
} else {
/* nothing to read */
con->close_timeout_ts = 0;
}
}
return HANDLER_FINISHED;
}
在結(jié)構(gòu)體connection,也就是保存連接相關(guān)數(shù)據(jù)的結(jié)構(gòu)體中, 有一個叫state的成員, 顧名思義, 這個成員保存的是一個連接的狀態(tài),這里所說的狀態(tài)與前面提到的IO狀態(tài)是不同, IO狀態(tài)是用于表示一個fd可讀/可寫/出錯等, 而這個狀態(tài)更多的是與協(xié)議相關(guān)的部分.它是一個枚舉類型:handler_t connection_handle_fdevent(void *s, void *context, int revents) {
server *srv = (server *)s;
connection *con = context;
// 添加到server的joblist中
joblist_append(srv, con);
// 可讀
if (revents & FDEVENT_IN) {
con->is_readable = 1;
}
// 可寫
if (revents & FDEVENT_OUT) {
con->is_writable = 1;
/* we don't need the event twice */
}
// 既不可讀也不可寫, 可能是出錯了
if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
/* looks like an error */
/* FIXME: revents = 0x19 still means that we should read from the queue */
if (revents & FDEVENT_HUP) {
if (con->state == CON_STATE_CLOSE) {
con->close_timeout_ts = 0;
} else {
/* sigio reports the wrong event here
*
* there was no HUP at all
*/
#ifdef USE_LINUX_SIGIO
if (srv->ev->in_sigio == 1) {
log_error_write(srv, __FILE__, __LINE__, "sd",
"connection closed: poll() -> HUP", con->fd);
} else {
connection_set_state(srv, con, CON_STATE_ERROR);
}
#else
connection_set_state(srv, con, CON_STATE_ERROR);
#endif
}
} else if (revents & FDEVENT_ERR) {
#ifndef USE_LINUX_SIGIO
log_error_write(srv, __FILE__, __LINE__, "sd",
"connection closed: poll() -> ERR", con->fd);
#endif
connection_set_state(srv, con, CON_STATE_ERROR);
} else {
log_error_write(srv, __FILE__, __LINE__, "sd",
"connection closed: poll() -> ???", revents);
}
}
// 如果連接的狀態(tài)是READ, 那么處理read狀態(tài)
if (con->state == CON_STATE_READ ||
con->state == CON_STATE_READ_POST) {
connection_handle_read_state(srv, con);
}
// 如果連接狀態(tài)是WRITE并卻寫緩沖區(qū)隊列中不為空 并且該連接是可寫的, 就去處理寫狀態(tài)
if (con->state == CON_STATE_WRITE &&
!chunkqueue_is_empty(con->write_queue) &&
con->is_writable) {
if (-1 == connection_handle_write(srv, con)) {
connection_set_state(srv, con, CON_STATE_ERROR);
log_error_write(srv, __FILE__, __LINE__, "ds",
con->fd,
"handle write failed.");
} else if (con->state == CON_STATE_WRITE) {
con->write_request_ts = srv->cur_ts;
}
}
// 如果連接的狀態(tài)是關(guān)閉
if (con->state == CON_STATE_CLOSE) {
/* flush the read buffers */
int b;
// 檢查讀緩沖區(qū)中是否還有數(shù)據(jù)
if (ioctl(con->fd, FIONREAD, &b)) {
log_error_write(srv, __FILE__, __LINE__, "ss",
"ioctl() failed", strerror(errno));
}
// 如果還有數(shù)據(jù), 打印錯誤log, 并且讀入數(shù)據(jù)
if (b > 0) {
char buf[1024];
log_error_write(srv, __FILE__, __LINE__, "sdd",
"CLOSE-read()", con->fd, b);
/* */
read(con->fd, buf, sizeof(buf));
} else {
/* nothing to read */
con->close_timeout_ts = 0;
}
}
return HANDLER_FINISHED;
}
/* the order of the items should be the same as they are processed
* read before write as we use this later */
typedef enum {
CON_STATE_CONNECT, // 連接
CON_STATE_REQUEST_START, // 開始獲取請求
CON_STATE_READ, // 處理讀
CON_STATE_REQUEST_END, // 請求結(jié)束
CON_STATE_READ_POST, // 處理讀,但是是POST過來的數(shù)據(jù)
CON_STATE_HANDLE_REQUEST, // 處理請求
CON_STATE_RESPONSE_START, // 開始回復(fù)
CON_STATE_WRITE, // 處理寫
CON_STATE_RESPONSE_END, // 回復(fù)結(jié)束
CON_STATE_ERROR, // 出錯
CON_STATE_CLOSE // 連接關(guān)閉
} connection_state_t;
* read before write as we use this later */
typedef enum {
CON_STATE_CONNECT, // 連接
CON_STATE_REQUEST_START, // 開始獲取請求
CON_STATE_READ, // 處理讀
CON_STATE_REQUEST_END, // 請求結(jié)束
CON_STATE_READ_POST, // 處理讀,但是是POST過來的數(shù)據(jù)
CON_STATE_HANDLE_REQUEST, // 處理請求
CON_STATE_RESPONSE_START, // 開始回復(fù)
CON_STATE_WRITE, // 處理寫
CON_STATE_RESPONSE_END, // 回復(fù)結(jié)束
CON_STATE_ERROR, // 出錯
CON_STATE_CLOSE // 連接關(guān)閉
} connection_state_t;
為什么需要這些狀態(tài)?因為lighttpd中采用了所謂"狀態(tài)機(jī)"去處理連接,而這些狀態(tài)就是狀態(tài)機(jī)中的各種不同狀態(tài).
在lighttpd的官方文檔中, 對其使用的狀態(tài)機(jī)有一篇文檔,在這里:
http://redmine.lighttpd.net/wiki/lighttpd/Docs:InternalHTTPStates
我覺得里面的這幅圖非常的直觀,學(xué)習(xí)過編譯原理的人一看就可以知道這是狀態(tài)機(jī)的轉(zhuǎn)換圖:

現(xiàn)在回到本章的主題中, lighttpd如何處理連接fd.
前面給出的處理連接fd的回調(diào)函數(shù),最開始地方有一段代碼:
joblist_append(srv, con);
這個函數(shù)將一個連接connection結(jié)構(gòu)體放入到j(luò)oblist中, 后面的部分根據(jù)不同的情況設(shè)置connection中的state字段,調(diào)用的是connection_set_state函數(shù).
現(xiàn)在回到server.c函數(shù)中, 第四節(jié)中已經(jīng)結(jié)合處理監(jiān)聽fd的流程講解了這段函數(shù):
// 輪詢FD
if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
/* n is the number of events */
int revents;
int fd_ndx;
fd_ndx = -1;
do {
fdevent_handler handler;
void *context;
handler_t r;
// 獲得處理這些事件的函數(shù)指針 fd等
// 獲得下一個fd在fdarray中的索引
fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx);
// 獲得這個fd要處理的事件類型
revents = fdevent_event_get_revent (srv->ev, fd_ndx);
// 獲取fd
fd = fdevent_event_get_fd (srv->ev, fd_ndx);
// 獲取回調(diào)函數(shù)
handler = fdevent_get_handler(srv->ev, fd);
// 獲取處理相關(guān)的context(對server是server_socket指針, 對client是connection指針)
context = fdevent_get_context(srv->ev, fd);
/* connection_handle_fdevent needs a joblist_append */
// 進(jìn)行處理
switch (r = (*handler)(srv, context, revents)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_WAIT_FOR_FD:
break;
case HANDLER_ERROR:
/* should never happen */
SEGFAULT();
break;
default:
log_error_write(srv, __FILE__, __LINE__, "d", r);
break;
}
} while (--n > 0);
} else if (n < 0 && errno != EINTR) {
log_error_write(srv, __FILE__, __LINE__, "ss",
"fdevent_poll failed:",
strerror(errno));
}
if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
/* n is the number of events */
int revents;
int fd_ndx;
fd_ndx = -1;
do {
fdevent_handler handler;
void *context;
handler_t r;
// 獲得處理這些事件的函數(shù)指針 fd等
// 獲得下一個fd在fdarray中的索引
fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx);
// 獲得這個fd要處理的事件類型
revents = fdevent_event_get_revent (srv->ev, fd_ndx);
// 獲取fd
fd = fdevent_event_get_fd (srv->ev, fd_ndx);
// 獲取回調(diào)函數(shù)
handler = fdevent_get_handler(srv->ev, fd);
// 獲取處理相關(guān)的context(對server是server_socket指針, 對client是connection指針)
context = fdevent_get_context(srv->ev, fd);
/* connection_handle_fdevent needs a joblist_append */
// 進(jìn)行處理
switch (r = (*handler)(srv, context, revents)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_WAIT_FOR_FD:
break;
case HANDLER_ERROR:
/* should never happen */
SEGFAULT();
break;
default:
log_error_write(srv, __FILE__, __LINE__, "d", r);
break;
}
} while (--n > 0);
} else if (n < 0 && errno != EINTR) {
log_error_write(srv, __FILE__, __LINE__, "ss",
"fdevent_poll failed:",
strerror(errno));
}
在server.c文件中, 緊跟著這段代碼的是:
// 處理joblist中的連接
for (ndx = 0; ndx < srv->joblist->used; ndx++) {
connection *con = srv->joblist->ptr[ndx];
handler_t r;
connection_state_machine(srv, con);
switch(r = plugins_call_handle_joblist(srv, con)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
break;
default:
log_error_write(srv, __FILE__, __LINE__, "d", r);
break;
}
con->in_joblist = 0;
}
簡單的說, 這段代碼是一個循環(huán), 從joblist中依次取出已經(jīng)放在這里的connection指針, 再調(diào)用connection_state_machine函數(shù)進(jìn)行處理.for (ndx = 0; ndx < srv->joblist->used; ndx++) {
connection *con = srv->joblist->ptr[ndx];
handler_t r;
connection_state_machine(srv, con);
switch(r = plugins_call_handle_joblist(srv, con)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
break;
default:
log_error_write(srv, __FILE__, __LINE__, "d", r);
break;
}
con->in_joblist = 0;
}
connection_state_machine函數(shù)是一個非常重要的函數(shù), 它就是處理連接fd的狀態(tài)機(jī), 在后面將詳細(xì)分析這個函數(shù).
現(xiàn)在回顧一下lighttpd處理連接fd的大體框架:前面的流程與處理監(jiān)聽fd大體相同,在與處理連接fd相關(guān)的回調(diào)函數(shù)中, 首先將需要處理的fd相關(guān)的connection加入到j(luò)oblist中, 設(shè)置它的state, 后面再輪詢joblist, 進(jìn)入狀態(tài)機(jī)進(jìn)行處理.
這個過程可能值得商榷, 比如為什么在回調(diào)函數(shù)中需要將一個connection指針加入到j(luò)oblist中, 后面再一個循環(huán)輪詢joblist中的connection,這樣不是顯得效率低下嗎?我們需要注意的是,前面提到的IO事件狀態(tài)和connection中的成員state是不同的!第一個輪詢過程(IO事件處理器的輪詢)是根據(jù)哪些fd的IO發(fā)生了變化被觸發(fā)而去調(diào)用回調(diào)函數(shù), 而后面的循環(huán)(joblist的輪詢)中的connection則不一定都是IO發(fā)生變化的!
打一個比方, 一個連接到來, 此時我們把它放入到事件處理器中, 當(dāng)它可讀時被觸發(fā), 也就是在第一個輪詢中被觸發(fā);如果在處理的時候出了問題, 不能繼續(xù), 此時我們只需要保存它當(dāng)前的state字段, 在下一次操作中, 由于它的IO沒有發(fā)生變化, 那么將不會在第一個輪詢也就是IO事件處理器中被處理, 而只會在輪詢joblist時被處理, 只需要它的state字段是正確的, 放到狀態(tài)機(jī)處理函數(shù)中就可以繼續(xù)下去.
posted on 2008-09-19 10:46 那誰 閱讀(3711) 評論(1) 編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程 、服務(wù)器設(shè)計 、Linux/Unix 、lighttpd