在 淺談 TCP 介紹了 TCP 基礎(chǔ)的理論知識(shí),這篇主要介紹 TCP 的一些應(yīng)用場(chǎng)景,主要包括(SYN Flood,TCP 長(zhǎng)連接和短連接,TCP/IP 實(shí)現(xiàn))。
1. SYN Flood
SYN Flood 又稱 SYN 洪水,是利用 TCP 三次握手的漏洞產(chǎn)生的一種攻擊方式。
常用攻擊方式有兩種,最終結(jié)果都是造成服務(wù)端在等待 ACK。一種是客戶端只管發(fā)出 SYN,不理會(huì)服務(wù)端返回的 ACK。一種是客戶端構(gòu)造錯(cuò)誤的來(lái)源 IP,使服務(wù)端返回 ACK 到錯(cuò)誤 IP。這兩種情況都會(huì)導(dǎo)致服務(wù)端無(wú)法正常收到客戶端發(fā)來(lái)的 ACK。使得 TCP 連接保持在半打開(kāi)狀態(tài),當(dāng) SYN 請(qǐng)求量過(guò)大時(shí),會(huì)出現(xiàn)大量半打開(kāi)狀態(tài)的 TCP 連接,當(dāng)系統(tǒng)資源被消耗盡后,導(dǎo)致新的連接無(wú)法被創(chuàng)建,造成正常用戶無(wú)法訪問(wèn)。
2. TCP 長(zhǎng)連接/TCP 短連接
首先需要知道的是 TCP 連接建立需要三次握手,斷開(kāi)連接需要四次,所以每次 TCP 的建立和關(guān)閉是需要消耗資源的。
TCP 短連接就是請(qǐng)求完成后關(guān)閉連接(一般是客戶端主動(dòng)關(guān)閉)。
TCP 長(zhǎng)連接是請(qǐng)求完成后仍保持 TCP 連接的狀態(tài),可繼續(xù)傳輸數(shù)據(jù)。TCP ?;罟δ懿捎枚〞r(shí)器來(lái)檢測(cè)連接狀態(tài)??蛻舳丝赡苷_\(yùn)行,已經(jīng)崩潰,重啟或不可達(dá),服務(wù)端需要針對(duì)檢測(cè)的情況做出不同反應(yīng)。
2.1 優(yōu)缺點(diǎn):
短連接管理簡(jiǎn)單,存在的連接都是最近建立和使用的。但每次建立和關(guān)閉新連接會(huì)有資源消耗,當(dāng)在大并發(fā)請(qǐng)求場(chǎng)景中使用,會(huì)出現(xiàn)大量的 TIME_WAIT 狀態(tài)的連接。導(dǎo)致資源的較大消耗。
長(zhǎng)連接需要 TCP ?;疃〞r(shí)器來(lái)檢測(cè)連接狀態(tài)。同時(shí)需要考慮不同長(zhǎng)連接的負(fù)載均衡問(wèn)題,但是可以在客戶端通過(guò)連接池得到部分解決,但在不同客戶端之間的負(fù)載均衡就無(wú)法做到。另外因?yàn)殚L(zhǎng)連接存在只創(chuàng)建不關(guān)閉的情況,所以會(huì)導(dǎo)致建立的長(zhǎng)連接越來(lái)越多。
2.2 短連接在請(qǐng)求量較大時(shí),導(dǎo)致過(guò)多的 TIME_WAIT
參考 淺談 TCP 1.3 中的圖,因?yàn)?TCP 是雙工模式,所以每個(gè)方向的連接都需要單獨(dú)關(guān)閉。
主動(dòng)關(guān)閉的一方發(fā)送 FIN 后,收到被動(dòng)關(guān)閉一方的 FIN,隨后返回 ACK 信號(hào),被動(dòng)關(guān)閉一方完成關(guān)閉連接閉環(huán),到達(dá) CLOSED 狀態(tài)。
而主動(dòng)關(guān)閉一方收到 FIN 后,發(fā)出 ACK,揮手已經(jīng)結(jié)束,所以主動(dòng)關(guān)閉一方不清楚被動(dòng)關(guān)閉一方收到 ACK 沒(méi)有,所以只能等待超出 2MSL(Maximum Segment Lifetime 報(bào)文段最大生存時(shí)間)后,如沒(méi)有再次收到 FIN (重連)即可進(jìn)行關(guān)閉。
2MSL 是因?yàn)楸粍?dòng)關(guān)閉一方首先等待 MSL 如果沒(méi)收到 ACK 則重新發(fā)送 FIN,然后主動(dòng)關(guān)閉一方等待 MSL 看是否能收到被動(dòng)關(guān)閉一方重新發(fā)送的 FIN,如果沒(méi)有收到則表示被動(dòng)關(guān)閉一方已成功收到了 ACK。
3. RPC 接口過(guò)度擔(dān)心性能
例如提供:get_topic_token_by_id 還是 get_topic_by_id?
當(dāng)數(shù)據(jù)傳輸長(zhǎng)度小于 MSS,數(shù)據(jù)仍被封裝在一個(gè)數(shù)據(jù)包中進(jìn)行傳輸,所以不會(huì)導(dǎo)致性能的下降。
4. Socket 剖析
4.1 基礎(chǔ) API
socket():創(chuàng)建一個(gè) socket 描述符(socket descriptor)。
bind():將一個(gè)地址族中的特定地址與 socket 進(jìn)行綁定。
listen():服務(wù)器通過(guò) listen 來(lái)監(jiān)聽(tīng)請(qǐng)求。
connect():客戶端通過(guò) connect 發(fā)出連接請(qǐng)求。
accept():內(nèi)核生成一個(gè)全新的描述符,代表與客戶唯一的 TCP 連接。
read()/write():從文件描述符中讀取數(shù)據(jù)或?qū)懭霐?shù)據(jù)到文件描述符。
close():關(guān)閉 socket 描述符(TODO:accept() 和 socket() 創(chuàng)建的描述符都需要關(guān)閉嗎?)
4.2 blocking 和 non-blocking 區(qū)別?
在 Network IO 操作中會(huì)涉及到兩種系統(tǒng)對(duì)象,一個(gè)是調(diào)用這個(gè) IO 用戶進(jìn)程(user process),一個(gè)是系統(tǒng)內(nèi)核(kernel)。
4.2.1 寫(xiě)阻塞:
當(dāng)調(diào)用 write 操作時(shí),只是將要寫(xiě)的數(shù)據(jù)復(fù)制到 kernel 的發(fā)送緩沖區(qū),什么時(shí)候發(fā)送到網(wǎng)絡(luò),什么時(shí)候?qū)Ψ浇邮?,系統(tǒng)不進(jìn)行通知和保證。所以當(dāng) kernel 的 send buffer 滿了之后就會(huì)造成 write 阻塞,即寫(xiě)入的速度大于對(duì)方讀取的速度。
4.2.2 讀阻塞:
當(dāng)調(diào)用 read 操作時(shí),會(huì)首先檢查 kernel 的 receive buffer 中是否有數(shù)據(jù),如果有數(shù)據(jù)則將數(shù)據(jù)拷貝到用戶進(jìn)程中,如果沒(méi)有數(shù)據(jù),blocking 和 non-blocking 進(jìn)行會(huì)做出不同的反應(yīng)。
4.2.3 blocking vs non-blocking
blocking IO 發(fā)現(xiàn) kernel 中無(wú)數(shù)據(jù),會(huì)進(jìn)行等待直到有數(shù)據(jù)出現(xiàn),然后再將數(shù)據(jù)從 kernel 拷貝到用戶進(jìn)程。
non-blocking IO 發(fā)現(xiàn) kernel 中無(wú)數(shù)據(jù)會(huì)直接返回 error。從用戶角度,進(jìn)程不再需要等待,每次 read 操作都會(huì)得到一個(gè)結(jié)果。當(dāng)進(jìn)程發(fā)現(xiàn) read 返回 error 后可以再次進(jìn)行 read 操作。
關(guān)于 blocking IO/non-blocking IO 的詳細(xì)細(xì)節(jié)可以參考:IO - 同步,異步,阻塞,非阻塞 (亡羊補(bǔ)牢篇)
疑問(wèn):(TODO)
長(zhǎng)連接導(dǎo)致報(bào)錯(cuò)的原因?(TCP keepalive)
當(dāng) TCP 數(shù)據(jù)包被 socket 隔斷后如何重新組裝?(eg: return big_data)
當(dāng) Socket 讀寫(xiě)發(fā)生錯(cuò)誤時(shí),如何進(jìn)行數(shù)據(jù)重傳?
參考資料:
SYN flood - 維基百科
TCP的 TIME_WAIT 快速回收與重用
Socket 通信原理和實(shí)現(xiàn)
IO - 同步,異步,阻塞,非阻塞 (亡羊補(bǔ)牢篇)
TCP/IP 應(yīng)用程序的通信連接模式