為什麼HTTP標準中 webService 主動會關閉tcp連接?

在http1.1協議中,對於客戶端(browser)的一個請求,如果返回Connection:Close,不是告訴客戶端讓客戶端關閉連接,而是服務端之後主動關閉連接,這樣的設計方案中,如何解決服務端大量的TIME_WAIT?為什麼不設計成讓客戶端主動關閉連接?


Server 關閉連接確實是歷史原因:HTTP/0.9 協議中 response 是沒有 header 的(RFC 1945 - Hypertext Transfer Protocol -- HTTP/1.0 參考 Simple-Response 的定義),所以 client 根本無從知道什麼時候這個東西結束。如 @梁濤 所說,這裡的 FIN 就和讀取文件時的 EOF 是一樣的作用。想想 UNIX 甚至可以用 read(2) / write(2) 操作 socket fd 就明白這樣的意義了。

然後回到 RFC 2616:
Connection: close 是一個 general-header( RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 )即:既可以作為 request header 也可以作為 response header。Connection: close 的作用在於"協商(signal)"。14.10 中:

HTTP/1.1 defines the "close" connection option for the sender to
signal that the connection will be closed after completion of the
response.

即雙方都可以關閉。"HTTP: The Definitive Guide" 也是這樣表述的。
實際上,在 "HTTP: The Definitive Guide" (英文版)p85 中討論了一模一樣的關於 TIME_WAIT 的問題。基本結論是:這個 TIME_WAIT 在 benchmark 的情況下會影響同一台 client 可以發起的請求數量;在實際應用中幾乎沒有問題。Server 保持 TIME_WAIT 並不是保持 socket,而只是保持一個記錄,表示「對應這兩個 end-point 的 packet 應該丟棄」,僅此而已。
話說這書里上下花了很大篇幅描述了一個相當複雜的關閉邏輯,為的是避免 RST 的出現,倒是有點意思。

回頭想想:
如果是 server 主動關閉連接的情況,只要調用一次 close() 就可以釋放連接,剩下的工作由內核 TCP 棧直接進行了處理,整個過程只有一次 syscall;
如果是要求 client 關閉,則 server 在寫完最後一個 response 之後需要把這個 socket 放入 readable 隊列,調用 select / epoll 去等待事件;然後調用一次 read() 才能知道連接已經被關閉,這其中是兩次 syscall,多一次用戶態程序被激活執行,而且 socket 保持時間更長——到底哪樣性能好?


歷史遺留吧,HTTP最早(0.9或更早)只有短連接,伺服器關閉連接表示數據發送完畢。
請懂行的說說為啥後來一直沒有甩掉這個包袱吧?


@梁濤 和 @陳碩 說的都很好,我來補充一下為什麼設計為不讓客戶端主動關閉連接。

這確實是歷史包袱。原因很簡單,早先客戶端處理HTTP是單線程的、阻塞的,伺服器端發送完信息後客戶端要一直等到信息處理完畢、渲染完畢,才能有處理能力來通知伺服器處理完成。在當時這個過程可以長達數分鐘,而且當時伺服器沒有能力去承載這麼多等待響應的連接(可以用Erlang計算一下這個延遲下需要滿足%99.9的可用性需要多麼恐怖的硬體)。

所以解決方案就是伺服器發送完之後就關閉連接。等客戶端接收到了所有信息,處理完畢,一看連接也關掉了,此時伺服器早已在處理其他連接了。

另外這也是為什麼有大量TIME_WAIT的原因,相比掛著連接等待客戶端關閉,伺服器等待確認TCP連接狀態要快太多了。

說到底就是HTTP協議太老了,雖然高級應用一直在更新,但是這種底層設計很難做出大的改變。這也是為什麼Google要弄SPDY來更新HTTP的底層機制。


根據有限的 TCP 和 HTTP 知識說明一下。

TCP 的 FIN 標記相當於「讀到文件尾」這一信號。客戶端在收到伺服器端發來的 FIN 標記就知道數據已經發完,可以進一步處理 HTTP Response 報文。如果換客戶端自己來計數也不是不可行,但要考慮到有一些 HTTP Response 報文可能缺少 Content-Length Header (該首部不是強制性要求的)。

另外,HTTP 1.1 要求默認使用 Keep-Alive,以便復用 TCP 連接(即長連接,不設置 Connection Header 就會有此效果),伺服器端不會主動關閉連接,此時要求客戶端自己進行報文長度計數,是否關閉連接由客戶端控制。如果通信過程出現異常,伺服器端會返回 RST 標記,強制斷開虛連接。

解決 TIME_WAIT 的辦法之一是修改內核參數將 2MSL 調小(影響整個系統,範圍過大,不推薦)。

====

有個問題,所謂「伺服器端大量的 TIME_WAIT」會有什麼影響么?有點迷糊了。


服務端資源是有限的,所以服務端要主動關閉連接。
timewait 問題,設置 tw_reuse 即可。


推薦閱讀:

TCP/IP哪些層加密了哪些沒有加密?
UNP第三版中大篇幅的介紹了SCTP,請問SCTP應用的發展趨勢怎麼樣?有沒有多投入學習的必要?
RIP協議中,解決路由自環的毒性逆轉思想是什麼?
進行 Linux 網路編程的人需不需要看 TCP/IP 詳解這種書?
IP地址為什麼是定長的?是什麼因素限制的?

TAG:計算機網路 | TCPIP |