1. 程式人生 > >Lighttpd1.4.20源代碼分析 筆記 狀態機之錯誤處理和連接關閉

Lighttpd1.4.20源代碼分析 筆記 狀態機之錯誤處理和連接關閉

全部 階段 內存 and ces ons keep ren log

這裏所說的錯誤有兩種:
1.http協議規定的錯誤,如404錯誤。
2.server執行過程中的錯誤。如write錯誤。

對於http協議規定的錯誤,這裏的“錯誤”是針對client的。

lighttpd返回相應的錯誤提示文件之後,相當於順利的完畢了一次請求,僅僅是結果和client想要的不一樣而已。

對於server執行中的錯誤,狀態機進入CON_STATE_ERROR狀態。常見的錯誤原因:client提前斷開連接。

比方你不停的刷新頁面。在你刷新的時候,前一次的連接沒有完畢,但被瀏覽器強行斷開。對於server而言,刷新前後的兩個連接是不相幹的,server在接收後一個連接的時候仍然會繼續處理前一次的連接。而前一次的連接已斷開,這就產生了連接錯誤。

進入CON_STATE_ERROR狀態後。假設前面的請求處理已經得到了結果。也就是http_status不為空。那麽調用plugins_call_handle_request_done告訴插件請求處理結束:

            /* even if the connection was drop we still have to write it to the access log */
            if (con->http_status) {
                plugins_call_handle_request_done(srv, con);
            }

假設使用了ssl,關閉ssl連接:

#ifdef USE_OPENSSL
    if (srv_sock->is_ssl) {
    /* 關閉ssl連接 */
    }
    ERR_clear_error();
#endif

接著:

            switch(con->mode) {
            case DIRECT:
#if 0
                log_error_write(srv, __FILE__, __LINE__, "sd",
                        "emergency exit: direct"
, con->fd); #endif break; default: switch(r = plugins_call_handle_connection_close(srv, con)) { case HANDLER_GO_ON: case HANDLER_FINISHED: break; default: log_error_write(srv, __FILE__, __LINE__, ""); break; } break; } connection_reset(srv, con);

假設連接模式不是DIRECT,調用plugins_call_handle_connection_close告訴插件連接已經關閉。

假設設置了keep_alive。此時可能是server首先關閉連接的。調用shutdown關閉連接的讀和寫。假設關閉沒有出錯,狀態機進入CON_STATE_CLOSE狀態。

假設沒有設置keep_alive或者shutdown調用失敗,那麽直接關閉連接。結束狀態機的執行。

            /* close the connection */
            if ((con->keep_alive == 1) &&
                (0 == shutdown(con->fd, SHUT_WR))) {
                con->close_timeout_ts = srv->cur_ts;
                connection_set_state(srv, con, CON_STATE_CLOSE);

                if (srv->srvconf.log_state_handling) {
                    log_error_write(srv, __FILE__, __LINE__, "sd",
                            "shutdown for fd", con->fd);
                }
            } else {
                connection_close(srv, con);
            }

            con->keep_alive = 0;

            srv->con_closed++;

註意到。這裏server主動關閉連接的時候用的是shutdown而不是close:

1.close使用引用計數,在計數為0時才關閉套接字;shutdown無論引用計數,直接激發TCP的正常連接終止序列。

2.close終止讀和寫兩個方向的數據傳送。shutdown能夠指定僅僅關閉連接的讀,或僅僅關閉連接的寫。或兩者均關閉。

以上lighttpd是關閉了連接的寫這一半,對於TCP套接字來說。這叫做半關閉:當前留在套接字發送緩沖區的數據仍然能夠發送。可是進程不能再對其調用寫函數(因為讀端沒有關閉,所以server仍然能夠讀數據),當數據發送完畢之後,TCP連接終止。

另外。註意一下:con->close_timeout_ts = srv->cur_ts;將close_timeout_ts的值設置為當前時間,在以下會用到。

在CON_STATE_CLOSE階段:

        case CON_STATE_CLOSE:
            if (srv->srvconf.log_state_handling) {
                log_error_write(srv, __FILE__, __LINE__, "sds",
                        "state for fd", con->fd, connection_get_state(con->state));
            }

            if (con->keep_alive) {
                if (ioctl(con->fd, FIONREAD, &b)) {
                    log_error_write(srv, __FILE__, __LINE__, "ss",
                            "ioctl() failed", strerror(errno));
                }
                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;
                }
            } else {
                con->close_timeout_ts = 0;
            }

            if (srv->cur_ts - con->close_timeout_ts > 1) {
                connection_close(srv, con);

                if (srv->srvconf.log_state_handling) {
                    log_error_write(srv, __FILE__, __LINE__, "sd",
                            "connection closed for fd", con->fd);
                }
            }

            break;

假設緩沖區中還有數據,server會把數據讀出來(然後丟棄),以騰出內存空間。

假設沒有數據可讀,那麽設置close_timeout_ts=0,關閉連接。

假設有數據可讀,讀取數據之後,連接依舊處在CON_STATE_CLOSE狀態中(在出了CON_STATE_ERROR後。進入CON_STATE_CLOSE,這段時間cur_ts是沒有改變的。假設有數據可讀,此時const_time_ts是等於cur_ts的,因此連接並未被關閉),連接相應的fd被增加到fdevent系統中監聽讀事件。

假設緩沖區中還有數據,那麽在connection_handle_fdevent 函數中,也有上面這段代碼,再次執行之,直到數據讀完。

隨著close_timeout_ts被設置為0,在下次joblist的調度中,狀態機將會關閉連接,清理全部資源。

至此,連接正式關閉。

關於狀態機的簡單解析就到此為止~

Lighttpd1.4.20源代碼分析 筆記 狀態機之錯誤處理和連接關閉