最近在處理一個(gè)TCP粘包問題兄朋,雖然相關(guān)的知識都略知一二尿庐,但是好像讓我完整的說出來細(xì)節(jié),卻還要查下資料鹃愤,因此搓彻,整理列出來如绸,以便復(fù)習(xí)。而且這些知識點(diǎn)是后臺開發(fā)人員的必備基本功旭贬,相當(dāng)于內(nèi)功怔接,還是要多拿出來溫故而知新。
1.不同協(xié)議層的限制值:
1.1 鏈路層(以太網(wǎng))
局域網(wǎng)(無線是另一種協(xié)議)采用CSMA/CD協(xié)議稀轨,核心概念就是
通道共用扼脐,發(fā)送前監(jiān)聽沖突,沖突后重發(fā)
標(biāo)準(zhǔn)以太網(wǎng)幀長度下限:64byte
原理大概就是因?yàn)榘l(fā)送前要監(jiān)聽是否有其他人在使用通道靶端,而根據(jù)通訊原理谎势,64byte傳輸?shù)臅r(shí)間就是知道是否有沖突的最小時(shí)間。
標(biāo)準(zhǔn)以太網(wǎng)幀長度上限:1518byte
這個(gè)值也是協(xié)議標(biāo)準(zhǔn)協(xié)會“拍腦袋”的結(jié)果杨名。
一方面脏榆,如果太小,每次傳輸?shù)睦寐示偷停═CP頭部20byte台谍,IP頭部20byte须喂,鏈路層頭部18byte);另一方面,如果太大坞生,就會導(dǎo)致共享通道被某一方霸占太久仔役,而且,包太大是己,出錯(cuò)率高又兵,緩存成本也要提高。
1.2 網(wǎng)絡(luò)層
MTU: 1500byte(TCP標(biāo)準(zhǔn)化采用576byte)
因?yàn)椤皹?biāo)準(zhǔn)以太網(wǎng)幀長度上限”為1518byte卒废,所以減去鏈路層18byte的頭部沛厨,IP數(shù)據(jù)報(bào)的最大限制值就等于1500byte。
IP分片
注意和傳輸層的“數(shù)據(jù)報(bào)分段”區(qū)分摔认。
如果IP數(shù)據(jù)報(bào)長度大于MTU逆皮,就需要分成幾段傳輸,然后重組参袱,分片和重組都是在網(wǎng)絡(luò)層協(xié)議完成的电谣。
因?yàn)門CP傳輸層才有提供超時(shí)和重傳機(jī)制,網(wǎng)絡(luò)層沒有抹蚀,所以如果有一片IP分片丟失剿牺,需要重傳(TCP的重傳),丟失的IP分片所屬的TCP數(shù)據(jù)的所有IP分片况鸣,都要再重新傳輸牢贸。
1.3 傳輸層(TCP)
MSS
每個(gè)TCP數(shù)據(jù)報(bào)(去掉IP頭部和TCP頭部)的最長長度。如果未設(shè)置镐捧,默認(rèn)為536byte(對應(yīng)MTU576byte)潜索,一般在以太網(wǎng),都是1460byte(1500-20-20)懂酱。
在TCP握手的前兩次商議MSS值竹习。
TCP分段
請區(qū)分 “TCP分段” 和 “IP分片”,兩個(gè)是在不同協(xié)議層的概念列牺。
如果TCP數(shù)據(jù)長度超過MSS整陌,就要分段,然后重組瞎领。
因?yàn)镸SS<MTU泌辫,所以TCP一般沒有IP分片,而UDP和ICMP沒有分段九默,所以就只能靠IP分片了震放。
2. Nagle 算法
Nagle算法
為了解決telnet場景下,敲一下鍵盤就發(fā)送一個(gè)字符驼修,傳輸效率低導(dǎo)致網(wǎng)絡(luò)阻塞的問題殿遂。
Nagle算法基本定義是:任意時(shí)刻诈铛,最多只能有一個(gè)未被確認(rèn)的小段。 所謂“小段”墨礁,指的是小于MSS尺寸的數(shù)據(jù)塊幢竹,所謂“未被確認(rèn)”,是指一個(gè)數(shù)據(jù)塊發(fā)送出去后恩静,沒有收到對方發(fā)送的ACK確認(rèn)該數(shù)據(jù)已收到焕毫。
可以設(shè)置TCP_NODELAY來關(guān)閉,提高傳輸效率蜕企,缺點(diǎn)是實(shí)時(shí)交互性差咬荷。
TCP確認(rèn)延遲機(jī)制
可以看出,Nagle算法受限于接收方發(fā)回ACK的時(shí)間間隔轻掩,因此與之配套的接收方使用“TCP確認(rèn)延遲機(jī)制”。延遲默認(rèn)為40ms懦底,視情況調(diào)整數(shù)值唇牧。可以設(shè)置TCP_QUICKACK標(biāo)志來關(guān)閉聚唐。
3. TCP粘包
為什么會出現(xiàn)TCP粘包丐重?
在Nagle算法的作用下,假設(shè)在等待ACK期間杆查,發(fā)送一段文字“hello”扮惦,然后又發(fā)送了“world”,假設(shè)MSS為7亲桦,這時(shí)對方接收到的就是“hellowo”崖蜜。消息黏在一起,所以稱為“粘包”客峭。
解決方法:一般是使用標(biāo)識來識別界限豫领。加字段size來指明一個(gè)包的長度,或者是在一條數(shù)據(jù)的結(jié)尾加特殊的結(jié)束符(比如/n)舔琅。
為什么UDP不會粘包等恐?
首先,在發(fā)送方备蚓,UDP沒有Nagle算法來合并發(fā)送幾個(gè)數(shù)據(jù)课蔬。
然后接收方,UDP是基于消息保護(hù)邊界郊尝,不同于TCP的基于流傳輸二跋。也就是UDP會使用鏈?zhǔn)奖3置織l接收的完整消息。比如發(fā)送方發(fā)了3條消息虚循,UDP接收方會在緩存獨(dú)立保存3條消息同欠,而TCP則可能只接收到一條合并的消息样傍;UDP需要recv 3次才能接收3條消息,TCP則可能recv一次就行了铺遂。
為什么UDP會采用鏈?zhǔn)奖4娼邮盏降南⑸栏纾恳驗(yàn)?b>TCP是一對一的連接,而UDP是可能一對多的連接襟锐,為了區(qū)分不同發(fā)送方發(fā)來的消息撤逢,所以需要分開存放。
4. 為什么UDP沒有發(fā)送緩沖區(qū)
首先需要大概了解一下TCP和UDP在執(zhí)行write的原理
TCP在write的時(shí)候粮坞,系統(tǒng)把消息從用戶態(tài)拷貝到內(nèi)核的socket發(fā)送緩沖區(qū)蚊荣,然后直接返回成功,接下來會有一系列操作會用到緩沖區(qū)(丟失重傳莫杈,Nagle算法的合并互例,滑動(dòng)窗口等等)。直到收到對方的ACK筝闹,內(nèi)核才會把消息從發(fā)送緩沖區(qū)刪除媳叨。
UDP在write的時(shí)候,因?yàn)槭遣豢煽總鬏敼厍辏灾皇前褦?shù)據(jù)從用戶態(tài)拷貝到內(nèi)核糊秆,然后立即發(fā)送出去,不管對方是否能收到议双,所以不需要緩沖區(qū)痘番。
TCP和UDP都有接收緩沖區(qū)
內(nèi)核態(tài)和用戶態(tài)的切換工作模式,決定了緩沖區(qū)的必要性平痰。當(dāng)然兩種緩沖區(qū)的工作模式不一樣汞舱。
TCP如果緩沖區(qū)滿了,會通過滑動(dòng)窗口的流量控制告訴對方不要再發(fā)了觉增,不然會丟掉新包兵拢。UDP則只是簡單的丟掉。
從緩沖區(qū)理解socket的阻塞和非阻塞操作
從上面可以看到逾礁,不管是read或者write说铃,操作其實(shí)都是消息在用戶態(tài)和內(nèi)核態(tài)的拷貝。因此嘹履,阻塞其實(shí)就是看內(nèi)核緩沖區(qū)有沒有數(shù)據(jù)腻扇。
比如阻塞模式下,read會一直等待內(nèi)核緩沖區(qū)有數(shù)據(jù)才返回砾嫉,而非阻塞模式幼苛,則會立即返回;阻塞模式下焕刮,write會一直等待內(nèi)核有足夠空間寫入數(shù)據(jù)才返回舶沿,而非阻塞模式墙杯,有空間則寫入,沒有就會告訴你失敗括荡。
5. 三次握手和四次揮手
為什么需要3次握手
因?yàn)槭墙⒁粋€(gè)全雙工的連接高镐,所以互相都要得到對方的確認(rèn),以及通信的雙方要互相通知對方自己的初始化的Sequence Number畸冲,所以流程順序應(yīng)該是這樣的:(1.A跟B說我要連接嫉髓;2.B答應(yīng)并且返回x+1;3.B跟A說要建立連接邑闲;4.A答應(yīng)并且返回y+1算行。)
總共有4個(gè)步驟,但是因?yàn)椴襟E2和3可以合并一起苫耸,所以就成了“3次握手”州邢。
注意:SeqNum的增加是和傳輸?shù)淖止?jié)數(shù)相關(guān)的,握手和揮手都是1個(gè)字節(jié)鲸阔,所以才加一偷霉。
如果A在執(zhí)行步驟4之前就斷開了,B會重發(fā)步驟3褐筛。在Linux下,默認(rèn)重試次數(shù)為5次叙身,5次的重試時(shí)間間隔為1s, 2s, 4s, 8s, 16s渔扎,總共31s,第5次發(fā)出后還要等32s才知道第5次也超時(shí)了信轿,所以晃痴,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才會把斷開這個(gè)連接财忽。
SYN flood攻擊
給服務(wù)器發(fā)了一個(gè)SYN后倘核,就下線了,于是服務(wù)器需要默認(rèn)等63s才會斷開連接即彪,這樣紧唱,攻擊者就可以把服務(wù)器的syn連接的隊(duì)列耗盡,讓正常的連接請求不能處理隶校。
于是漏益,Linux下給了一個(gè)叫tcp_syncookies的參數(shù)來應(yīng)對這個(gè)事——當(dāng)SYN隊(duì)列滿了后,TCP會通過源地址端口深胳、目標(biāo)地址端口和時(shí)間戳打造出一個(gè)特別的Sequence Number發(fā)回去(又叫cookie)绰疤,如果是攻擊者則不會有響應(yīng),如果是正常連接舞终,則會把這個(gè) SYN Cookie發(fā)回來轻庆,然后服務(wù)端可以通過cookie建連接(即使你不在SYN隊(duì)列中)癣猾。請注意,請先千萬別用tcp_syncookies來處理正常的大負(fù)載的連接的情況余爆。因?yàn)榉子睿瑂ynccookies是妥協(xié)版的TCP協(xié)議,并不嚴(yán)謹(jǐn)龙屉。
對于正常的請求呐粘,你應(yīng)該調(diào)整三個(gè)TCP參數(shù)可供你選擇,第一個(gè)是:tcp_synack_retries 可以用他來減少重試次數(shù)转捕;第二個(gè)是:tcp_max_syn_backlog作岖,可以增大SYN連接數(shù);第三個(gè)是:tcp_abort_on_overflow 處理不過來干脆就直接拒絕連接了五芝。
5. TCP滑動(dòng)窗口
TCP滑動(dòng)窗口是網(wǎng)絡(luò)流控的一種實(shí)現(xiàn)方法痘儡。接收方通過通知發(fā)送方自己的窗口大小,從而控制發(fā)送方的發(fā)送速度枢步,達(dá)到防止發(fā)送方發(fā)送速度過快而導(dǎo)致自己被淹沒的目的沉删。
由上圖可以看出窗口的動(dòng)態(tài)變化過程:
1.一開始窗口大小360,Client發(fā)送140長度的數(shù)據(jù)給Server醉途,Server接收返回ACK并調(diào)整窗口為260矾瑰;
2.Client接收到ACK并調(diào)整窗口為260,然后發(fā)送180的數(shù)據(jù)給Server隘擎,Server接收后返回ACK又調(diào)整窗口為80殴穴;
3.Client接收到ACK并調(diào)整窗口為80,然后發(fā)送80的數(shù)據(jù)給Server货葬,Server接收后返回ACK調(diào)整窗口為0采幌;
TCP是雙工的協(xié)議,會話的雙方都可以同時(shí)接收震桶、發(fā)送數(shù)據(jù)休傍。TCP會話的雙方都各自維護(hù)一個(gè)“發(fā)送窗口”和一個(gè)“接收窗口”。
其中各自的“接收窗口”大小取決于應(yīng)用蹲姐、系統(tǒng)磨取、硬件的限制(TCP傳輸速率不能大于應(yīng)用的數(shù)據(jù)處理速率)。
各自的“發(fā)送窗口”則要求取決于對端通告的“接收窗口”淤堵,要求相同寝衫。
好了,差不多就這些了拐邪,好像還有非常多沒有涉及慰毅,不過相信把這些都理解好了,已經(jīng)足夠應(yīng)付大多數(shù)的面試了扎阶。
以后有時(shí)間再整理一個(gè)應(yīng)用層協(xié)議方面的文章汹胃。