mongoose源碼分析系列之active_connections

1. 所在的結構體定義

struct mg_server {
  sock_t listening_sock;
  union socket_address lsa;   // Listening socket address
  struct ll active_connections;
  struct ll uri_handlers;
  char *config_options[NUM_OPTIONS];
  void *server_data;
  void *ssl_ctx;    // SSL context
  sock_t ctl[2];    // Control socketpair. Used to wake up from select() call
};
active_connections是一個雙向循環鏈表:
struct ll { struct ll *prev, *next; };

2. 變量初始化

mg_create_server函數:
  LINKED_LIST_INIT(&server->active_connections);
#define LINKED_LIST_INIT(N)  ((N)->next = (N)->prev = (N))

3. 向鏈表中添加元素

accept_new_connection函數
該函數又是一個高級的if else if else的寫法,作者的意圖操作是:
  • if(執行一個語句並判斷)
  • else if(再執行一個語句並判斷) {異常處理}
  • else if(再執行一個語句並判斷) {異常處理}
  • else {最終執行一些操作}
這裏的厲害之處就在於:
每次if或者else if執行完語句後,故意讓預期表達式爲false,同而可以執行下一個分支,這樣正常情況下,上面的三個if或else if判斷中的表達式都會執行到,同時保證最終進入到else中完成所有的處理。

下面來分析函數的具體作用:
  • sock = accept(server->listening_sock, &sa.sa, &len)    accept一個連接
  • check_acl(server->config_options[ACCESS_CONTROL_LIST], ntohl(* (uint32_t *) &sa.sin.sin_addr)   檢查連接不是acl ACCESS_CONTROL_LIST
  • conn = (struct connection *) calloc(1, sizeof(*conn))    calloc一個struct connection大小的空間,將指針賦給conn
  • set_close_on_exec(sock); and set_non_blocking_mode(sock);     給sock設置參數
  • 然後賦值:conn->server、conn->client_sock、conn->mg_conn.remote_ip、conn->mg_conn.remote_port以及conn->mg_conn.server_param
  • LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link);     將conn->link添加到server->active_connections循環鏈表中
注意:這裏只是將conn->link添加到server->active_connections循環鏈表中,並沒有添加和conn的指針相關的元素,這裏又是一個技巧。獲取conn的地方使用的另外一個宏是LINKED_LIST_ENTRY:
#define LINKED_LIST_ENTRY(P,T,N)  ((T *)((char *)(P) - offsetof(T, N)))
調用時,傳遞的三個參數是:struct ll *變量、struct connection結構體以及link成員變量,一個使用的例子如下:
conn = LINKED_LIST_ENTRY(lp, struct connection, link);
LINKED_LIST_ENTRY宏定義分解來看:
  • (char *)(P)        將P指針強轉爲char *
  • offsetof(T, N)     計算結構體T中成員N在該結構體中的偏移量
  • ((char *)(P) - offsetof(T, N))     將P的指針往前減少相應的偏移量,即獲得結構體T的指針
  • ((T *)((char *)(P) - offsetof(T, N)))     最後強轉爲(T *)類型

4. 輪詢所有的active_connections

mg_poll_server函數中
  LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
    conn = LINKED_LIST_ENTRY(lp, struct connection, link);
#define LINKED_LIST_FOREACH(H,N,T) \
     for (N = (H)->next, T = (N)->next; N != (H); N = (T), T = (N)->next)

該函數中有三處使用到這種遍歷server->active_connections並取得struct connection *類型的指針的地方:
  • add_to_set for socket select
  • Read/write from clients
  • Close expired connections and those that need to be closed
其中,close_conn(conn);時,會執行下述語句來移除server->active_connections列表中對應的節點:
    LINKED_LIST_REMOVE(&conn->link);

5. 從鏈表中移除節點

從代碼上看,只有在close_conn函數中才有調用到LINKED_LIST_REMOVE(&conn->link)操作,但是close_conn函數只在mg_poll_server中有調用。
#define LINKED_LIST_REMOVE(N) do { ((N)->next)->prev = ((N)->prev); \
     ((N)->prev)->next = ((N)->next); LINKED_LIST_INIT(N); } while (0)

LINKED_LIST_REMOVE宏完成了將conn從server->active_connections列表中移除的功能。
然後closesocket(conn->client_sock)並依次free掉conn結構體中申請的空間以及最後free掉自身。

mg_destroy_server函數中,只是遍歷所有的active_connections,然後free掉conn,但是並沒有完成其它釋放操作,如closesocket(conn->client_sock)、free(conn->request)等等,因此,此處需要改爲也調用close_conn(conn)來完成所有的釋放和銷燬動作。

6. close_conn函數調用處分析

既然server->active_connections鏈表實際上是用來保存struct connection *數據的,那麼要了解什麼時候釋放節點,則必須分析close_conn(conn)的調用處。
調用close_conn函數的地方有三處:
mg_poll_server函數:
     else if (conn->flags & CONN_CLOSE) {
      close_conn(conn);
    }
如果conn->flags標記中包含CONN_CLOSE,那麼輪詢處理中就會關閉該connection。

mg_poll_server函數:
    if (conn->flags & CONN_CLOSE || conn->last_activity_time < expire_time) {
      close_conn(conn);
    }
如果conn->flags標記中包含CONN_CLOSE或者連接的最後活動時間小於失效時間,則輪詢處理中也會關閉該connection。

mg_destroy_server函數:
      close_conn(conn);
此處本來就是要銷燬server,調用close_conn來關閉和銷燬資源。

6.1 conn->flags中CONN_CLOSE標記

write_to_client函數:
  int n = conn->ssl == NULL ? send(conn->client_sock, io->buf, io->len, 0) : 0;
  if (is_error(n)) {
    conn->flags |= CONN_CLOSE;
  }
如果發送數據失敗,置conn->flags中CONN_CLOSE標記

write_to_client函數:
  if (io->len == 0 && conn->flags & CONN_SPOOL_DONE) {
    conn->flags |= CONN_CLOSE;
  }
如果待發送數據爲0(亦即發送數據完畢),並且CONN_SPOOL_DONE標記也爲true,置conn->flags中CONN_CLOSE標記

read_from_client函數:
  n = recv(conn->client_sock, buf, sizeof(buf), 0);
  if (is_error(n)) {
    conn->flags |= CONN_CLOSE;
  } 
如果接收數據失敗,置conn->flags中CONN_CLOSE標記

close_local_endpoint函數:
  if (keep_alive) {
    process_request(conn);  // Can call us recursively if pipelining is used
  } else {
    conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
  }
如果不是keep_alive,並且數據已經發送完畢,置conn->flags中CONN_CLOSE標記

6.2 conn->flags中CONN_SPOOL_DONE標記

open_file_endpoint函數:
  if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
    conn->flags |= CONN_SPOOL_DONE;
    close(conn->endpoint.fd);
    conn->endpoint_type = EP_NONE;
  }
如果HTTP請求方法是HEAD(類似於GET, 但是不返回body信息,用於檢查對象是否存在,以及得到對象的元數據),則置conn->flags中CONN_SPOOL_DONE標記

send_options函數:
  conn->flags |= CONN_SPOOL_DONE;
如果HTTP請求方法是OPTIONS(詢問可以執行哪些方法),則置conn->flags中CONN_SPOOL_DONE標記

close_local_endpoint函數:
  if (keep_alive) {
    process_request(conn);  // Can call us recursively if pipelining is used
  } else {
    conn->flags |= conn->remote_iobuf.len == 0 ? CONN_CLOSE : CONN_SPOOL_DONE;
  }
如果不是keep_alive,並且數據尚未發送完畢,置conn->flags中CONN_SPOOL_DONE標記

綜上分析,當HTTP HEAD或HTTP OPTIONS請求,或者需要進行半關閉(HTTP 300重定向或數據讀取出錯等等),如果這個時候數據沒有傳輸完畢,需要置CONN_SPOOL_DONE標記




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章