web網(wǎng)站開(kāi)發(fā)報(bào)告深圳seo優(yōu)化
?前面了解到服務(wù)器和客戶端在創(chuàng)建套接字,建立連接后,就可以進(jìn)入到下一步,雙發(fā)可以互相發(fā)送和接收數(shù)據(jù),本篇博客就來(lái)學(xué)習(xí)一下這個(gè)過(guò)程。
?我們印象里,發(fā)送數(shù)據(jù)應(yīng)該是我們?cè)跒g覽器輸入網(wǎng)址,敲擊回車的一瞬間,發(fā)送動(dòng)作就完成了,回頭服務(wù)器處理完成將數(shù)據(jù)發(fā)送客戶端,瀏覽器解析出來(lái),這就是反過(guò)來(lái)接收的過(guò)程。
1. 發(fā)送數(shù)據(jù)
?由淺入深,了解這個(gè)大體過(guò)程,我們先來(lái)看看發(fā)送數(shù)據(jù)的簡(jiǎn)單過(guò)程。對(duì)于瀏覽器,他沒(méi)有辦法直接向網(wǎng)絡(luò)中發(fā)送數(shù)據(jù),而是要將http請(qǐng)求委托給協(xié)議棧(操作系統(tǒng)的網(wǎng)絡(luò)控制軟件)來(lái)發(fā)送。但實(shí)際上,在計(jì)算機(jī)中,并不是只有瀏覽器會(huì)發(fā)送網(wǎng)絡(luò)請(qǐng)求,QQ、微信等很多應(yīng)用程序都會(huì)執(zhí)行這個(gè)動(dòng)作。所以協(xié)議棧工作就是會(huì)接收各種應(yīng)用程序發(fā)送過(guò)來(lái)的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù),其實(shí)就是一堆的二進(jìn)制字節(jié)數(shù)據(jù)。
?協(xié)議棧在拿到數(shù)據(jù)后,是不是會(huì)直接發(fā)送到網(wǎng)絡(luò)中的呢?必然不是,他在內(nèi)部會(huì)維護(hù)一段內(nèi)存緩沖區(qū),等待下一段數(shù)據(jù),然后在某個(gè)合適的時(shí)機(jī)再發(fā)送出去。這塊內(nèi)存就是發(fā)送數(shù)據(jù)的專用緩沖區(qū)。當(dāng)然,接收數(shù)據(jù)的時(shí)候也是有一塊專用內(nèi)存的,后面我們?cè)僬f(shuō)這個(gè)。這里還提到了,合適的時(shí)機(jī)發(fā)送數(shù)據(jù),這個(gè)時(shí)機(jī)是根據(jù)兩個(gè)要素來(lái)判斷的,我們看看是哪兩個(gè)。
1.1 網(wǎng)絡(luò)包長(zhǎng)度
?第一個(gè)因素是跟網(wǎng)絡(luò)包長(zhǎng)度相關(guān)的,什么意思呢,對(duì)于某些GET請(qǐng)求,要發(fā)送的請(qǐng)求內(nèi)容必然很少,一個(gè)網(wǎng)絡(luò)包就能放得下,但是有些POST請(qǐng)求,比如我要寫的這篇博客,經(jīng)過(guò)編碼解析,需要很多歌網(wǎng)絡(luò)包才能放的下,這里就涉及到拆包的概念。
?這里先了解兩個(gè)網(wǎng)絡(luò)詞匯:MTU
,MSS
- MTU: 指的是一個(gè)網(wǎng)絡(luò)包的最大長(zhǎng)度,以太網(wǎng)中通常是1500個(gè)字節(jié)。
- MSS: MTU中去掉頭部之后,所能容納的數(shù)據(jù)的最大長(zhǎng)度。
?了解這兩個(gè)概念,我們?cè)趤?lái)看下上面說(shuō)的拆包的概念,即我們發(fā)送的某次網(wǎng)絡(luò)請(qǐng)求,可能是通過(guò)1個(gè)網(wǎng)絡(luò)包發(fā)送給服務(wù)器的,也可能是很多個(gè),決定因素就是MTU和MSS。
?在應(yīng)用程序?qū)?shù)據(jù)發(fā)送給協(xié)議棧的時(shí)候,數(shù)據(jù)可大可小,協(xié)議棧無(wú)法決定,如果每次接收到應(yīng)用程序的一次數(shù)據(jù)就立即發(fā)送出去,必然會(huì)導(dǎo)致發(fā)送大量小的網(wǎng)絡(luò)包,網(wǎng)絡(luò)效率下降。所以,協(xié)議棧一般會(huì)累積到數(shù)據(jù)量可以塞滿一個(gè)網(wǎng)絡(luò)包的時(shí)候再發(fā)送出去,即MTU的長(zhǎng)度,這就是第一個(gè)決定協(xié)議棧發(fā)送數(shù)據(jù)的因素。
1.2 發(fā)送時(shí)間
?決定協(xié)議棧 發(fā)送數(shù)據(jù)的第二個(gè)因素是時(shí)間,為啥呢?我們可以試想一下,如果GET請(qǐng)求的數(shù)據(jù)長(zhǎng)度無(wú)法達(dá)到一個(gè)MTU的長(zhǎng)度,協(xié)議棧一直等待到一個(gè)網(wǎng)絡(luò)包的數(shù)據(jù)長(zhǎng)度再發(fā)出去,必然會(huì)產(chǎn)生很大的延遲,給我們卡頓的感覺(jué)。所以,某些情況下,即便網(wǎng)絡(luò)包沒(méi)有被填滿,也會(huì)立即把數(shù)據(jù)發(fā)送出去。
?協(xié)議棧內(nèi)部會(huì)維護(hù)一個(gè)計(jì)時(shí)器,在超過(guò)設(shè)定的時(shí)間閾值后,即便沒(méi)有達(dá)到一個(gè)完整網(wǎng)絡(luò)包數(shù)據(jù)長(zhǎng)度,也會(huì)立即發(fā)送。一般,這個(gè)時(shí)間是由協(xié)議棧的開(kāi)發(fā)者決定的,不同操作系統(tǒng)的不同版本會(huì)有不同實(shí)現(xiàn)。
?其實(shí),決定協(xié)議棧發(fā)送數(shù)據(jù)的這兩個(gè)要素,在某些情況下是比較矛盾的,立即發(fā)送會(huì)導(dǎo)致網(wǎng)絡(luò)效率下降,等待太久又會(huì)造成延遲。過(guò)分依靠協(xié)議棧來(lái)決定發(fā)送時(shí)機(jī)會(huì)帶來(lái)一些問(wèn)題,所以協(xié)議棧也給了應(yīng)用程序一個(gè)選項(xiàng),來(lái)決定是否立即發(fā)送。像瀏覽器這樣的會(huì)話型應(yīng)用程序,一般會(huì)選擇“立即發(fā)送”的選項(xiàng)。
2. 確認(rèn)發(fā)送成功以及重發(fā)功能的實(shí)現(xiàn)
?TCP協(xié)議的非常重要的功能就是可以確認(rèn)通信的一方是否已經(jīng)成功收到了網(wǎng)絡(luò)包,如果沒(méi)有收到,必須具有重發(fā)的功能。這個(gè)功能的實(shí)現(xiàn)就是借助于ACK號(hào)和seq序號(hào)要進(jìn)行對(duì)方接收確認(rèn)的操作。
?上文我們說(shuō)過(guò),在網(wǎng)絡(luò)請(qǐng)求內(nèi)容過(guò)大的時(shí)候,TCP會(huì)有拆包的邏輯,那么在拆分的過(guò)程中,TCP就會(huì)計(jì)算好并記錄每個(gè)網(wǎng)絡(luò)小包在整個(gè)請(qǐng)求內(nèi)容中處于第幾個(gè)字節(jié),然后再發(fā)送網(wǎng)絡(luò)包的時(shí)候,在TCP頭部記錄這個(gè)字節(jié)數(shù)(就是seq
序號(hào),比如目前是第1個(gè)字節(jié)),服務(wù)器在接收到網(wǎng)絡(luò)包的時(shí)候,會(huì)讀取這個(gè)字節(jié),然后再計(jì)算這個(gè)網(wǎng)絡(luò)包MSS的長(zhǎng)度(比如網(wǎng)絡(luò)包數(shù)據(jù)長(zhǎng)度是1000),在確認(rèn)回復(fù)的時(shí)候,會(huì)將ACK賦值為ACK = 1 + 1000
并返回給客戶端??蛻舳嗽诮邮盏紸CK號(hào)的時(shí)候就可以確定網(wǎng)絡(luò)包已經(jīng)順利被對(duì)方接收,否則就會(huì)重試發(fā)送。
?我們可以想象一下,客戶端在發(fā)送下一個(gè)網(wǎng)絡(luò)包的時(shí)候,一定是從第1001個(gè)字節(jié)開(kāi)始的,于是服務(wù)器在收到請(qǐng)求后,可以順便驗(yàn)證1001是不是和自己最后一次ACK響應(yīng)的字節(jié)數(shù)相等,如果相等說(shuō)明中間沒(méi)有丟包,如果是2001,說(shuō)明中間丟失了至少一個(gè)網(wǎng)絡(luò)包。
?這里我們已經(jīng)提到了ACK和seq,TCP協(xié)議可以通過(guò)ACK號(hào)和序號(hào)就可以確認(rèn)對(duì)方是否收到了網(wǎng)絡(luò)包。我們來(lái)看一個(gè)虛擬的例子加深一下了解。
2.1 調(diào)整ACK號(hào)等待時(shí)間
?我們的網(wǎng)絡(luò)傳輸并不是一帆風(fēng)順的,發(fā)生擁塞和抖動(dòng)的情況是非常常見(jiàn)的。前文我們提到TCP會(huì)通過(guò)ACK號(hào)確認(rèn)對(duì)方已經(jīng)接收到網(wǎng)絡(luò)包,但是在網(wǎng)絡(luò)比較慢的情況下,發(fā)送和接收ACK號(hào)的平均響應(yīng)時(shí)間就會(huì)比較長(zhǎng)了,如果客戶端在這個(gè)時(shí)候設(shè)置了比較短的等待時(shí)間,就會(huì)在沒(méi)收到ACK的情況一直向以太網(wǎng)中發(fā)送數(shù)據(jù),這對(duì)于本來(lái)已經(jīng)繁忙的網(wǎng)絡(luò)就更加糟糕了,這其實(shí)就是TCP的網(wǎng)絡(luò)包重傳。
?通常,當(dāng)網(wǎng)絡(luò)包重傳發(fā)生后,有可能前一個(gè)相同網(wǎng)絡(luò)包的ACK號(hào)才返回,這樣的重傳其實(shí)是不必要的。所以,對(duì)于等待時(shí)間來(lái)說(shuō),需要設(shè)置一個(gè)合適的值,這個(gè)時(shí)間應(yīng)該是可以動(dòng)態(tài)調(diào)整的,而計(jì)算方法就是根據(jù)過(guò)往發(fā)送數(shù)據(jù)的過(guò)程中,持續(xù)監(jiān)測(cè)ACK號(hào)的響應(yīng)時(shí)間,如果ACK號(hào)的返回時(shí)間變慢,就會(huì)響應(yīng)延長(zhǎng)這個(gè)等待時(shí)間,否則就縮短等待時(shí)間。
?除此之外,TCP還是使用了滑動(dòng)窗口的方式來(lái)管理數(shù)據(jù)發(fā)送和ACK號(hào)的管理,大體思路就是第一個(gè)網(wǎng)絡(luò)包在發(fā)送出去之后,并不是等待當(dāng)前網(wǎng)絡(luò)包的ACK號(hào)返回才發(fā)送下一個(gè),而是直接發(fā)送下一個(gè),或者說(shuō)是下面一系列的網(wǎng)絡(luò)包,這樣的話,發(fā)送的等待時(shí)間就會(huì)被有效的利用起來(lái)了。這個(gè)過(guò)程相對(duì)復(fù)雜一些,涉及到窗口大小的概念,這個(gè)窗口大小就是指接收方網(wǎng)絡(luò)協(xié)議棧中,在當(dāng)前時(shí)間里,剩余的最大緩存空間,也就是能接收的字節(jié)數(shù)。下圖中可以看出來(lái)一來(lái)一回和滑動(dòng)窗口的方式,這里不再深入展開(kāi),可以查看相關(guān)資料。
3. 接收數(shù)據(jù)
?在客戶端發(fā)送完數(shù)據(jù)的過(guò)程后,服務(wù)器端就可以接收并處理網(wǎng)絡(luò)包了,對(duì)于單個(gè)網(wǎng)絡(luò)包的處理比較簡(jiǎn)單,對(duì)于客戶端拆分后分多次發(fā)送的網(wǎng)絡(luò)包,服務(wù)器的TCP協(xié)議同樣會(huì)以相同的方式拼接起來(lái)轉(zhuǎn)換成為對(duì)應(yīng)的網(wǎng)絡(luò)請(qǐng)求,其實(shí)就是和客戶端處理相反的方式進(jìn)行的。服務(wù)器在處理請(qǐng)求后,就會(huì)將相應(yīng)數(shù)據(jù)發(fā)送給客戶端。
?我們可以想一下,客戶端的瀏覽器程序在委托協(xié)議棧發(fā)送了網(wǎng)絡(luò)請(qǐng)求后,就處于等待響應(yīng)結(jié)果的狀態(tài)。這個(gè)狀態(tài)其實(shí)是瀏覽器調(diào)用了Socket組件庫(kù)的read()
函數(shù),協(xié)議棧會(huì)將這個(gè)工作掛起,直到服務(wù)器數(shù)據(jù)相應(yīng)之后,協(xié)議棧寫入到接收緩沖區(qū)中,在這個(gè)過(guò)程之前,接收緩沖區(qū)一直是空的,瀏覽器就無(wú)法處理數(shù)據(jù),這個(gè)掛起就是我們常說(shuō)的阻塞過(guò)程。這個(gè)如果繼續(xù)延伸的話,會(huì)有阻塞式IO,非阻塞式IO,IO多路復(fù)用等知識(shí)點(diǎn),在此不深入。
?總結(jié)一下這個(gè)過(guò)程,客戶端的協(xié)議棧會(huì)檢查接收到的數(shù)據(jù)和TCP頭部的內(nèi)容,判斷是否有數(shù)據(jù)丟失,如果沒(méi)有問(wèn)題會(huì)向服務(wù)器返回ACK號(hào)。然后協(xié)議棧將接收到的數(shù)據(jù)暫存到接收緩沖區(qū)(這個(gè)緩沖區(qū)是協(xié)議棧的)中,然后將數(shù)據(jù)塊按照順序連接起來(lái)還原成原始的數(shù)據(jù),最后將數(shù)據(jù)交還給應(yīng)用程序,其實(shí)是把協(xié)議棧緩沖區(qū)中的數(shù)據(jù)復(fù)制到瀏覽器制定的內(nèi)存地址中,然后瀏覽器去解析的過(guò)程(這個(gè)過(guò)程還是在read
里面實(shí)現(xiàn)并把控制流程交還給瀏覽器的)。
4. 斷開(kāi)連接
?接下來(lái)最后一個(gè)流程,就是數(shù)據(jù)發(fā)送完成之后的斷開(kāi)連接了,那么斷開(kāi)連接這個(gè)操作是由客戶端還是服務(wù)端發(fā)起的呢?
?在協(xié)議棧中并沒(méi)有規(guī)定哪一方應(yīng)該先發(fā)起斷開(kāi)操作,通常是由應(yīng)用程序判斷自己的數(shù)據(jù)已經(jīng)發(fā)起后就可以發(fā)起斷開(kāi)動(dòng)作了。比如我們?cè)L問(wèn)web服務(wù)器,發(fā)送請(qǐng)求,服務(wù)器接受請(qǐng)求處理完成會(huì)向客戶端返回?cái)?shù)據(jù)包,等到所有數(shù)據(jù)都返回了,服務(wù)器會(huì)主動(dòng)發(fā)起斷開(kāi)操作。下面,我們就以這個(gè)例子,服務(wù)器先發(fā)起斷開(kāi)操作理解這個(gè)過(guò)程。
?所謂的斷開(kāi)操作也是由發(fā)起方調(diào)用Socket庫(kù)的中close()
程序?qū)崿F(xiàn)的,在這個(gè)方法中,協(xié)議棧會(huì)生成包含了斷開(kāi)控制信息的TCP頭部,具體來(lái)說(shuō)就是將FIN
比特位設(shè)置1,然后再委托IP模塊將數(shù)據(jù)發(fā)給客戶端,接下來(lái),服務(wù)器套接字中就會(huì)記錄下斷開(kāi)操作的相關(guān)信息。
?接下來(lái)看我們的客戶端,在接收到FIN
比特位為1的包時(shí),客戶端知道了,噢服務(wù)器要斷開(kāi)連接了,那好在自己的套接字中標(biāo)記一下要進(jìn)入斷開(kāi)操作了,記住這里只是標(biāo)記一下,同時(shí)必須要返回服務(wù)器ACK號(hào),告知服務(wù)器已收到FIN=1
的斷開(kāi)網(wǎng)絡(luò)包了。
?然后,待到客戶端協(xié)議棧接收緩沖區(qū)數(shù)據(jù)被應(yīng)用程序全部取走之后(前面講到的應(yīng)用程序的read()
操作),客戶端感覺(jué)時(shí)機(jī)成熟了,也會(huì)向服務(wù)器發(fā)送一個(gè)包含FIN=1
頭部的網(wǎng)絡(luò)包,服務(wù)器同理也要返回ACK包,至此,雙方的通訊正式結(jié)束。
5. 刪除套接字
?接下來(lái)斷開(kāi)操作的最后一步就是刪除套接字,這里尤其注意用來(lái)通訊的套接字不會(huì)立即刪除,而是會(huì)等待一段時(shí)間后再刪除,具體原因如下:
?我們現(xiàn)在舉個(gè)跟上面相反的斷開(kāi)的例子,由客戶端發(fā)起斷開(kāi)請(qǐng)求:
- 客戶端發(fā)送
FIN=1
包 - 服務(wù)器返回
ACK
號(hào) - 服務(wù)器發(fā)送
FIN=1
包 - 客戶端返回
ACK
號(hào)
?這里特別注意最后一步,客戶端在返回ACK號(hào)之后,如果立即刪除套接字會(huì)發(fā)生什么呢?正常情況,可能是服務(wù)器收到客戶端的ACK號(hào)雙方通訊結(jié)束沒(méi)問(wèn)題。但是如果因?yàn)榫W(wǎng)絡(luò)擁塞問(wèn)題,服務(wù)器沒(méi)有在規(guī)定時(shí)間收到第四步的ACK號(hào),那么服務(wù)器又發(fā)送了一次FIN=1
,這里可能會(huì)有問(wèn)題了,因?yàn)榭蛻舳艘呀?jīng)刪除了套接字,此時(shí)如果恰巧又其他應(yīng)用程序請(qǐng)求連接服務(wù)器并且創(chuàng)建了相同端口號(hào)的套接字,那么這個(gè)新創(chuàng)建的套接字因?yàn)槭盏搅艘粭l莫名奇妙的FIN=1
就要進(jìn)入斷開(kāi)操作了,就會(huì)有問(wèn)題了。所以客戶端并不會(huì)立即刪除套接字,就是為了防止這個(gè)問(wèn)題發(fā)生。
?通常,這個(gè)等待刪除套接字的時(shí)間就是幾分鐘而已。