<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          通過源碼理解http層和tcp層的keep-alive

          共 5253字,需瀏覽 11分鐘

           ·

          2020-07-13 18:14

          很久沒更新文章了,今天突然想到這個(gè)問題,打算深入理解一下。我們知道建立tcp連接的代價(jià)是比較昂貴的,三次握手,慢開始,或者建立一個(gè)連接只為了傳少量數(shù)據(jù)。這時(shí)候如果能保存連接,那會(huì)大大提高效率。下面我們通過源碼來看看keep-alive的原理。本文分成兩個(gè)部分

          1. http層的keep-alive

          2. tcp層的keep-alive

          1 http層的keep-alive

          最近恰好在看nginx1.17.9,我們就通過nginx來分析。我們先來看一下nginx的配置。

          keepalive_timeout?timeout;
          keepalive_requests?number;

          上面兩個(gè)參數(shù)告訴nginx,如果客戶端設(shè)置了connection:keep-alive頭。nginx會(huì)保持這個(gè)連接多久,另外nginx還支持另外一個(gè)限制,就是這個(gè)長連接上最多可以處理多少個(gè)請(qǐng)求。達(dá)到閾值后就斷開連接。我們首先從nginx解析http報(bào)文開始。
          ngx_http_request.c

          ngx_http_read_request_header(r);
          //?解析http請(qǐng)求行,r->header_in的內(nèi)容由ngx_http_read_request_header設(shè)置
          rc?=?ngx_http_parse_request_line(r,?r->header_in);
          //?解析完一個(gè)http頭,開始處理
          ngx_http_process_request_headers(rev);

          上面兩句代碼是解析http報(bào)文頭,比如解析到connection:keep-alive。那么ngx_http_read_request_header函數(shù)就會(huì)解析出這個(gè)字符串,然后保存到r->header_in。

          ngx_http_header_t??ngx_http_headers_in[]?=?{
          ????{?
          ????????ngx_string("Connection"),?
          ????????offsetof(ngx_http_headers_in_t,?connection),
          ????????ngx_http_process_connection?
          ????}
          ????...
          },
          static?void?ngx_http_process_request_headers(ngx_event_t?*rev)?{
          ????hh?=?ngx_hash_find(&cmcf->headers_in_hash,?h->hash,?h->lowcase_key,?h->key.len);

          ????if?(hh?&&?hh->handler(r,?h,?hh->offset)?!=?NGX_OK)?{
          ?????????break;
          ?????}
          }

          上面的代碼大致就是根據(jù)剛才解析到的Connection:keep-alive字符串,通過Connection為key從ngx_http_headers_in數(shù)組中找到對(duì)應(yīng)的處理函數(shù)。然后執(zhí)行。我們看看ngx_http_process_connection 。

          static?ngx_int_t
          ngx_http_process_connection(ngx_http_request_t?*r,?ngx_table_elt_t?*h,
          ????ngx_uint_t?offset)

          {
          ????if?(ngx_strcasestrn(h->value.data,?"close",?5?-?1))?{
          ????????r->headers_in.connection_type?=?NGX_HTTP_CONNECTION_CLOSE;

          ????}?else?if?(ngx_strcasestrn(h->value.data,?"keep-alive",?10?-?1))?{
          ????????r->headers_in.connection_type?=?NGX_HTTP_CONNECTION_KEEP_ALIVE;
          ????}

          ????return?NGX_OK;
          }

          非常簡單,就是判斷value的值是什么,我們假設(shè)這里是keep-alive,那么nginx會(huì)設(shè)置connection_type為NGX_HTTP_CONNECTION_KEEP_ALIVE。接著nginx處理完http頭后,調(diào)用ngx_http_process_request函數(shù),該函數(shù)會(huì)調(diào)用ngx_http_handler函數(shù)。

          void
          ngx_http_handler(ngx_http_request_t?*r)?
          {
          ?????switch?(r->headers_in.connection_type)?{
          ????????case?0:
          ????????????r->keepalive?=?(r->http_version?>?NGX_HTTP_VERSION_10);
          ????????????break;

          ????????case?NGX_HTTP_CONNECTION_CLOSE:
          ????????????r->keepalive?=?0;
          ????????????break;

          ????????case?NGX_HTTP_CONNECTION_KEEP_ALIVE:
          ????????????r->keepalive?=?1;
          ????????????break;
          ????????}
          }

          我們看到這時(shí)候connection_type的值是NGX_HTTP_CONNECTION_KEEP_ALIVE,nginx會(huì)設(shè)置keepalive字段為1。看完設(shè)置,我們看什么時(shí)候會(huì)使用這個(gè)字段。我們看nginx處理完一個(gè)http請(qǐng)求后,調(diào)用ngx_http_finalize_connection關(guān)閉連接時(shí)的邏輯。

          ?if?(!ngx_terminate
          ?????????&&?!ngx_exiting
          ?????????&&?r->keepalive
          ?????????&&?clcf->keepalive_timeout?>?0)
          ????{
          ????????ngx_http_set_keepalive(r);
          ????????return;
          ????}

          我們知道這時(shí)候r->keepalive是1,clcf->keepalive_timeout就是文章開頭提到的nginx配置的。接著進(jìn)入ngx_http_set_keepalive。

          rev->handler?=?ngx_http_keepalive_handler;
          ngx_add_timer(rev,?clcf->keepalive_timeout);

          nginx會(huì)設(shè)置一個(gè)定時(shí)器,過期時(shí)間是clcf->keepalive_timeout。過期后回調(diào)函數(shù)是ngx_http_keepalive_handler。

          static?void
          ngx_http_keepalive_handler(ngx_event_t?*rev)?
          {
          ????if?(rev->timedout?||?c->close)?{
          ????????ngx_http_close_connection(c);
          ????????return;
          ????}
          }

          我們看到nginx會(huì)通過ngx_http_close_connection關(guān)閉請(qǐng)求。這就是nginx中關(guān)于keep-alive的邏輯。

          2 tcp中的keep-alive

          相比應(yīng)用層的長連接,tcp層提供的功能更多。我們看linux2.6.13.1代碼里提供的配置。

          //?多久沒有收到數(shù)據(jù)就發(fā)起探測包
          #define?TCP_KEEPALIVE_TIME????(120*60*HZ)?/*?two?hours?*/
          //?探測次數(shù)
          #define?TCP_KEEPALIVE_PROBES????9???????/*?Max?of?9?keepalive?probes????*/
          //?沒隔多久探測一次
          #define?TCP_KEEPALIVE_INTVL????(75*HZ)

          這是linux提供的默認(rèn)值。下面再看看閾值。

          #define?MAX_TCP_KEEPIDLE????32767
          #define?MAX_TCP_KEEPINTVL????32767
          #define?MAX_TCP_KEEPCNT????????127

          這三個(gè)配置和上面三個(gè)一一對(duì)應(yīng)。是上面三個(gè)配置的閾值。我們一般通過setsockopt函數(shù)來設(shè)置keep-alive。所以來看一下tcp層tcp_setsockopt的實(shí)現(xiàn)。下面只摘取其中一個(gè)配置。其他的是類似的。

          ????case?TCP_KEEPIDLE:
          ????????if?(val?1?||?val?>?MAX_TCP_KEEPIDLE)
          ????????????err?=?-EINVAL;
          ????????else?{
          ????????????tp->keepalive_time?=?val?*?HZ;
          ????????????/*
          ????????????????tcp_time_stamp是當(dāng)前時(shí)間,tp->rcv_tstamp是上次收到數(shù)據(jù)包的時(shí)間,
          ????????????????相減得到多長時(shí)間沒有收到數(shù)據(jù)包
          ????????????*/

          ????????????__u32?elapsed?=?tcp_time_stamp?-?tp->rcv_tstamp;
          ????????????//?比如設(shè)置一分鐘,那么有20秒沒有收到了。則40秒后開啟探測。
          ????????????if?(tp->keepalive_time?>?elapsed)
          ????????????????elapsed?=?tp->keepalive_time?-?elapsed;
          ????????????else
          ????????????????//?直接達(dá)到超時(shí)時(shí)間了,直接開始探測
          ????????????????elapsed?=?0;
          ????????????//?開啟一個(gè)定時(shí)器
          ????????????tcp_reset_keepalive_timer(sk,?elapsed);
          ????????}
          ????????break;

          我們看tcp_reset_keepalive_timer

          void?tcp_reset_keepalive_timer?(struct?sock?*sk,?unsigned?long?len)
          {
          ????init_timer(&sk->sk_timer);
          ????sk->sk_timer.function???=?&tcp_keepalive_timer;
          ????sk->sk_timer.data???=?(unsigned?long)sk;
          ????sk_reset_timer(sk,?&sk->sk_timer,?jiffies?+?len);
          }

          超時(shí)處理函數(shù)是tcp_keepalive_timer

          ????//?多長時(shí)間沒有收到數(shù)據(jù)包
          ????elapsed?=?tcp_time_stamp?-?tp->rcv_tstamp;
          ????/*
          ????????keepalive_time_when(tp))?=?tp->keepalive_time???:?sysctl_tcp_keepalive_time;
          ????????如果用戶沒有設(shè)置則取默認(rèn)值
          ????????如果elapsed?>?keepalive_time_when(tp)說明達(dá)到發(fā)送探測包的條件了
          ????*/

          ????if?(elapsed?>=?keepalive_time_when(tp))?{
          ????????//?再判斷探測次數(shù)是否也達(dá)到閾值了,是則發(fā)送重置包斷開連接
          ????????if?((!tp->keepalive_probes?&&?tp->probes_out?>=?sysctl_tcp_keepalive_probes)?||
          ?????????????(tp->keepalive_probes?&&?tp->probes_out?>=?tp->keepalive_probes))?{
          ????????????tcp_send_active_reset(sk,?GFP_ATOMIC);
          ????????????tcp_write_err(sk);
          ????????????goto?out;
          ????????}
          ????}


          支持

          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:


          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 關(guān)注我的官網(wǎng)?https://muyiy.cn,讓我們成為長期關(guān)系

          3. 關(guān)注公眾號(hào)「高級(jí)前端進(jìn)階」,公眾號(hào)后臺(tái)回復(fù)「面試題」 送你高級(jí)前端面試題,回復(fù)「加群」加入面試互助交流群


          》》面試官都在用的題庫,快來看看《《


          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产欧美精品久久久久 | 97国产成人无码精品久久久 | 色五月婷婷丁香电影网 | 丁香五月五月丁香 | 国产三级久久久精品麻豆三级 |