看過(guò)太多tcp相關(guān)文章,但是看完總是不過(guò)癮,似懂非懂,反復(fù)考慮過(guò)后,我覺(jué)得是那些文章太過(guò)理論,看起來(lái)沒(méi)有體感,所以吸收不了。 希望這篇文章能做到言簡(jiǎn)意賅,幫助大家透過(guò)案例來(lái)理解原理。
這個(gè)大家基本都能說(shuō)幾句,面試的時(shí)候候選人也肯定會(huì)告訴你這些:
三次握手
四次揮手
可靠連接
丟包重傳
但是我只希望大家記住一個(gè)核心的:tcp是可以可靠傳輸協(xié)議,它的所有特點(diǎn)都為這個(gè)可靠傳輸服務(wù)。
tcp在傳輸過(guò)程中都有一個(gè)ack,接收方通過(guò)ack告訴發(fā)送方收到那些包了。這樣發(fā)送方能知道有沒(méi)有丟包,進(jìn)而確定重傳。
來(lái)看一個(gè)java代碼連接數(shù)據(jù)庫(kù)的三次握手過(guò)程

三個(gè)紅框表示建立連接的三次握手:
第一步:client 發(fā)送 syn 到server 發(fā)起握手;
第二步:server 收到 syn后回復(fù)syn+ack給client;
第三步:client 收到syn+ack后,回復(fù)server一個(gè)ack表示收到了server的syn+ack(此時(shí)client的48287端口的連接已經(jīng)是established)
握手的核心目的是告知對(duì)方seq(綠框是client的初始seq,藍(lán)色框是server 的初始seq),對(duì)方回復(fù)ack(收到的seq+包的大?。@樣發(fā)送端就知道有沒(méi)有丟包了。
握手的次要目的是告知和協(xié)商一些信息,圖中黃框。
MSS–最大傳輸包
SACK_PERM–是否支持Selective ack(用戶(hù)優(yōu)化重傳效率)
WS–窗口計(jì)算指數(shù)(有點(diǎn)復(fù)雜的話先不用管)
這就是tcp為什么要握手建立連接,就是為了解決tcp的可靠傳輸。
再來(lái)看java連上mysql后,執(zhí)行了一個(gè)SQL: select sleep(2); 然后就斷開(kāi)了連接

四個(gè)紅框表示斷開(kāi)連接的四次揮手:
第一步: client主動(dòng)發(fā)送fin包給server
第二步: server回復(fù)ack(對(duì)應(yīng)第一步fin包的ack)給client,表示server知道client要斷開(kāi)了
第三步: server發(fā)送fin包給client,表示server也可以斷開(kāi)了
第四部: client回復(fù)ack給server,表示既然雙發(fā)都發(fā)送fin包表示斷開(kāi),那么就真的斷開(kāi)吧
這個(gè)問(wèn)題太惡心,面試官太喜歡問(wèn),其實(shí)他也許只能背誦:因?yàn)椤?/p>
我也不知道怎么回答。網(wǎng)上都說(shuō)tcp是雙向的,所以斷開(kāi)要四次。但是我認(rèn)為建連接也是雙向的(雙向都協(xié)調(diào)告知對(duì)方自己的seq號(hào)),為什么不需要四次握手呢,所以網(wǎng)上說(shuō)的不一定精準(zhǔn)。
你再看三次握手的第二步發(fā) syn+ack,如果拆分成兩步先發(fā)ack再發(fā)syn完全也是可以的(效率略低),這樣三次握手也變成四次握手了。
看起來(lái)?yè)]手的時(shí)候多一次,主要是收到第一個(gè)fin包后單獨(dú)回復(fù)了一個(gè)ack包,如果能回復(fù)fin+ack那么四次揮手也就變成三次了。 來(lái)看一個(gè)案例:

圖中第二個(gè)紅框就是回復(fù)的fin+ack,這樣四次揮手變成三次了(如果一個(gè)包就是一次的話)。
我的理解:之所以絕大數(shù)時(shí)候我們看到的都是四次揮手,是因?yàn)槭盏絝in后,知道對(duì)方要關(guān)閉了,然后OS通知應(yīng)用層要關(guān)閉啥的,這里應(yīng)用層可能需要做些準(zhǔn)備工作,有一些延時(shí),所以先回ack,準(zhǔn)備好了再發(fā)fin 。 握手過(guò)程沒(méi)有這個(gè)準(zhǔn)備過(guò)程所以可以立即發(fā)送syn+ack。
ack總是seq+len(包的大小),這樣發(fā)送方明確知道server收到那些東西了。
但是特例是三次握手和四次揮手,雖然len都是0,但是syn和fin都要占用一個(gè)seq號(hào),所以這里的ack都是seq+1。

看圖中左邊紅框里的len+seq就是接收方回復(fù)的ack的數(shù)字,表示這個(gè)包接收方收到了。然后下一個(gè)包的seq就是前一個(gè)包的len+seq,依次增加,一旦中間發(fā)出去的東西沒(méi)有收到ack就是丟包了,過(guò)一段時(shí)間(或者其他方式)觸發(fā)重傳,保障了tcp傳輸?shù)目煽啃浴?/p>
MSS 最大一個(gè)包中能傳輸?shù)男畔ⅲú缓瑃cp、ip包頭),MSS+包頭就是MTU(最大傳輸單元),如果MTU過(guò)大可能在傳輸?shù)倪^(guò)程中被卡住過(guò)不去造成卡死(這個(gè)大小的包一直傳輸不過(guò)去),跟丟包還不一樣。
SACK_PERM 用于丟包的話提升重傳效率,比如client一次發(fā)了1、2、3、4、5 這5個(gè)包給server,實(shí)際server收到了 1、3、4、5這四個(gè)包,中間2丟掉了。這個(gè)時(shí)候server回復(fù)ack的時(shí)候,都只能回復(fù)2,表示2前面所有的包都收到了,給我發(fā)第二個(gè)包吧,如果server 收到3、4、5還是沒(méi)有收到2的話,也是回復(fù)ack 2而不是回復(fù)ack 3、4、5、6的,表示快點(diǎn)發(fā)2過(guò)來(lái)。
但是這個(gè)時(shí)候client雖然知道2丟了,然后會(huì)重發(fā)2,但是不知道3、4、5有沒(méi)有丟啊,實(shí)際3、4、5 server都收到了,如果支持sack,那么可以ack 2的時(shí)候同時(shí)告訴client 3、4、5都收到了,這樣client重傳的時(shí)候只重傳2就可以,如果沒(méi)有sack的話那么可能會(huì)重傳2、3、4、5,這樣效率就低了。
來(lái)看一個(gè)例子:

圖中的紅框就是SACK。
知識(shí)點(diǎn):ack數(shù)字表示這個(gè)數(shù)字前面的數(shù)據(jù)都收到了。
tcp所有特性基本上核心都是為了可靠傳輸這個(gè)目標(biāo)來(lái)服務(wù)的,然后有一些是出于優(yōu)化性能的目的。