沒有網(wǎng)站可以做淘寶客嗎搜索引擎的設(shè)計與實(shí)現(xiàn)
TCP協(xié)議
延遲應(yīng)答
它也是基于滑動窗口,提高效率的一種機(jī)制,結(jié)合滑動窗口以及流量控制,能夠以延遲應(yīng)答ACK的方式,把反饋的窗口,搞大.核心在于允許范圍內(nèi),讓窗口盡可能大.
如果接收數(shù)據(jù)的主機(jī)立刻返回ACK應(yīng)答,這時候返回的窗口可能比較小.
1.假設(shè)接收端緩沖區(qū)為1M.一次收到了500K的數(shù)據(jù);如果立刻應(yīng)答,返回的窗口就是500K;
2.但實(shí)際上可能處理端處理的速度很快1,10ms之內(nèi)就把500K數(shù)據(jù)從緩沖區(qū)消費(fèi)掉了;
3.在這種情況下,接收端處理還遠(yuǎn)沒有到達(dá)自己的極限,即使窗口再放大一些,也能處理的過來;
4.如果接收端稍微等一會再應(yīng)答,比如等待200ms再應(yīng)答,那么這個時候返回的窗口就是1M;
簡而言之:接受方收到數(shù)據(jù)之后,不會立即返回ACK.而是稍等一下,等一會再返回ACK.等的這一會,相當(dāng)于給接收方的應(yīng)用程序這里,騰出更多的時間,來消費(fèi)這里的數(shù)據(jù).
典型場景:發(fā)送方不停發(fā),接收方不停取
新收到的數(shù)據(jù)也占一部分空間.如果不是立即返回,比如延時100ms,在100ms之內(nèi),接收方應(yīng)用程序就能再多消費(fèi)一些數(shù)據(jù),剩余的空間就更大,返回的窗口就是一個比較大的值.?
一定要記得,窗口越大,網(wǎng)絡(luò)吞吐量就越大,傳輸效率就越高.我們的目標(biāo)就是在保證網(wǎng)絡(luò)不擁塞的情況下盡可能提高傳輸效率;
那么所有包都可以延時應(yīng)答嗎?肯定也不是;
數(shù)量限制:每隔N個包就應(yīng)答一次;
時間限制:超過最大延遲時間就應(yīng)答一次;
具體的數(shù)量和超時時間,依操作系統(tǒng)不同也有差異;一般N取2,超時時間取200ms;
捎帶應(yīng)答
盡可能把能合并的數(shù)據(jù)包合并,從而提高效率的效果.?
在延遲應(yīng)答的基礎(chǔ)上,我們發(fā)現(xiàn),很多情況下,客戶端服務(wù)器在應(yīng)用層也是"一發(fā)一收"的.意味著客戶端給服務(wù)器說了"How are you",服務(wù)器也會給客戶端回一個"Fine, thank you";
那么這個時候ACK就可以搭順風(fēng)車,和服務(wù)器回應(yīng)的"Fine, thank you"一起給回客戶端.
正常情況下,2和3之間有一定時間間隔,此時就分兩個包發(fā)送.但是由于延遲應(yīng)答,ack應(yīng)答時間有所推遲,ack就可以和response合并.
ack在延時的這段時間里,響應(yīng)數(shù)據(jù)剛好準(zhǔn)備好了.此時就可以把a(bǔ)ck和應(yīng)答的響應(yīng)數(shù)據(jù)合并成一個TCP數(shù)據(jù)報.本身ack也不攜帶任何載荷,只是把ACK載荷設(shè)置為1,并設(shè)置確認(rèn)序號以及窗口大小.
注意!很多時候客戶端和服務(wù)端之間是長連接,要進(jìn)行若干次請求的.在捎帶應(yīng)答的加持下,在捎帶應(yīng)答的加持下,后續(xù)每次傳輸請求響應(yīng),都可能觸發(fā)捎帶應(yīng)答(也不是一定觸發(fā),具體是否能觸發(fā),取決于代碼怎么寫,取決于下一個數(shù)據(jù)來的快不快),都可能把接下來的數(shù)據(jù)和ack合二為一.
面向字節(jié)流
創(chuàng)建一個TCP的socket,同時在內(nèi)核中創(chuàng)建一個發(fā)送緩沖區(qū)和一個接收緩沖區(qū).?
調(diào)用write時,數(shù)據(jù)會先寫入發(fā)送緩沖區(qū)中;
如果發(fā)送的字節(jié)數(shù)太長,會被拆分成多個TCP的數(shù)據(jù)包發(fā)出;
如果發(fā)送的字節(jié)太短,就會先在緩沖區(qū)中等待,等到緩沖區(qū)長度差不多了,或者其他合適的時機(jī)發(fā)送出去;
接收數(shù)據(jù)的時候,數(shù)據(jù)也是從網(wǎng)卡的驅(qū)動程序到達(dá)內(nèi)核的接收緩沖區(qū);
然后應(yīng)用程序可以調(diào)用read從接收緩沖區(qū)拿數(shù)據(jù);
另一方面,TCP的一個連接,既有發(fā)送緩沖區(qū),也有接收緩沖區(qū),那么對于一個連接,既可以讀數(shù)據(jù),也可以寫數(shù)據(jù),這個概念叫全雙工;
由于緩沖區(qū)的存在,TCP程序的讀和寫不需要一一匹配,例如:
寫100個字節(jié)數(shù)據(jù)時,可以調(diào)用一次write寫100個字節(jié),也可以調(diào)用100個write,每次寫一個字節(jié);
讀100個字節(jié)數(shù)據(jù)時,也完全不需要考慮寫的時候是怎么寫的,既可以一次read100個字節(jié),也可以一次read一個字節(jié),重復(fù)100次;
粘包問題
在tcp傳輸?shù)臄?shù)據(jù)到了接收方之后,接收方要根據(jù)socket api來read出來.read出來的結(jié)果就是應(yīng)用層數(shù)據(jù)包.由于整個read過程非常靈活,可能使代碼中無法區(qū)分出當(dāng)前的數(shù)據(jù)從哪到哪是一個完整的數(shù)據(jù)包.
首先要明確,粘包問題中的"包",是指應(yīng)用層的數(shù)據(jù)包;
在TCP協(xié)議頭中,沒有如同UDP一樣的"報文長度"這樣的字段,但是有一個序號這樣的字段;
站在傳輸層的角度上,TCP是一個一個報文過來的,按照序號排好放在緩沖區(qū)中;
站在應(yīng)用層的角度,看到的只是一串連續(xù)的字節(jié)數(shù)據(jù);
那么應(yīng)用程序看到了這么一連串的字節(jié)數(shù)據(jù),就不知道從哪個部分到哪個部分,是一個完整的應(yīng)用層數(shù)據(jù)包;
那么如何避免粘包問題呢?歸根結(jié)底就是一句話,明確兩個包之間的邊界.?
對于定長的包,保證每次都按固定的大小讀取即可;例如上面的Request結(jié)構(gòu),是固定大小,那么就從緩沖區(qū)從頭開始按sizeof(Request)依次讀取即可;
對于變長的包,可以在包頭的位置,約定一個包總長度的字段,從而就知道了包結(jié)束的位置;
對于變長的包,還可以在包和包之間使用明確的分隔符(應(yīng)用層協(xié)議,是程序員自己來定的,只要保證分隔符不和正文沖突即可);
思考:對于UDP協(xié)議來說,是否也存在"粘包問題"呢?
對于UDP,如果還沒有上層交付數(shù)據(jù),UDP報文長度仍然存在.同時,UDP是一個一個把數(shù)據(jù)交付給應(yīng)用層.就有很明確的數(shù)據(jù)邊界.
站在應(yīng)用層的角度,使用UDP的時候,要么收到完整的UDP報文,要么不收.不會出現(xiàn)"半個"的情況.
UDP的接收緩沖區(qū)不是隊列結(jié)構(gòu),而是鏈表,每一個結(jié)點(diǎn)都是一個UDP數(shù)據(jù)報.
粘包問題,是TCP引起的,但TCP本身并不解決,而是由程序員寫代碼自行處理(應(yīng)用層邏輯),xml,json, protobuffer都能處理粘包問題.
異常情況
進(jìn)程終止:進(jìn)程終止會導(dǎo)致釋放文件操作符,仍然可以發(fā)送FIN,和正常關(guān)閉沒有什么區(qū)別.(進(jìn)程無論是正常結(jié)束,還是異常崩潰,都會觸發(fā)到回收文件資源,關(guān)閉文件這樣的效果(系統(tǒng)自動完成的),就會觸發(fā)四次揮手)
TCP連接的生命周期,可以比進(jìn)程更長一些.雖然進(jìn)程已經(jīng)退出了,但是TCP連接還在,仍然可以繼續(xù)進(jìn)行四次揮手.
其中一方機(jī)器關(guān)機(jī)(按照正常流程關(guān)機(jī)):當(dāng)有個主機(jī),觸發(fā)關(guān)機(jī)操作,就會先強(qiáng)制終止所有的進(jìn)程(類似于上述的強(qiáng)殺進(jìn)程),終止進(jìn)程自然會觸發(fā)四次揮手~
點(diǎn)了關(guān)機(jī)之后,此時,四次揮手不一定能揮完,因?yàn)橄到y(tǒng)馬上就關(guān)閉了.如果揮的快,就能夠順利揮完,此時,本端和對端都能正確刪除保存的連接信息.(四次揮手的核心流程)
如果揮的不快,至少也能把第一個FIN發(fā)給對端,至少能告訴對方,我這邊要結(jié)束了.
對端收到FIN之后,對端也要進(jìn)入釋放連接的流程了,返回ACK,并且也發(fā)FIN.這里的FIN不會有ACK了,FIN沒收到ACK時,勢必要進(jìn)行重傳(超時重傳的流程中了).
當(dāng)重傳幾次后,發(fā)現(xiàn)還是不行,還是沒有ACK,這個時候就會單方面釋放連接信息.
其中一方出現(xiàn)了斷電(也算關(guān)機(jī),更突然的關(guān)機(jī)):
(a):斷電的是接收方:發(fā)送方就會突然發(fā)現(xiàn),沒有ACK了,就要重傳.重傳了幾次后,還是不行.
TCP就會嘗試復(fù)位連接.相當(dāng)于清除原來的TCP中的各種臨時數(shù)據(jù)重新開始.
需要利用到TCP的"復(fù)位報文段"(RST). 但此時的RST也不會有ACK.重置了還不行,單方面放棄連接.
(b):斷電的是發(fā)送方:這個情況下,接收方需要區(qū)分出,發(fā)送方是掛了,還是好著暫時沒發(fā).
TCP也是如此,接收方一段時間之后,沒有收到對方的消息,就會觸發(fā)"心跳包"來詢問對方情況
如果對端沒心跳了,此時本端也就會嘗試復(fù)位并且單方面釋放連接了.
TCP/UDP對比
我們說TCP是可靠連接,那么是不是TCP一定優(yōu)于UDP呢?TCP和UDP之間的優(yōu)點(diǎn)和缺點(diǎn),不能簡單,絕對的進(jìn)行比較.
TCP用于可靠傳輸?shù)膱鼍?應(yīng)用于文件傳輸,重要狀態(tài)更新,數(shù)據(jù)包很大的傳輸;(絕大部分場景)
UDP用于高速傳輸和實(shí)時性要求較高的通信領(lǐng)域,例如,早期的QQ,視頻傳輸?shù)?另外UDP可以用于廣播;(對于效率要求很高,但對于可靠性不高).?
歸根結(jié)底,TCP和UDP都是程序員的工具,什么時候用,具體怎么用,還是根據(jù)具體場景判定.
?如何用UDP實(shí)現(xiàn)可靠傳輸?
參考TCP可靠性機(jī)制,在應(yīng)用層實(shí)現(xiàn)類似邏輯.
例如
引入序列號,保證數(shù)據(jù)順序;
引入確認(rèn)應(yīng)答,確保對端收到了數(shù)據(jù);
引入超時重傳,如果隔一段時間沒有應(yīng)答,就重發(fā)數(shù)據(jù).