零。前言
TCP自從1974年被發(fā)明出來之后,歷經(jīng)30多年發(fā)展,目前成為最重要的互聯(lián)網(wǎng)基礎(chǔ)協(xié)議。有線網(wǎng)絡(luò)環(huán)境下,TCP表現(xiàn)的如虎添翼,但在移動互聯(lián)網(wǎng)和物聯(lián)網(wǎng)環(huán)境下,稍微表現(xiàn)得略有不足。
移動互聯(lián)網(wǎng)突出特性不穩(wěn)定:信號不穩(wěn)定,網(wǎng)絡(luò)連接不穩(wěn)定。雖然目前發(fā)展到4G,手機(jī)網(wǎng)絡(luò)帶寬有所增強(qiáng),但因其流動特性,信號也不是那么穩(wěn)定:坐長途公交車,或搭乘城鐵時(shí),或周邊上網(wǎng)密集時(shí)等環(huán)境,現(xiàn)實(shí)環(huán)境很復(fù)雜。
以下討論基于Linux服務(wù)器環(huán)境,假定環(huán)境為移動互聯(lián)網(wǎng)環(huán)境。記錄我目前所知TCP的一些不足,有所偏差,請給與指正。
一。三次握手
在確定傳遞數(shù)據(jù)之前需要三次握手,顯然有些多余,業(yè)界提出了TCP Fast Open (TFO)擴(kuò)展機(jī)制,兩次握手之后就可以發(fā)送正常業(yè)務(wù)數(shù)據(jù)了。但這需要客戶端和服務(wù)器端內(nèi)核層面都支持才行: Linux內(nèi)核3.6客戶端,3.7支持服務(wù)器端。

進(jìn)階閱讀:TCP Fast Open: expediting web services
二。慢啟動
一次的HTTP請求,應(yīng)用層發(fā)送較大HTML頁面的數(shù)據(jù),需要經(jīng)過若干個(gè)往返循環(huán)時(shí)間(Round-Trip Time)之后,擁塞窗口才能夠擴(kuò)展到最大適合數(shù)值,中間過程頗為冗余。這個(gè)參數(shù)直接關(guān)系著系統(tǒng)吞吐量,吞吐量大了,系統(tǒng)延遲小了。但設(shè)置成多大,需要根據(jù)業(yè)務(wù)進(jìn)行抉擇。
3.0內(nèi)核之前初始化擁塞窗口(initcwnd)大小為3。一個(gè)已建立連接初始傳輸數(shù)據(jù)時(shí)可傳遞3個(gè)MSS,若1個(gè)MSS為1400那么一次性可傳遞4K的數(shù)據(jù),若為10,一次性可傳遞13K的數(shù)據(jù)。
谷歌經(jīng)過調(diào)研,建議移動互聯(lián)網(wǎng)WEB環(huán)境下建議initcwnd設(shè)置成10,linux內(nèi)核3.0版本之后默認(rèn)值為10。遇到較低內(nèi)核,需要手動進(jìn)行設(shè)置。
若是局域網(wǎng)環(huán)境有類似大數(shù)據(jù)或文件的傳輸需求,可以考慮適當(dāng)放寬一些。
若長連接建立之后傳輸?shù)亩际切∠ⅲ看蝹鬏敹M(jìn)制不到4K,那么慢啟動改動與否都是無關(guān)緊要的事情了。
進(jìn)階閱讀:
三。線頭阻塞(Head-of-line blocking, HOL)
TCP協(xié)議數(shù)據(jù)傳輸需要按序傳輸,可以理解為FIFO先進(jìn)先出隊(duì)列,當(dāng)前面數(shù)據(jù)傳輸丟失后,后續(xù)數(shù)據(jù)單元只能等待,除非已經(jīng)丟失的數(shù)據(jù)被重傳并確認(rèn)接收以后,后續(xù)數(shù)據(jù)包才會被交付給客戶端設(shè)備,這就是所謂的線頭(HOL,head-of-line blocking)阻塞。比較浪費(fèi)服務(wù)器帶寬又降低了系統(tǒng)性能,不高效。

1. 多路復(fù)用不理想
HTTP/2提出的業(yè)務(wù)層面多路復(fù)用,雖然在一定程度上解決了HTTP/1.*單路傳輸問題,但依然受制于所依賴的TCP本身線頭阻塞的缺陷。構(gòu)建于TCP上層協(xié)議的多路復(fù)用,一旦發(fā)生出現(xiàn)線頭阻塞,需要小心對待多路的業(yè)務(wù)數(shù)據(jù)發(fā)送失敗問題。
2. TCP Keepalive機(jī)制失效
理論上TCP的Keepalive保活擴(kuò)展機(jī)制,在出現(xiàn)線頭阻塞的時(shí)候,發(fā)送不出去被一直阻塞,完全失效。
類似于NFS文件系統(tǒng),一般采用雙向的TCP Keepalive保活機(jī)制,用以規(guī)避某一端因線頭阻塞出現(xiàn)導(dǎo)致Keepalive無效的問題,及時(shí)感知一端存活情況。
3. 線頭阻塞超時(shí)提示
數(shù)據(jù)包發(fā)送了,啟動接收確認(rèn)定時(shí)器,超時(shí)后會重發(fā),重發(fā)依然無確認(rèn),后續(xù)數(shù)據(jù)會一直堆積到待發(fā)送隊(duì)列中,這里會有一個(gè)阻塞超時(shí),算法很復(fù)雜。上層應(yīng)用會接收到來自內(nèi)核協(xié)議棧的匯報(bào)"No route to host"的錯(cuò)誤信息,默認(rèn)不大于16分鐘時(shí)間。在服務(wù)器端(沒有業(yè)務(wù)心跳支持的情況下)發(fā)送數(shù)據(jù)前把終端強(qiáng)制斷線,順便結(jié)合TCPDUMP截包,等15分鐘左右內(nèi)核警告"EHOSTUNREACH"錯(cuò)誤,應(yīng)用層面就可以看到"No route to host"的通知。
四。四次擺手
兩端連接成功建立之后,需要關(guān)閉時(shí),需要產(chǎn)生四次交互,這在移動互聯(lián)網(wǎng)環(huán)境下,顯得有些多余。快速關(guān)閉,快速響應(yīng),冗余交互導(dǎo)致網(wǎng)絡(luò)帶寬被占用。
五。確認(rèn)機(jī)制通知到上層應(yīng)用?
這是一個(gè)比較美好的愿望,上層應(yīng)用在調(diào)用內(nèi)核層接口發(fā)送大段數(shù)據(jù),內(nèi)核完成發(fā)送并且收到對方完整確認(rèn),然后通知上層應(yīng)用已經(jīng)發(fā)送成功,那么在一些環(huán)境下,可以節(jié)省不少業(yè)務(wù)層面交互步驟。
六。NAT網(wǎng)關(guān)超時(shí)
IPV4有限,局域網(wǎng)環(huán)境借助于NAT路由設(shè)備擴(kuò)展了接入終端設(shè)備的數(shù)量。當(dāng)建立一個(gè)TCP長連接時(shí),NAT設(shè)備需要維護(hù)一個(gè)內(nèi)部終端連接外部服務(wù)器所使用的內(nèi)部IP:PORT與出去的IP:PORT映射對應(yīng)關(guān)系。這個(gè)關(guān)系需要維護(hù),比較耗費(fèi)內(nèi)存資源,有超時(shí)定時(shí)器清理,否則會導(dǎo)致內(nèi)存撐爆。
不同NAT設(shè)備超時(shí)值不一樣,因此才需要心跳輔助,確保經(jīng)過NAT設(shè)備的連接一直保持,避免因過長的時(shí)間被踢掉。比如針對中國移動網(wǎng)絡(luò)連接持久時(shí)間一般設(shè)置為不超過5分鐘。各種網(wǎng)絡(luò)略有差異,引入智能心跳機(jī)制比較合適。
七。終端IP漫游
手機(jī)終端經(jīng)常在2G/3G/4G和WIFI之間切換,導(dǎo)致IP地址頻繁發(fā)生改變。這樣造成的后果就是已有的網(wǎng)絡(luò)請求-響應(yīng)被放棄和終止,需要人工干預(yù)或重新發(fā)起請求,存在資源浪費(fèi)現(xiàn)象。
支持Multipath TCP的終端設(shè)備,可以同時(shí)利用 2G/3G/4G 和 WiFi 建立Mutlpath連接,通過多點(diǎn)優(yōu)化網(wǎng)絡(luò)下載,且互為備份。可以很好解決多個(gè)網(wǎng)絡(luò)共存的情況下,一個(gè)網(wǎng)絡(luò)中斷不會導(dǎo)致全局請求處理中斷,在設(shè)備的連接穩(wěn)定和可靠性方面有所增強(qiáng)。
當(dāng)然,服務(wù)器之間也可以利用Multipath TCP的多個(gè)網(wǎng)絡(luò)增強(qiáng)網(wǎng)絡(luò)吞吐量。
現(xiàn)狀是:
- 目前只有IOS 7以及后續(xù)版本支持
- Linux kernel 3.10實(shí)驗(yàn)分支上可以看到其支持身影,但何時(shí)合并到主分支上,暫時(shí)未知
進(jìn)階閱讀:A closer look at the scientific literature on Multipath TCP
八。TCP緩存膨脹
當(dāng)路由器接收到的數(shù)據(jù)包超越其隊(duì)列長度時(shí),一般會隨機(jī)丟包,以減少膨脹。針對上層應(yīng)用程序而言,延遲增加,或誤認(rèn)為數(shù)據(jù)丟失,或連接丟失等。
遇到這種情況,一般建議快速發(fā)包,以避免丟失的數(shù)據(jù)部分。內(nèi)核層面今早升級到最新版,不低于3.6即可。
進(jìn)階閱讀:Bufferbloat
九。TCP不是絕對可靠的
- IP和TCP協(xié)議在頭部都會有check sum錯(cuò)誤校驗(yàn)和機(jī)制,16位表示,反碼相加,結(jié)果求反,具體可參考 TCP校驗(yàn)和的原理和實(shí)現(xiàn)。一般錯(cuò)誤很輕松可檢測出來,但遇到兩個(gè)16位數(shù)字相加后結(jié)果不變的情況就一籌莫展了
-
以太網(wǎng)幀CRC32校驗(yàn)一般情況下都很OK,但可能遇到兩端隔離多個(gè)路由器情況下,就有可能出現(xiàn)問題,比如陳碩老師提供的一張圖: 
上圖中Client向Server發(fā)了一個(gè)TCP segment,這個(gè)segment先被封裝成一個(gè)IP packet,再被封裝成ethernet frame,發(fā)送到路由器(圖中消息a)。Router收到ethernet frame (b),轉(zhuǎn)發(fā)到另一個(gè)網(wǎng)段(c),最后Server收到d,通知應(yīng)用程序。Ethernet CRC能保證a和b相同,c和d相同;TCP header check sum的強(qiáng)度不足以保證收發(fā)payload的內(nèi)容一樣。另外,如果把Router換成NAT,那么NAT自己會構(gòu)造c(替換掉源地址),這時(shí)候a和d的payload不能用tcp header checksum校驗(yàn)。
-
路由器可能偶然出現(xiàn)硬件/內(nèi)存故障導(dǎo)致收發(fā)IP報(bào)文出現(xiàn)多bit/單bit的反轉(zhuǎn)或雙字節(jié)交換,這個(gè)反轉(zhuǎn)如果發(fā)生在payload區(qū),那么無法用鏈路層、網(wǎng)絡(luò)層、傳輸層的check sum查出來,只能通過應(yīng)用層的check sum來檢測。因此建議應(yīng)用層要設(shè)法添加校驗(yàn)數(shù)據(jù)功能。
-
大文件下載添加校驗(yàn)保證數(shù)據(jù)完整性,一般采用MD5,也用于防止安全篡改
參考資料:
十。小結(jié)
在這個(gè)滿世界都是TCP的環(huán)境下,要想對TCP動大手術(shù),這個(gè)是不太可能的,因?yàn)樗呀?jīng)固化到已有的系統(tǒng)內(nèi)核和固件中。比如升級終端(比如Android/IOS等)系統(tǒng)/固件,Linux服務(wù)器內(nèi)核,中間設(shè)備/中介設(shè)備(如路由器等),這是一個(gè)浩大工程,目前看也不現(xiàn)實(shí)。
TCP位于系統(tǒng)內(nèi)核層,內(nèi)核空間的升級、修復(fù),最為麻煩。服務(wù)器端升級還好說一些,用戶終端系統(tǒng)的升級那叫一個(gè)難。用戶空間/用戶核的應(yīng)用升級、改造相對比來說可控性強(qiáng),基于此Google專家們直接在UDP協(xié)議上進(jìn)行構(gòu)建、并且運(yùn)行在用戶空間的QUIC協(xié)議,綜合了UDP的輕量和TCP的可靠性,是一個(gè)比較新穎的方向。
若是對以后底層傳輸協(xié)議有所期望的話:
- 在用戶空間(用戶核)出現(xiàn)可以定制的協(xié)議,類似于QUIC
- 傳統(tǒng)的TCP/UDP可以運(yùn)行在用戶空間,直接略過內(nèi)核
- 完整協(xié)議棧以靜態(tài)鏈接庫形式提供給上層應(yīng)用
- 上層應(yīng)用可以在編譯、打包的時(shí)包含其所依賴協(xié)議棧靜態(tài)鏈接庫so文件
- dpdk/netmap等Packet IO框架 + 用戶空間協(xié)議堆棧,數(shù)據(jù)將從網(wǎng)卡直接送達(dá)上層應(yīng)用
- Linux內(nèi)核重要性降低,常規(guī)的SSH系統(tǒng)維護(hù)
雖然TCP存在這樣、那樣的問題,但目前還是無法繞過的網(wǎng)絡(luò)基礎(chǔ)設(shè)施,但稍微明白一些不足的地方,或許會對我們當(dāng)前使用的現(xiàn)狀有所幫助。