<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ### 一.概述 Lighttpd采用多進程網絡服務模型。 進程分兩種:監控進程watcher 和 工作進程 workers。 監控進程:fork工作進程并監視工作進程的數目,一旦有工作進程退出,監控進程立即fork新的工作進程。 工作進程:接收客戶端請求并做出服務響應。 一般情況下,存在一個監控進程和多個工作進程。 max-worker值默認為0時,沒有監控進程,只有一個工作進程。 關于初始化:Lighttpd很多地方內存申請都是采用calloc,malloc()和calloc()的主要區別是前者不能初始化所分配的內存空間,而后者能。 主流程入口文件:server.c ### 二.Lighttpd進程守護化 main函數中的函數daemonize調用使得Lighttpd進程轉換為守護進程,從而脫離控制終端,在后臺提供服務,避免了執行過程中的信息在終端上顯示,也避免了服務被終端信息打斷。 (啟動時假如選項D可不守護化) 下面我們來分析下: ~~~ #ifdef HAVE_FORK static void daemonize(void) { #ifdef SIGTTOU /* 下面用于屏蔽一些有關控制終端操作的信號,防止守護進程沒有正常運作之前控制終端受到干擾退出或掛起 */ signal(SIGTTOU, SIG_IGN); //忽略后臺進程寫控制終端信號 #endif #ifdef SIGTTIN signal(SIGTTIN, SIG_IGN); //忽略后臺進程讀控制終端信號 #endif #ifdef SIGTSTP signal(SIGTSTP, SIG_IGN); //忽略終端掛起 #endif /* 下面開始從普通進程轉換為守護進程 * 目標1:后臺運行。 * 做法:脫離控制終端->調用fork之后終止父進程,子進程被init收養,此步達到后臺運行的目標。 */ if (0 != fork()) exit(0); /*,目標2:脫離控制終端,登陸會話和進程組。 * 做法:使用setsid創建新會話,成為新會話的首進程,則與原來的 * 登陸會話和進程組自動脫離,從而脫離控制終端。 * (上一步的fork保證了子進程不可能是一個會話的首進程,這是調用setsid的必要條件) */ if (-1 == setsid()) exit(0); /* 上面已經完成了大部分工作,但是有的系統上,當會話首進程打開 * 一個尚未與任何會話相關聯的終端設備時,該設備自動作為控制 * 終端分配給該會話。 * 為避免該情況,我們再次fork進程,于是新進程不再是會話首進程。 * 會話首進程退出時可能會給所有會話內的進程發送SIGHUP,而該 * 信號默認是結束進程,故需要忽略該信號來防止孫子進程意外結束。 */ signal(SIGHUP, SIG_IGN); if (0 != fork()) exit(0); /* 最后目標:改變工作目錄到根目錄。 * 原因:進程活動時,其工作目錄所在的文件系統不能卸下。 */ if (0 != chdir("/")) exit(0); } #endif ~~~ 進程屬于一個進程組(一個或多個進程的集合),登陸會話是包含一個或多個進程組的集合,這些進程組共享一個控制終端。 ### 三.多進程網絡服務模型 Lighttpd一開始是單進程的,在完成一組公共操作后開始轉換為多進程。 代碼框架如下: ![這里寫圖片描述](https://box.kancloud.cn/2016-02-25_56ceaefa43133.jpg "") 具體代碼分析注釋: ~~~ #ifdef USE_ALARM signal(SIGALRM, signal_handler); /* setup periodic timer (1 second) */ if (setitimer(ITIMER_REAL, &interval, NULL)) { //每隔一秒產生一個ALARM log_error_write(srv, __FILE__, __LINE__, "s", "setting timer failed"); return -1; } getitimer(ITIMER_REAL, &interval); #endif #ifdef HAVE_FORK /* start watcher and workers */ num_childs = srv->srvconf.max_worker; //存放最大的子進程的數目 if (num_childs > 0) { int child = 0; //child變量用于標記是否為子進程,0代表父進程,1代表子進程 while (!child && !srv_shutdown && !graceful_shutdown) { //子進程不可進入,srv_shutdown=1 或 graceful_shutdown=1時父進程跳出 if (num_childs > 0) { switch (fork()) { //創建子進程 case -1: return -1; case 0: child = 1; //子進程標記 break; default: num_childs--; //父進程 break; } } else { //子進程產生完畢 int status; //保存子進程退出狀態 if (-1 != wait(&status)) { //阻塞等待子進程退出,收尸 /** * one of our workers went away */ num_childs++; //表示可以再產生新的子進程 } else { switch (errno) { //發生中斷 case EINTR: /** * if we receive a SIGHUP we have to close our logs ourself as we don't * have the mainloop who can help us here */ if (handle_sig_hup) { handle_sig_hup = 0; log_error_cycle(srv); //重新打開日志文件 /** * forward to all procs in the process-group * * we also send it ourself */ if (!forwarded_sig_hup) { //通知組內所有進程 forwarded_sig_hup = 1; //只通知一次,使得后面的kill只調用一次 kill(0, SIGHUP); } } break; default: break; } } } } /** * for the parent this is the exit-point */ if (!child) { //父進程的退出點,關閉所以工作進程,做一些清理工作(關閉日志,連接的網絡資源,插件,內存等)。 /** * kill all children too */ if (graceful_shutdown) { kill(0, SIGINT); } else if (srv_shutdown) { kill(0, SIGTERM); } log_error_close(srv); network_close(srv); connections_free(srv); plugins_free(srv); server_free(srv); return 0; } } #endif ~~~ 下面的是子進程部分的關鍵代碼分析: 一開始子進程進行各種初始化工作,包括fd時間處理器的初始化(fdevent_init(srv->max_fds + 1, srv->event_handler)),stat cache初始化(stat_cache_init())等。 子進程工作在一個大while循環中。 while的工作流程如下: 1、判斷連接是否斷開。如果斷開,則調用處理程序進行處理并重新開始新一輪的日志記錄。 2、判斷是否接到了alarm函數發出的信號。接受到信號后,判斷服務器記錄的時間是否和當前時間相同。如果相同,說明時間還沒有過一秒,繼續處理連接請求。如果不相同,則時間已經過了一秒。那么,服務器則觸發插件,清理超時連接,清理stat-cache緩存。這理里面最重要的是處理超時連接。程序中通過一個for循環查詢所有的連接,比較其idle的時間和允許的最大idle時間來判斷連接是否超時。如果連接超時,則讓連接進入出錯的狀態(connection_set_state(srv, con, CON_STATE_ERROR);)。 3、判斷服務器socket連接是否失效。如果失效了,則在不是服務器過載的情況下將所有連接重新加入到fdevent中。 4、如果socket沒有失效,判斷服務器是否過載。如果過載了,則關閉所有連接,清理服務器并退出服務器。 5、分配文件描述符。 6、啟動事件輪詢。等待各種IO時間的發生。包括文件讀寫,socket請求等。 7、一旦有事件發生,調用相應的處理函數進行處理。 8、最后,檢查joblist中是否有未處理的job并處理之。 至此,一次循環結束了。然后,繼續循環直到服務器關閉。 ~~~ /* main-loop */ while (!srv_shutdown) { //只要srv_shutdown不為1,工作進程持續執行 int n; size_t ndx; time_t min_ts; if (handle_sig_hup) { //如果收到HUP信號 handler_t r; /* reset notification */ handle_sig_hup = 0; //重置標識 /* cycle logfiles */ switch(r = plugins_call_handle_sighup(srv)) { //通過plugins_call_handle_sighup來調用各個模塊的HUP處理函數 case HANDLER_GO_ON: break; default: log_error_write(srv, __FILE__, __LINE__, "sd", "sighup-handler return with an error", r); break; } if (-1 == log_error_cycle(srv)) { //重新打開日志文件,并寫入收到HUP信號到日志。此處并沒有重新讀取配置文件 log_error_write(srv, __FILE__, __LINE__, "s", "cycling errorlog failed, dying"); return -1; } else { #ifdef HAVE_SIGACTION log_error_write(srv, __FILE__, __LINE__, "sdsd", "logfiles cycled UID =", last_sighup_info.si_uid, "PID =", last_sighup_info.si_pid); #else log_error_write(srv, __FILE__, __LINE__, "s", "logfiles cycled"); #endif } } if (handle_sig_alarm) { //收到ALARM信號 /* a new second */ #ifdef USE_ALARM /* reset notification */ handle_sig_alarm = 0; #endif /* get current time */ min_ts = time(NULL); if (min_ts != srv->cur_ts) { int cs = 0; connections *conns = srv->conns; handler_t r; switch(r = plugins_call_handle_trigger(srv)) { //調用plugins_call_handle_trigger來處理各個模塊的ALARM信號處理函數 case HANDLER_GO_ON: break; case HANDLER_ERROR: log_error_write(srv, __FILE__, __LINE__, "s", "one of the triggers failed"); break; default: log_error_write(srv, __FILE__, __LINE__, "d", r); break; } /* trigger waitpid */ srv->cur_ts = min_ts; //更新服務器記錄時間 /* cleanup stat-cache */ stat_cache_trigger_cleanup(srv); //清除緩存,刪除一些比較舊的節點 /** * check all connections for timeouts * */ for (ndx = 0; ndx < conns->used; ndx++) { //處理超時連接 int changed = 0; connection *con; int t_diff; con = conns->ptr[ndx]; if (con->state == CON_STATE_READ || con->state == CON_STATE_READ_POST) { if (con->request_count == 1) { if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) { /* time - out */ #if 0 log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed - read-timeout:", con->fd); #endif /* lighttpd 中采用了狀態機(state-engine)去處理每一個連接, * 狀態機中的每一種節點表示連接當時所處的狀態,包括 connect 、 * reqstart 、 read 、 reqend 、 readpost 、handlereq、 * respstart、write、respend、error、close 這 11 個狀態 */ connection_set_state(srv, con, CON_STATE_ERROR); //調用connection_set_state進行狀態機的狀態轉換 changed = 1; } } else { if (srv->cur_ts - con->read_idle_ts > con->conf.max_keep_alive_idle) { /* time - out */ #if 0 log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed - read-timeout:", con->fd); #endif connection_set_state(srv, con, CON_STATE_ERROR); changed = 1; } } } ……………… /* we don't like div by zero */ if (0 == (t_diff = srv->cur_ts - con->connection_start)) t_diff = 1; /* 處理傳輸速度限制 * 如果某一時刻平均傳輸速度達到了用戶設置的最大值,則停止發送數據(con->traffic_limit_reached將被設為1, * 進入下面if中處理)。只要檢測到平均傳輸速度小于用戶設置的最大值就繼續發送數據, * 則滿足if的條件,con->traffic_limit_reached設為 0,同時調用狀態機切換函數。 */ if (con->traffic_limit_reached && (con->conf.kbytes_per_second == 0 || ((con->bytes_written / t_diff) < con->conf.kbytes_per_second * 1024))) { /* enable connection again */ con->traffic_limit_reached = 0; changed = 1; } if (changed) { connection_state_machine(srv, con); } con->bytes_written_cur_second = 0; *(con->conf.global_bytes_per_second_cnt_ptr) = 0; #if 0 if (cs == 0) { fprintf(stderr, "connection-state: "); cs = 1; } fprintf(stderr, "c[%d,%d]: %s ", con->fd, con->fcgi.fd, connection_get_state(con->state)); #endif } if (cs == 1) fprintf(stderr, "\n"); } } /* 根據當前的資源利用情況禁用或啟用 server sockets 服務 * * 禁用:當文件描述符(當前文件描述符和等待文件描述符之和)大于 0.9 倍服務器最大 * (系統允許或用戶設置)文件描述符數目或當前連接大于最大(系統允許或用戶設置)連接 * 數目或收到終止服務器指令時。 * * 啟用:當文件描述符(當前文件描述符和等待文件描述符之和)小于 0.8 倍服務器最大 * (系統允許或用戶設置)文件描述符數目并且當前連接小于 0.9 倍最大(系統允許或用戶設 * 置)連接數目并且終止服務器標志為 0 時。 */ if (srv->sockets_disabled) { /* our server sockets are disabled, why ? */ if ((srv->cur_fds + srv->want_fds < srv->max_fds * 0.8) && /* we have enough unused fds */ (srv->conns->used < srv->max_conns * 0.9) && (0 == graceful_shutdown)) { for (i = 0; i < srv->srv_sockets.used; i++) { server_socket *srv_socket = srv->srv_sockets.ptr[i]; fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN); } log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets enabled again"); srv->sockets_disabled = 0; } } else { if ((srv->cur_fds + srv->want_fds > srv->max_fds * 0.9) || /* out of fds */ (srv->conns->used > srv->max_conns) || /* out of connections */ (graceful_shutdown)) { /* graceful_shutdown */ /* disable server-fds */ for (i = 0; i < srv->srv_sockets.used; i++) { //逐個刪除該工作進程上的所有在 socket 描述符上的事件監聽器(通過 fdevent_event_del 函數)。 server_socket *srv_socket = srv->srv_sockets.ptr[i]; fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd); if (graceful_shutdown) { //如果是關閉服務器則注銷事件監聽器結構(主要是釋放內存空間,防止內存泄露)并且關閉文件描述符、刪除附屬文件。 /* we don't want this socket anymore, * * closing it right away will make it possible for * the next lighttpd to take over (graceful restart) * */ fdevent_unregister(srv->ev, srv_socket->fd); close(srv_socket->fd); srv_socket->fd = -1; /* network_close() will cleanup after us */ if (srv->srvconf.pid_file->used && srv->srvconf.changeroot->used == 0) { if (0 != unlink(srv->srvconf.pid_file->ptr)) { if (errno != EACCES && errno != EPERM) { log_error_write(srv, __FILE__, __LINE__, "sbds", "unlink failed for:", srv->srvconf.pid_file, errno, strerror(errno)); } } } } } if (graceful_shutdown) { log_error_write(srv, __FILE__, __LINE__, "s", "[note] graceful shutdown started"); } else if (srv->conns->used > srv->max_conns) { log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, connection limit reached"); } else { log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds"); } srv->sockets_disabled = 1; } } if (graceful_shutdown && srv->conns->used == 0) { /* we are in graceful shutdown phase and all connections are closed * we are ready to terminate without harming anyone */ srv_shutdown = 1; } /* we still have some fds to share */ if (srv->want_fds) { //如果有待處理的文件描述符,則通過狀態機切換函數進行處理,為了合理利用資源,程序會保證至少有 16 個空閑文件描述符 /* check the fdwaitqueue for waiting fds */ int free_fds = srv->max_fds - srv->cur_fds - 16; connection *con; for (; free_fds > 0 && NULL != (con = fdwaitqueue_unshift(srv, srv->fdwaitqueue)); free_fds--) { connection_state_machine(srv, con); srv->want_fds--; } } /* 通過 fdevent_poll -> epoll_wait(以 USE_LINUX_EPOLL 為例)來輪詢 I/O 事件的發生, * 其中等待 I/O 事件發生的超時值 timeout_ms=1000milliseconds,即 1 秒。 * 如果在等待的這 1 秒內有 I/O 事件發生,則返回的 n 值記錄事件數目,隨后用一個 do-while * 循環對每一個發生的 I/O 事件進行處理。 */ if ((n = fdevent_poll(srv->ev, 1000)) > 0) { /* n is the number of events */ int revents; int fd_ndx; #if 0 if (n > 0) { log_error_write(srv, __FILE__, __LINE__, "sd", "polls:", n); } #endif fd_ndx = -1; do { fdevent_handler handler; void *context; handler_t r; fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx); //獲得發生了 I/O 事件的文件描述符在 fdarray 中的索引 revents = fdevent_event_get_revent (srv->ev, fd_ndx); //獲得該文件描述符上發生的 I/O 事件類型 fd = fdevent_event_get_fd (srv->ev, fd_ndx); //獲得該文件描述符 handler = fdevent_get_handler(srv->ev, fd); //獲得 I/O 事件處理的回調函數 context = fdevent_get_context(srv->ev, fd); //獲得 I/O 事件處理的上下文環境 /* connection_handle_fdevent needs a joblist_append */ #if 0 log_error_write(srv, __FILE__, __LINE__, "sdd", "event for", fd, revents); #endif switch (r = (*handler)(srv, context, revents)) { //調用回調函數進行 I/O 事件處理,并傳入相關參數。 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)); } ~~~ 參考資料:《Lighttpd源碼分析》 高群凱 編著
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看