2.3 收發(fā)數(shù)據(jù)
將HTTP請(qǐng)求消息交給協(xié)議棧
當(dāng)控制流程從connect回到應(yīng)用程序之后,接下來(lái)就進(jìn)入數(shù)據(jù)收發(fā)階段了弹沽。
首先窜锯,協(xié)議棧并不關(guān)心應(yīng)用程序傳來(lái)的數(shù)據(jù)是什么內(nèi)容熏挎。應(yīng)用程序在調(diào)用write時(shí)會(huì)指定發(fā)送數(shù)據(jù)的長(zhǎng)度蚌成,在協(xié)議椙岸唬看來(lái),要發(fā)送的數(shù)據(jù)就是一定長(zhǎng)度的二進(jìn)制字節(jié)序列而已担忧。
其次芹缔,協(xié)議棧并不是一收到數(shù)據(jù)就馬上發(fā)送出去,而是會(huì)將數(shù)據(jù)存放在內(nèi)部的發(fā)送緩沖區(qū)中瓶盛,并等待應(yīng)用程序的下一段數(shù)據(jù)最欠。這樣做是有道理的。應(yīng)用程序交給協(xié)議棧發(fā)送的數(shù)據(jù)長(zhǎng)度是由應(yīng)用程序本身來(lái)決定的惩猫,不同的應(yīng)用程序在實(shí)現(xiàn)上有所不同芝硬,有些程序會(huì)一次性傳遞所有的數(shù)據(jù),有些程序則會(huì)逐字節(jié)或者逐行傳遞數(shù)據(jù)轧房“枰酰總之,一次將多少數(shù)據(jù)交給協(xié)議棧是由應(yīng)用程序自行決定的锯厢,協(xié)議棧并不能控制這一行為皮官。
第一個(gè)判斷要素是每個(gè)網(wǎng)絡(luò)包能容納的數(shù)據(jù)長(zhǎng)度脯倒,協(xié)議棧會(huì)根據(jù)一個(gè)叫作MTU的參數(shù)來(lái)進(jìn)行判斷实辑。
另一個(gè)判斷要素是時(shí)間捺氢。
MTU:一個(gè)網(wǎng)絡(luò)包的最大長(zhǎng)度,以太網(wǎng)中一般為1500字節(jié)剪撬。
MSS:除去頭部之后摄乒,一個(gè)網(wǎng)絡(luò)包所能容納的TCP數(shù)據(jù)的最大長(zhǎng)度。
協(xié)議棧發(fā)送數(shù)據(jù)的判斷要素:第一個(gè)判斷要素是每個(gè)網(wǎng)絡(luò)包能容納的數(shù)據(jù)長(zhǎng)度残黑,協(xié)議棧會(huì)根據(jù)一個(gè)叫作MTU的參數(shù)來(lái)進(jìn)行判斷馍佑。另一個(gè)判斷要素是時(shí)間。
在進(jìn)行發(fā)送操作時(shí)需要綜合考慮這兩個(gè)要素以達(dá)到平衡梨水。不過拭荤,TCP協(xié)議規(guī)格中并沒有告訴我們?cè)鯓硬拍芷胶猓虼藢?shí)際如何判斷是由協(xié)議棧的開發(fā)者來(lái)決定的疫诽,也正是由于這個(gè)原因舅世,不同種類和版本的操作系統(tǒng)在相關(guān)操作上也就存在差異。
協(xié)議棧也給應(yīng)用程序保留了控制發(fā)送時(shí)機(jī)的余地奇徒。應(yīng)用程序在發(fā)送數(shù)據(jù)時(shí)可以指定一些選項(xiàng)雏亚,比如如果指定“不等待填滿緩沖區(qū)直接發(fā)送”,則協(xié)議棧就會(huì)按照要求直接發(fā)送數(shù)據(jù)摩钙。像瀏覽器這種會(huì)話型的應(yīng)用程序在向服務(wù)器發(fā)送數(shù)據(jù)時(shí)罢低,等待填滿緩沖區(qū)導(dǎo)致延遲會(huì)產(chǎn)生很大影響,因此一般會(huì)使用直接發(fā)送的選項(xiàng)胖笛。
對(duì)較大的數(shù)據(jù)進(jìn)行拆分
根據(jù)發(fā)送緩沖區(qū)中的數(shù)據(jù)拆分的情況网持,當(dāng)判斷需要發(fā)送這些數(shù)據(jù)時(shí),就在每一塊數(shù)據(jù)前面加上TCP頭部长踊,并根據(jù)套接字中記錄的控制信息標(biāo)記發(fā)送方和接收方的端口號(hào)功舀,然后交給IP模塊來(lái)執(zhí)行發(fā)送數(shù)據(jù)的操作(圖2.6)。
使用ACK號(hào)確認(rèn)網(wǎng)絡(luò)包已收到
首先之斯,TCP模塊在拆分?jǐn)?shù)據(jù)時(shí)日杈,會(huì)先算好每一塊數(shù)據(jù)相當(dāng)于從頭開始的第幾個(gè)字節(jié),接下來(lái)在發(fā)送這一塊數(shù)據(jù)時(shí)佑刷,將算好的字節(jié)數(shù)寫在TCP頭部中莉擒,“序號(hào)”字段就是派在這個(gè)用場(chǎng)上的。
然后瘫絮,發(fā)送數(shù)據(jù)的長(zhǎng)度也需要告知接收方涨冀,不過這個(gè)并不是放在TCP頭部里面的,因?yàn)橛谜麄€(gè)網(wǎng)絡(luò)包的長(zhǎng)度減去頭部的長(zhǎng)度就可以得到數(shù)據(jù)的長(zhǎng)度麦萤,所以接收方可以用這種方法來(lái)進(jìn)行計(jì)算鹿鳖。
有了上面兩個(gè)數(shù)值扁眯,我們就可以知道發(fā)送的數(shù)據(jù)是從第幾個(gè)字節(jié)開始,長(zhǎng)度是多少了翅帜。
通過這些信息姻檀,接收方還能夠檢查收到的網(wǎng)絡(luò)包有沒有遺漏。
在實(shí)際的通信中涝滴,序號(hào)并不是從1開始的绣版,而是需要用隨機(jī)數(shù)計(jì)算出一個(gè)初始值,這是因?yàn)槿绻蛱?hào)都從1開始歼疮,通信過程就會(huì)非常容易預(yù)測(cè)杂抽,有人會(huì)利用這一點(diǎn)來(lái)發(fā)動(dòng)攻擊。但是如果初始值是隨機(jī)的韩脏,那么對(duì)方就搞不清楚序號(hào)到底是從多少開始計(jì)算的缩麸,因此需要在開始收發(fā)數(shù)據(jù)之前將初始值告知通信對(duì)象。大家應(yīng)該還記得在我們剛才講過的連接過程中赡矢,有一個(gè)將SYN控制位設(shè)為1并發(fā)送給服務(wù)器的操作杭朱,就是在這一步將序號(hào)的初始值告知對(duì)方的。實(shí)際上济竹,在將SYN設(shè)為1的同時(shí)痕檬,還需要同時(shí)設(shè)置序號(hào)字段的值,而這里的值就代表序號(hào)的初始值送浊。
通過“序號(hào)”和“ACK號(hào)”可以確認(rèn)接收方是否收到了網(wǎng)絡(luò)包梦谜。
網(wǎng)卡、集線器袭景、路由器都沒有錯(cuò)誤補(bǔ)償機(jī)制唁桩,一旦檢測(cè)到錯(cuò)誤就直接丟棄相應(yīng)的包。應(yīng)用程序也是一樣耸棒,因?yàn)椴捎肨CP傳輸荒澡,即便發(fā)生一些錯(cuò)誤對(duì)方最終也能夠收到正確的數(shù)據(jù),所以應(yīng)用程序只管自顧自地發(fā)送這些數(shù)據(jù)就好了与殃。
根據(jù)網(wǎng)絡(luò)包平均往返時(shí)間調(diào)整ACK號(hào)等待時(shí)間
在公司里的局域網(wǎng)環(huán)境下单山,幾毫秒就可以返回ACK號(hào),但在互聯(lián)網(wǎng)環(huán)境中幅疼,當(dāng)遇到擁塞時(shí)需要幾百毫秒才能返回ACK號(hào)也并不稀奇米奸。
正因?yàn)椴▌?dòng)如此之大,所以將等待時(shí)間設(shè)置為一個(gè)固定值并不是一個(gè)好辦法爽篷。因此悴晰,TCP采用了動(dòng)態(tài)調(diào)整等待時(shí)間的方法,這個(gè)等待時(shí)間是根據(jù)ACK號(hào)返回所需的時(shí)間來(lái)判斷的。
TCP會(huì)在發(fā)送數(shù)據(jù)的過程中持續(xù)測(cè)量ACK號(hào)的返回時(shí)間铡溪,如果ACK號(hào)返回變慢漂辐,則相應(yīng)延長(zhǎng)等待時(shí)間;相對(duì)地棕硫,如果ACK號(hào)馬上就能返回髓涯,則相應(yīng)縮短等待時(shí)間。
使用窗口有效管理ACK號(hào)
滑動(dòng)窗口饲帅,就是在發(fā)送一個(gè)包之后复凳,不等待ACK號(hào)返回瘤泪,而是直接發(fā)送后續(xù)的一系列包灶泵。這樣一來(lái),等待ACK號(hào)的這段時(shí)間就被有效利用起來(lái)了对途。
接收方需要告訴發(fā)送方自己最多能接收多少數(shù)據(jù)赦邻,然后發(fā)送方根據(jù)這個(gè)值對(duì)數(shù)據(jù)發(fā)送操作進(jìn)行控制,這就是滑動(dòng)窗口方式的基本思路实檀。
當(dāng)接收方的TCP收到包后惶洲,會(huì)先將數(shù)據(jù)存放到接收緩沖區(qū)中。然后膳犹,接收方需要計(jì)算ACK號(hào)恬吕,將數(shù)據(jù)塊組裝起來(lái)還原成原本的數(shù)據(jù)并傳遞給應(yīng)用程序,如果這些操作還沒完成下一個(gè)包就到了也不用擔(dān)心须床,因?yàn)橄乱粋€(gè)包也會(huì)被暫存在接收緩沖區(qū)中铐料。如果數(shù)據(jù)到達(dá)的速率比處理這些數(shù)據(jù)并傳遞給應(yīng)用程序的速率還要快,那么接收緩沖區(qū)中的數(shù)據(jù)就會(huì)越堆越多豺旬,最后就會(huì)溢出钠惩。緩沖區(qū)溢出之后,后面的數(shù)據(jù)就進(jìn)不來(lái)了族阅,因此接收方就收不到后面的包了篓跛,這就和中途出錯(cuò)的結(jié)果是一樣的,也就意味著超出了接收方處理能力坦刀。我們可以通過下面的方法來(lái)避免這種情況的發(fā)生愧沟。
接收方需要告訴發(fā)送方自己最多能接收多少數(shù)據(jù),然后發(fā)送方根據(jù)這個(gè)值對(duì)數(shù)據(jù)發(fā)送操作進(jìn)行控制鲤遥,這就是滑動(dòng)窗口方式的基本思路沐寺。
前面提到的能夠接收的最大數(shù)據(jù)量稱為窗口大小,它是TCP調(diào)優(yōu)參數(shù)中非常有名的一個(gè)渴频。
ACK與窗口的合并
更新窗口大小的時(shí)機(jī)應(yīng)該是接收方從緩沖區(qū)中取出數(shù)據(jù)傳遞給應(yīng)用程序的時(shí)候芽丹。這個(gè)操作是接收方應(yīng)用程序發(fā)出請(qǐng)求時(shí)才會(huì)進(jìn)行的,而發(fā)送方不知道什么時(shí)候會(huì)進(jìn)行這樣的操作卜朗,因此當(dāng)接收方將數(shù)據(jù)傳遞給應(yīng)用程序拔第,導(dǎo)致接收緩沖區(qū)剩余容量增加時(shí)咕村,就需要告知發(fā)送方,這就是更新窗口大小的時(shí)機(jī)蚊俺。
那么ACK號(hào)又是什么情況呢懈涛?當(dāng)接收方收到數(shù)據(jù)時(shí),如果確認(rèn)內(nèi)容沒有問題泳猬,就應(yīng)該向發(fā)送方返回ACK號(hào)批钠,因此我們可以認(rèn)為收到數(shù)據(jù)之后馬上就應(yīng)該進(jìn)行這一操作。
發(fā)送方的數(shù)據(jù)到達(dá)接收方得封,在接收操作完成之后就需要向發(fā)送方返回ACK號(hào)埋心,而再經(jīng)過一段時(shí)間,當(dāng)數(shù)據(jù)傳遞給應(yīng)用程序之后才需要更新窗口大小忙上。但如果根據(jù)這樣的設(shè)計(jì)來(lái)實(shí)現(xiàn)拷呆,每收到一個(gè)包,就需要向發(fā)送方分別發(fā)送ACK號(hào)和窗口更新這兩個(gè)單獨(dú)的包疫粥。這樣一來(lái)茬斧,接收方發(fā)給發(fā)送方的包就太多了,導(dǎo)致網(wǎng)絡(luò)效率下降梗逮。
在等待發(fā)送ACK號(hào)的時(shí)候正好需要更新窗口项秉,這時(shí)就可以把ACK號(hào)和窗口更新放在一個(gè)包里發(fā)送,從而減少包的數(shù)量慷彤。當(dāng)需要連續(xù)發(fā)送多個(gè)ACK號(hào)時(shí)娄蔼,也可以減少包的數(shù)量,這是因?yàn)锳CK號(hào)表示的是已收到的數(shù)據(jù)量瞬欧,也就是說贷屎,它是告訴發(fā)送方目前已接收的數(shù)據(jù)的最后位置在哪里,因此當(dāng)需要連續(xù)發(fā)送ACK號(hào)時(shí)艘虎,只要發(fā)送最后一個(gè)ACK號(hào)就可以了唉侄,中間的可以全部省略。當(dāng)需要連續(xù)發(fā)送多個(gè)窗口更新時(shí)也可以減少包的數(shù)量野建,因?yàn)檫B續(xù)發(fā)生窗口更新說明應(yīng)用程序連續(xù)請(qǐng)求了數(shù)據(jù)属划,接收緩沖區(qū)的剩余空間連續(xù)增加。這種情況和ACK號(hào)一樣候生,可以省略中間過程同眯,只要發(fā)送最終的結(jié)果就可以了。
接收HTTP響應(yīng)消息
首先唯鸭,瀏覽器在委托協(xié)議棧發(fā)送請(qǐng)求消息之后须蜗,會(huì)調(diào)用read程序(之前的圖2.3④)來(lái)獲取響應(yīng)消息。然后,控制流程會(huì)通過read轉(zhuǎn)移到協(xié)議棧明肮,然后協(xié)議棧會(huì)執(zhí)行接下來(lái)的操作菱农。和發(fā)送數(shù)據(jù)一樣,接收數(shù)據(jù)也需要將數(shù)據(jù)暫存到接收緩沖區(qū)中柿估,這里的操作過程如下循未。首先,協(xié)議棧嘗試從接收緩沖區(qū)中取出數(shù)據(jù)并傳遞給應(yīng)用程序秫舌,但這個(gè)時(shí)候請(qǐng)求消息剛剛發(fā)送出去的妖,響應(yīng)消息可能還沒返回。響應(yīng)消息的返回還需要等待一段時(shí)間足陨,因此這時(shí)接收緩沖區(qū)中并沒有數(shù)據(jù)嫂粟,那么接收數(shù)據(jù)的操作也就無(wú)法繼續(xù)。這時(shí)钠右,協(xié)議棧會(huì)將應(yīng)用程序的委托赋元,也就是從接收緩沖區(qū)中取出數(shù)據(jù)并傳遞給應(yīng)用程序的工作暫時(shí)掛起,等服務(wù)器返回的響應(yīng)消息到達(dá)之后再繼續(xù)執(zhí)行接收操作飒房。
大家可以認(rèn)為這時(shí)協(xié)議棧會(huì)進(jìn)入暫停狀態(tài),但實(shí)際上并非如此媚值。協(xié)議棧會(huì)負(fù)責(zé)處理來(lái)自很多應(yīng)用程序的工作狠毯,因此掛起其中一項(xiàng)工作并不意味著協(xié)議棧就完全暫停了,協(xié)議棧會(huì)繼續(xù)執(zhí)行其他的工作褥芒。在執(zhí)行其他工作的時(shí)候嚼松,掛起的工作并沒有在執(zhí)行,因此看上去和暫停是一樣的锰扶。
首先献酗,協(xié)議棧會(huì)檢查收到的數(shù)據(jù)塊和TCP頭部的內(nèi)容,判斷是否有數(shù)據(jù)丟失坷牛,如果沒有問題則返回ACK號(hào)罕偎。
然后,協(xié)議棧將數(shù)據(jù)塊暫存到接收緩沖區(qū)中京闰,并將數(shù)據(jù)塊按順序連接起來(lái)還原出原始的數(shù)據(jù)
最后將數(shù)據(jù)交給應(yīng)用程序颜及。
具體來(lái)說,協(xié)議棧會(huì)將接收到的數(shù)據(jù)復(fù)制到應(yīng)用程序指定的內(nèi)存地址中蹂楣,然后將控制流程交回應(yīng)用程序俏站。將數(shù)據(jù)交給應(yīng)用程序之后,協(xié)議棧還需要找到合適的時(shí)機(jī)向發(fā)送方發(fā)送窗口更新痊土。