注: 本文參考極客時間《趣談計算機(jī)網(wǎng)絡(luò)》
TCP和UDP的區(qū)別
首先TCP是面向連接的,UDP是無需連接的,TCP有著三握四揮,并且三次握手和四次揮手是對TCP建立的連接有著重要意義的兩步,并且TCP是對IP無可靠性提供可靠性的源頭,UDP繼承了IP的特性,不保證不丟失包,不保證按順序到達(dá)
TCP面向字節(jié)流,發(fā)送的時候是一個流,沒有頭尾,IP包不是一個流,而是一個個的IP包,UDP也是如此
TCP是有擁塞控制的,但是UDP沒有
UDP

MAC層去掉之后,IP層首部會有一個8位的協(xié)議,這里會存放著數(shù)據(jù)里到底是TCP還是UDP,當(dāng)然這里是UDP,如果我們知道UDP格式就可以解析出來了
下一步就通過UDP包中的目標(biāo)端口號,將這個包交給應(yīng)用程序處理
UDP使用場景
- 需要資源少,網(wǎng)絡(luò)情況較好,或者對于丟包不敏感的應(yīng)用
- 不需要一對一溝通,建立連接,而是可以廣播的應(yīng)用,DHCP就是一種廣播形式,基于UDP
- 需要處理速度快,時延低,可以容忍少數(shù)丟包,但是要求即使網(wǎng)絡(luò)擁塞也不退縮,這也就早就了UDP的簡單、處理速度快,不用擔(dān)心別的事情
TCP

源端口和目標(biāo)端口不可少,包的序號是為了解決亂序問題,為了解決包的先后順序,還有就是確認(rèn)序號,發(fā)出去的包要有確認(rèn),不然無法知道是否收到,若沒有收到就要重新發(fā)送,直到送達(dá),這就是TCP的不丟包的實(shí)質(zhì)
對于TCP來說,IP層丟不丟包管不著,但是在TCP層,會努力保證可靠性
- 順序問題,穩(wěn)重不亂
- 丟包問題,承諾靠譜
- 連接維護(hù),有始有終
- 流量控制,把握分寸
- 擁塞控制,知進(jìn)知退
三次握手
- SYN 發(fā)起一個連接
- ACK 回復(fù)
- RST 重新連接
- FIN 結(jié)束連接

一開始,客戶端和服務(wù)端都處于CLOSED狀態(tài),先是服務(wù)端主動監(jiān)聽某個端口,處于LISTEN狀態(tài),然后客戶端主動發(fā)起連接SYN,之后處于SYN-SENT狀態(tài),服務(wù)端收到發(fā)起的連接,返回SYN,并且ACK客戶端的SYN,之后處于SYN-RCVD狀態(tài)。客戶端收到服務(wù)端發(fā)送的SYN和ACK之后,發(fā)送ACK的ACK,之后處于ESTABLISHED狀態(tài),因?yàn)樗话l(fā)一收成功了,服務(wù)端收到ACK的ACK之后,處于ESTABLISHED,因?yàn)樗惨话l(fā)一收了
第一次握手:Client 什么都不能確認(rèn);Server 確認(rèn)了對方發(fā)送正常
第二次握手:Client 確認(rèn)了:自己發(fā)送、接收正常,對方發(fā)送、接收正常;Server 確認(rèn)了:自己接收正常,對方發(fā)送正常
第三次握手:Client 確認(rèn)了:自己發(fā)送、接收正常,對方發(fā)送、接收正常;Server 確認(rèn)了:自己發(fā)送、接收正常,對方發(fā)送接收正常
所以三次握手就能確認(rèn)雙發(fā)收發(fā)功能都正常,缺一不可。
四次揮手

最后客戶端A的TIME-WAIT狀態(tài)時間要足夠長,長到如果B沒有收到ACK的話,B會再次發(fā)送FIN關(guān)閉連接,A會重新發(fā)送一個ACK并且時間足夠長到這個包到B
A如果直接跑路的話,它的端口就空出來了,但是B不知道,原來發(fā)的包如果在路上,但是這時突然另一個應(yīng)用開啟在了這個端口上,那不就混亂了,所以A也需要等待足夠時間,等到B發(fā)送的包在網(wǎng)絡(luò)中掛掉之后再空出端口來
等待時間設(shè)置為2MSL,報文最大的生存時間,協(xié)議規(guī)定MSL為2分鐘,實(shí)際應(yīng)用中常用的是30s,1分鐘和2分鐘等
TCP狀態(tài)機(jī)

窗口
為了記錄所有發(fā)送的包和接收的包,TCP也需要發(fā)送端和接收端分別都有緩存來保存這些記錄,發(fā)送端的緩存里是按照包的ID一個個排列,根據(jù)處理情況分為下面四個部分
- 發(fā)送了并且已經(jīng)確認(rèn)了的,這些是應(yīng)該刪掉的
- 發(fā)送了并且尚未確認(rèn),需要等待確認(rèn)的回復(fù)之后才可以刪掉
- 沒有發(fā)送的,但是已經(jīng)在等待了的
- 沒有發(fā)送,并且暫時不會發(fā)送
在TCP里,接收端會給發(fā)送端報一個窗口的大小,叫做Advertised window,這個窗口大小應(yīng)該等于上面說的第二部分加上第三部分也就是已經(jīng)發(fā)送了但是沒有得到確認(rèn)的加上還沒有發(fā)送,并且正在準(zhǔn)備發(fā)送的,超過這個窗口的,接收端忙不過來,就不能發(fā)送了
發(fā)送端需要保持的數(shù)據(jù)結(jié)構(gòu)

- LastByteAcked表示第一部分和第二部分的分界線
- LastByteSent表示第二部分和第三部分的分界線
- LastByteAcked + 窗口大小 表示第三部分和第四部分的分界線
接收端需要保持的數(shù)據(jù)結(jié)構(gòu)
- 第一部分:接收并且確認(rèn)過的
- 還沒有接收但是馬上就能接收的,也是我能夠接受的最大工作量
- 還沒有接收,也沒有辦法接收的,超過工作量的部分

- MaxRcvBuffer 最大緩存的量
- LastByteRead之后是已經(jīng)接收了的,但是還沒有被應(yīng)用層讀取的
- NextByteExcepted是第一部分和第二部分的分界線
第二部分的窗口有多大?
NextByteExpected 和 LastByteRead的差其實(shí)是還沒有被應(yīng)用層讀取的部分占用調(diào)MaxRcvBuffer的量,定義為A,窗口大小其實(shí)是MaxRcvBuffer減去A
其中第二部分里面,由于收到的包可能不是順序的,會出現(xiàn)空檔,只有和第一部分連續(xù)的,可以馬上進(jìn)行回復(fù),中間空著的部分需要等待,哪怕后面的已經(jīng)來了(可以看到接收端的窗口出現(xiàn)了虛線和實(shí)線的區(qū)別)
順序問題與丟包問題
發(fā)送端

接收端

在發(fā)送端看來,1、2、3都已經(jīng)發(fā)送并且確認(rèn)的;4、5、6、7、8、9都是發(fā)送了還沒有確認(rèn);10、11、12是還沒有發(fā)出的;13、14、15是接收方?jīng)]有空間不準(zhǔn)備發(fā)送的
在接收端看來,1、2、3、4、5都是已經(jīng)完成ACK的,但是是沒有被應(yīng)用層讀取的;6、7是等待接收的;8、9是已經(jīng)接收,但是還沒有ACK的
當(dāng)前的狀態(tài)
- 1、2、3都沒有問題,雙方達(dá)成一致
- 4和5接收方說自己已經(jīng)發(fā)送了ACK了,但是發(fā)送方還沒有收到,這個ACK包可能丟了,也可能還在路上
- 6、7、8、9肯定都發(fā)送了,但是只有8、9到了,6、7還沒有到達(dá),出現(xiàn)了亂序,這個時候只能在接收方緩存著但是沒有辦法ACK
確認(rèn)和重發(fā)機(jī)制
假設(shè)4的ACK到了,不幸的是5的ACK丟了,6、7的數(shù)據(jù)包丟失了,這應(yīng)該怎么做?
超時重試
對每一個發(fā)送了,但是沒有ACK的包,都設(shè)有一個定時器,超過了一定的時間就重新嘗試,但是這個超時的時間如何進(jìn)行評估呢,這個時間不宜過短,時間必須大于往返時間RTT,否則將會引起不必要的重傳,也不宜過長,這樣的超時時間變長,訪問就變慢了
RTT(Round-Trip Time): 往返時延。在計算機(jī)網(wǎng)絡(luò)中它是一個重要的性能指標(biāo),表示從發(fā)送端發(fā)送數(shù)據(jù)開始,到發(fā)送端收到來自接收端的確認(rèn)(接收端收到數(shù)據(jù)后便立即發(fā)送確認(rèn)),總共經(jīng)歷的時延。
估計往返時間需要TCP通過采樣RTT的時間,然后進(jìn)行加權(quán)平均,計算出來一個值,并且這個值還是隨著網(wǎng)絡(luò)的狀況不斷變化的,我們成為自適應(yīng)重傳算法
如果過一段時間,5、6、7都超時了,就會重新發(fā)送,接收方發(fā)現(xiàn)5原來接受過,于是丟棄5;6收到了,發(fā)送ACK,要求下一個是7,7不幸又丟了,當(dāng)7再次超時的時候,有需要重傳的時候,TCP的策略是超時間隔加倍,每當(dāng)遇到一次超時重傳的時候,都會將下一次超時時間間隔設(shè)為先前的兩倍,兩次超時就說明網(wǎng)絡(luò)環(huán)境差,不適合頻繁反復(fù)發(fā)送
超時觸發(fā)重傳存在的問題是,超時周期可能相對較長
有一個快速重傳的機(jī)制,當(dāng)接收方收到一個序號大于下一個所期望的報文段時,就檢測到了數(shù)據(jù)流中的一個間隔,于是發(fā)送三個冗余的ACK,客戶端收到后,在定時器過期之前,重傳丟失的報文段
例如,接收方發(fā)現(xiàn)6、8、9都已經(jīng)接收了,7還沒來,那肯定是丟了,于是發(fā)送三個6的ACK要求下一個是7,客戶端收到3個ACK就會發(fā)現(xiàn)7的包確實(shí)又丟了,不再等待超時,馬上重發(fā)
SACK,這種方式需要在TCP頭加一個SACK的東西,可以將緩存的地圖發(fā)送給發(fā)送方,例如有了ACK6、ACK8、ACK9就會知道7丟了
流量控制
在對于包的確認(rèn)中,同時會攜帶一個窗口的大小
假設(shè)窗口不變,始終為9,4的確認(rèn)來的時候,會右移一個,這個時候第13個包也可以發(fā)送了

這個時候,假設(shè)發(fā)送端發(fā)送過猛,會將第三部分的10、11、12、13全部發(fā)送完畢,之后就停止發(fā)送了,未發(fā)送可發(fā)送部分為0

當(dāng)對于包5的確認(rèn)到達(dá)的時候,在客戶端相當(dāng)于窗口滑動了一格,這個時候才可以有更多的包可以發(fā)送了,接下來14可以被發(fā)送

如果接收方實(shí)在處理太慢,導(dǎo)致緩存中沒有了空間,可以通過確認(rèn)信息修改窗口的大小,甚至可以設(shè)置為0,讓發(fā)送端暫時停止發(fā)送
假設(shè)接收端應(yīng)用一直不讀取緩存中的數(shù)據(jù),當(dāng)數(shù)據(jù)包6被確認(rèn)后,窗口大小就會減小一個變?yōu)?

這個時候可以看到,接收端的窗口并沒有向右移動,只是簡單地將左邊的標(biāo)記右移一格,窗口大小變?yōu)?

如果接收端一直不處理數(shù)據(jù),則隨著確認(rèn)包越來越多,窗口越來越小直到為0

如果情況變成這樣,發(fā)送方會定時發(fā)送窗口探測數(shù)據(jù)包,看看是否有機(jī)會調(diào)整窗口的大小,當(dāng)接收方比較慢的時候,要防止低能窗口綜合征,不要空出一個字節(jié)就告訴發(fā)送方,然后立馬被填滿,可以當(dāng)窗口太小的時候,不更新窗口,直到達(dá)到一定大小,或者緩沖區(qū)一般為空的時候再更新窗口
擁塞控制
擁塞控制同樣通過窗口的大小來控制,滑動窗口是為了防止發(fā)送方把接收方緩存塞滿,而擁塞窗口是為了不把網(wǎng)絡(luò)填滿
LastByteSent - LastByteAcked <= min{滑動窗口, 擁塞窗口}
TCP協(xié)議是不知道真?zhèn)€網(wǎng)絡(luò)路徑都是什么,TCP包常被比喻為往一個誰管理灌水TCP擁塞控制就是在不堵塞,不丟包的情況下,盡量發(fā)揮帶寬
網(wǎng)路通道的容量 = 帶寬 x 往返延遲

假設(shè)往返時間為8s,發(fā)送的過程4s,返回的時間4s,每個包1024byte,過了8s,8個包都發(fā)出去了,其中4個已經(jīng)到達(dá)了接收端,但是ACK還在路上,不能算是發(fā)送成功了,5-8后四個包還在路上沒被接收,這個時候,整個管道剛好被撐滿
如果我們在這個基礎(chǔ)上再將窗口調(diào)大一點(diǎn),會出現(xiàn)什么現(xiàn)象?
如果從發(fā)送端到接收端會經(jīng)過四個設(shè)備,每個設(shè)備處理包的時間需要1s,所以4個包的話,總共的處理時間為4s,如果窗口調(diào)大,也就有可能增加發(fā)送速度,單位時間內(nèi),會有更多的包到達(dá)這些中間設(shè)備,那么處理中的設(shè)備會丟棄到多余的包,這是我們不想看到的
這個時候,我們可以為這四臺設(shè)備增加緩存,處理不過來的包在隊列里等待,這樣就不會丟失了,但是缺點(diǎn)是會增加時間,在之前我們分析過只需要4s一個包即可到達(dá)發(fā)送端,但是進(jìn)入緩存中多余的包肯定到達(dá)的時間是要超過4s的,如果這個時候發(fā)送方還是沒有收到ACK那么就會觸發(fā)超時重傳,TCP的擁塞控制就是為了處理包的丟失和超時重傳
一條TCP連接的開始,cwnd設(shè)置為一個報文段,一次只能發(fā)送一個,當(dāng)收到這個確認(rèn)的時候,cwnd +1,于是一次能夠發(fā)送2個,當(dāng)這兩個的確認(rèn)到來的時候,每個確認(rèn)的cwnd + 1 ,兩個確認(rèn)的cwnd就可以 +2,現(xiàn)在可以發(fā)送4個, 這是指數(shù)級別的增長,但是有一個值sshthresh為65535字節(jié),當(dāng)超過這個值的時候不要增長得這么快了,可能快滿了,再慢下來
于是,每收到一個確認(rèn)后,cwnd增長1/cwnd,一共發(fā)送8個的話,當(dāng)8個確認(rèn)到來的時候,每個確認(rèn)增加1/8,八個確認(rèn)一共cwnd + 1,于是一次能夠發(fā)送9個,變成了線性增長,但是肯定有一天會滿,這個時候就會出現(xiàn)擁堵,就需要慢慢等待包的處理
擁塞的一種形式是丟包,需要超時重傳,這個時候
- 將sshthresh設(shè)為cwnd/2
- 將cwnd設(shè)為1
重新開始慢啟動,這樣的話,只要超時重傳就感覺會回到解放前
快速重傳,當(dāng)接收端發(fā)現(xiàn)丟了一個中間包的時候,發(fā)送三次前一個包的ACK,于是發(fā)送端就會快速重傳,不必等待超時再重傳,TCP認(rèn)為這種情況不嚴(yán)重,因?yàn)榇蟛糠譀]丟,只丟了一小部分
- cwnd減半為cwnd/2
- sshthresh變?yōu)閏wnd
- 當(dāng)3個包返回的時候,cwnd變?yōu)閟shthresh + 3

正是這種知道該快還是慢的情況下,使得時延很重要的情況下,反而降低了速度,但是擁塞控制還是存在問題
第一個問題是,丟包并不代表是網(wǎng)絡(luò)擁堵了導(dǎo)致設(shè)備不能處理多余的包主動丟棄,但是這個時候如果我們認(rèn)為是擁塞了,是不合適的
第二個問題是,TCP的擁塞控制要等到將中間設(shè)備都填充滿了才會丟包,降低速度,這個時候其實(shí)已經(jīng)晚了,只要TCP填滿管道就好了,沒必要接著填
為了優(yōu)化這兩個問題,有了TCP BBR擁塞算法,它企圖找到一個平衡點(diǎn),通過不斷的加快發(fā)送速度,將管道填滿,但是不會填滿中間設(shè)備的緩存,因?yàn)檫@樣時延會增加,這個平衡的時點(diǎn)可以很好的達(dá)到高帶寬和低時延的平衡
