TCP和網(wǎng)絡(luò)編程相關(guān)問(wèn)題

一. 握手

1. 三次握手的過(guò)程?

客戶端向服務(wù)端發(fā)SYN k,客戶端進(jìn)入SYN_SEND狀態(tài)
服務(wù)端收到后向客戶端發(fā) ACK k+1, SYN j,服務(wù)端進(jìn)入SYN_RECV狀態(tài)。
客戶端向服務(wù)端發(fā)ACK j+1, 客戶端ESTABLISH。
服務(wù)端收到ACK后進(jìn)入ESTABLISH狀態(tài)。
雙向連接建立。

2. SYN攻擊是什么?解決方案?

SYN攻擊:攻擊者偽造大量IP向TCP端口發(fā)送SYN請(qǐng)求,服務(wù)器不停超時(shí)重傳ACK,占用了未完成連接隊(duì)列的大量位置,使正常的連接無(wú)法建立,拖慢服務(wù)器業(yè)務(wù)。
解決方案:SYN cookies,減少超時(shí)時(shí)間等。

3. 為什么是三次握手?

主要是TCP是全雙工通信,二次握手肯定滿足不了要求。
四次握手只是把第二次的ACK和SYN請(qǐng)求分開(kāi),但連接尚未建立,并不需要等待什么,所以合在一起是比較合理的。

二. 揮手

1. 四次揮手的過(guò)程?

客戶端向服務(wù)端發(fā)送FIN??蛻舳诉M(jìn)入FIN_WAIT_1狀態(tài)
服務(wù)端回復(fù)ACK,此時(shí)客戶端側(cè)發(fā)送通道關(guān)閉。服務(wù)端進(jìn)入CLOSE_WAIT狀態(tài),客戶端進(jìn)入FIN_WAIT_2狀態(tài)。
服務(wù)端發(fā)送FIN,此時(shí)服務(wù)端進(jìn)入LAST_ACK狀態(tài)。
客戶端收到FIN,發(fā)送ACK,此時(shí)客戶端進(jìn)入TIME_WAIT狀態(tài),服務(wù)端收到ACK進(jìn)入CLOSED狀態(tài)。
客戶端等待2MSL后,進(jìn)入CLOSED狀態(tài)。

2. 為什么是四次揮手,三次行不行?

三次行不行?三次當(dāng)然不行。
四次揮手和三次揮手的區(qū)別就是中間那次FIN和ACK要不要放在一個(gè)包里發(fā),答案當(dāng)然是否定的。TCP是全雙工連接,服務(wù)端收到FIN后,只是保證不會(huì)再收到來(lái)自客戶端的消息了,因?yàn)榭蛻舳艘P(guān)閉連接了。但是服務(wù)端這邊的發(fā)送緩沖區(qū)可能還有消息沒(méi)有發(fā)出去,要發(fā)送完這些未盡的消息才能發(fā)送FIN,表示服務(wù)端也要關(guān)閉了。
當(dāng)然在實(shí)現(xiàn)中,如果緩沖區(qū)確實(shí)沒(méi)有數(shù)據(jù),服務(wù)器確實(shí)會(huì)立刻發(fā)送FIN+ACK的組合包,看起來(lái)像是三次揮手,但這也只是實(shí)現(xiàn)的優(yōu)化,并不代表協(xié)議設(shè)計(jì)成了這樣。

3. 出現(xiàn)大量TIME_WAIT是為什么?如何解決?

一般服務(wù)端出現(xiàn)大量TIME_WAIT都是HTTP服務(wù)器的東西。這是因?yàn)橐淮蜨TTP請(qǐng)求過(guò)程中,如果是短連接,關(guān)閉連接的都是服務(wù)器,因此可能會(huì)出現(xiàn)比較多的TIME_WAIT。
修改/sbin/sysctl -p的數(shù)值,這是系統(tǒng)中TIME_WAIT的一些參數(shù)。

net.ipv4.tcp_tw_reuse = 1 表示開(kāi)啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認(rèn)為0,表示關(guān)閉;
net.ipv4.tcp_tw_recycle = 1 表示開(kāi)啟TCP連接中TIME-WAIT sockets的快速回收,默認(rèn)為0,表示關(guān)閉;
net.ipv4.tcp_fin_timeout 修改系統(tǒng)默認(rèn)的 TIMEOUT 時(shí)間。
net.ipv4.tcp_max_tw_buckets TIMEWAIT最大連接數(shù)

此處應(yīng)該注意,tcp_tw_recycle選項(xiàng)在公網(wǎng)打開(kāi)會(huì)導(dǎo)致客戶端連接出錯(cuò)率上升,謹(jǐn)慎打開(kāi)。

4. 出現(xiàn)大量CLOSE_WAIT是為什么?如何解決?

服務(wù)端出現(xiàn)大量CLOSE_WAIT狀態(tài)的連接,根據(jù)協(xié)議來(lái)看顯然是服務(wù)端沒(méi)有發(fā)送FIN所致,所以一般都是服務(wù)器代碼寫(xiě)的有問(wèn)題,或者發(fā)送緩沖區(qū)積壓了太多東西,一直沒(méi)有發(fā)完,輪不到發(fā)送FIN,一般也是程序?qū)懙挠袉?wèn)題(對(duì)于服務(wù)端碼農(nóng)而言)。
解決方案:查代碼吧。一般都是沒(méi)有正確的處理連接關(guān)閉所致。

5. TIME_WAIT的作用是什么?

TIME_WAIT被設(shè)計(jì)為等待2MSL,也就是兩倍的包最長(zhǎng)的生命周期,這主要是為了兩個(gè)目的:
(1) 防止迷路的FIN包最后又發(fā)回來(lái),影響正常的連接。
如果不等2MSL,而是等待時(shí)間比較短,那可能一個(gè)對(duì)端發(fā)來(lái)的超時(shí)的FIN包被發(fā)送過(guò)來(lái),此時(shí)可能已經(jīng)建立了一個(gè)新的連接,那么這時(shí)候新的連接也會(huì)受到影響。
(2)可靠地關(guān)閉TCP連接
如果客戶端最后發(fā)送的ACK超時(shí),那么根據(jù)TCP超時(shí)重傳的機(jī)制,服務(wù)端會(huì)重發(fā)FIN,此時(shí)如果客戶端已經(jīng)關(guān)閉連接,那么就會(huì)回復(fù)一個(gè)RST,這樣就會(huì)被認(rèn)為是一個(gè)錯(cuò)誤。為了避免這種情況出現(xiàn),客戶端要等待2MSL,準(zhǔn)備好迎接重發(fā)的FIN(如果有的話)。

6. tcp有了keepalive的機(jī)制,那么應(yīng)用層的心跳機(jī)制是否還需要?

需要。
tcp的keepalive僅能保證tcp連接的正常,并不代表應(yīng)用還是正常的,比如說(shuō)應(yīng)用在某處死鎖了,然而內(nèi)核對(duì)此并不知情,還會(huì)繼續(xù)處理keepalive,這樣看起來(lái)連接還是OK的,但是應(yīng)用已經(jīng)萎了。

三. 擁塞控制,流量控制

擁塞控制是為了解決全網(wǎng)流量不至于太過(guò)擁堵的問(wèn)題,對(duì)自身連接未必是最優(yōu)的。
流量控制就是為了防止一方發(fā)太快,接收方?jīng)]有緩存放。

1.慢啟動(dòng)算法?

發(fā)送方一開(kāi)始便向網(wǎng)絡(luò)發(fā)送多個(gè)報(bào)文段,直至達(dá)到接收方通告的窗口大小為止。當(dāng)發(fā)送方和接收方處于同一個(gè)局域網(wǎng)時(shí),這種方式是可以的。但是如果在發(fā)送方和接收方之間存在多個(gè)路由器和速率較慢的鏈路時(shí),就有可能出現(xiàn)一些問(wèn)題。
一些中間路由器必須緩存分組,并有可能耗盡存儲(chǔ)器的空間。
現(xiàn)在,TCP需要支持一種被稱為“慢啟動(dòng)(slow start)”的算法。該算法通過(guò)觀察到新分組進(jìn)入網(wǎng)絡(luò)的速率應(yīng)該與另一端返回確認(rèn)的速率相同而進(jìn)行工作。
慢啟動(dòng)為發(fā)送方的TCP增加了另一個(gè)窗口:擁塞窗口(congestion window),記為cwnd。當(dāng)與另一個(gè)網(wǎng)絡(luò)的主機(jī)建立TCP連接時(shí),擁塞窗口被初始化為 1個(gè)報(bào)文段(即另一端通告的報(bào)文
段大小)。每收到一個(gè)ACK,擁塞窗口就增加一個(gè)報(bào)文段( cwnd以字節(jié)為單位,但是慢啟動(dòng)以報(bào)文段大小為單位進(jìn)行增加)。發(fā)送方取擁塞窗口與通告窗口中的最小值作為發(fā)送上限。擁
塞窗口是發(fā)送方使用的流量控制,而通告窗口則是接收方使用的流量控制。發(fā)送方開(kāi)始時(shí)發(fā)送一個(gè)報(bào)文段,然后等待 ACK。當(dāng)收到該ACK時(shí),擁塞窗口從1增加為2,即可以發(fā)送兩個(gè)報(bào)文段。當(dāng)收到這兩個(gè)報(bào)文段的ACK時(shí),擁塞窗口就增加為4。這是一種指數(shù)增加的關(guān)系。
總的來(lái)說(shuō),就是指數(shù)增加。那為啥叫慢啟動(dòng)呢,可能是因?yàn)閱?dòng)時(shí)的窗口比較小吧……

2. 擁塞避免算法?

擁塞避免在慢啟動(dòng)增長(zhǎng)到ssthresh后開(kāi)始執(zhí)行,開(kāi)始線性增加發(fā)送窗口長(zhǎng)度,而不再指數(shù)增加,并且一旦發(fā)現(xiàn)有丟包(發(fā)現(xiàn)ACK沒(méi)收到)就要把ssthresh置為發(fā)生擁塞時(shí)接收窗口的一半,然后把cwnd設(shè)為1,重新開(kāi)始一遍慢啟動(dòng)過(guò)程。

3. 快速重傳?

快重傳算法首先要求接收方每收到一個(gè)失序的報(bào)文段后就立即發(fā)出重復(fù)確認(rèn)(為的是使發(fā)送方及早知道有報(bào)文段沒(méi)有到達(dá)對(duì)方)而不要等到自己發(fā)送數(shù)據(jù)時(shí)才進(jìn)行捎帶確認(rèn)。這樣讓發(fā)送方很快能知道發(fā)生了丟包,馬上去重傳丟掉的包。

4. 快速恢復(fù)?

當(dāng)發(fā)送方連續(xù)收到三個(gè)重復(fù)確認(rèn),就執(zhí)行“乘法減小”算法,把慢開(kāi)始門限ssthresh減半。這是為了預(yù)防網(wǎng)絡(luò)發(fā)生擁塞。請(qǐng)注意:接下去不執(zhí)行慢開(kāi)始算法,而是開(kāi)始執(zhí)行擁塞避免算法。
由于發(fā)送方現(xiàn)在認(rèn)為網(wǎng)絡(luò)很可能沒(méi)有發(fā)生擁塞,因此與慢開(kāi)始不同之處是現(xiàn)在不執(zhí)行慢開(kāi)始算法(即擁塞窗口cwnd現(xiàn)在不設(shè)置為1),而是把cwnd值設(shè)置為 慢開(kāi)始門限ssthresh減半后的數(shù)值,然后開(kāi)始執(zhí)行擁塞避免算法(“加法增大”),使擁塞窗口緩慢地線性增大。

5. 流量控制

流量控制就是滑動(dòng)窗口機(jī)制,收發(fā)雙方通過(guò)報(bào)文同步接收緩沖區(qū)長(zhǎng)度,以避免過(guò)快發(fā)送者的出現(xiàn)。
如果控制流量到0窗口,那么就設(shè)置一個(gè)零窗口定時(shí)器,時(shí)間到了就發(fā)一個(gè)窗口探測(cè)報(bào)文來(lái)更新窗口。

6. BBR算法有什么改進(jìn)?

BBR算法的改進(jìn)主要就是對(duì)擁塞避免算法的改進(jìn),主要思路就是通過(guò)計(jì)算時(shí)延帶寬積來(lái)計(jì)算網(wǎng)絡(luò)帶寬,而不是像傳統(tǒng)的擁塞避免算法那樣,一旦出現(xiàn)超時(shí)就認(rèn)為是網(wǎng)絡(luò)擁塞,減少退避窗口。
基本思路就是分別估計(jì)帶寬和延時(shí),交替測(cè)量帶寬和延遲;用一段時(shí)間內(nèi)的帶寬極大值和延遲極小值作為估計(jì)值。
具體可以參考知乎的回答。

三. TCP的那些狀態(tài)

  • CLOSED:初始狀態(tài)。

  • LISTEN:服務(wù)器處于監(jiān)聽(tīng)狀態(tài)。

  • SYN_SEND:客戶端socket執(zhí)行CONNECT連接,發(fā)送SYN包,進(jìn)入此狀態(tài)。

  • SYN_RECV:服務(wù)端收到SYN包并發(fā)送服務(wù)端SYN包,進(jìn)入此狀態(tài)。

  • ESTABLISH:表示連接建立??蛻舳税l(fā)送了最后一個(gè)ACK包后進(jìn)入此狀態(tài),服務(wù)端接收到ACK包后進(jìn)入此狀態(tài)。

  • FIN_WAIT_1:終止連接的一方(通常是客戶機(jī))發(fā)送了FIN報(bào)文后進(jìn)入。等待對(duì)方FIN。

  • CLOSE_WAIT:(假設(shè)服務(wù)器)接收到客戶機(jī)FIN包之后等待關(guān)閉的階段。在接收到對(duì)方的FIN包之后,自然是需要立即回復(fù)ACK包的,表示已經(jīng)知道斷開(kāi)請(qǐng)求。但是本方是否立即斷開(kāi)連接(發(fā)送FIN包)取決于是否還有數(shù)據(jù)需要發(fā)送給客戶端,若有,則在發(fā)送FIN包之前均為此狀態(tài)。

  • FIN_WAIT_2:此時(shí)是半連接狀態(tài),即有一方要求關(guān)閉連接,等待另一方關(guān)閉??蛻舳私邮盏椒?wù)器的ACK包,但并沒(méi)有立即接收到服務(wù)端的FIN包,進(jìn)入FIN_WAIT_2狀態(tài)。

  • LAST_ACK:服務(wù)端發(fā)動(dòng)最后的FIN包,等待最后的客戶端ACK響應(yīng),進(jìn)入此狀態(tài)。

  • TIME_WAIT:客戶端收到服務(wù)端的FIN包,并立即發(fā)出ACK包做最后的確認(rèn),在此之后的2MSL時(shí)間稱為TIME_WAIT狀態(tài)。

四. 網(wǎng)絡(luò)編程

1. IO多路復(fù)用技術(shù)

select和epoll都是IO多路復(fù)用技術(shù)的一種。
先說(shuō)IO多路復(fù)用吧,在傳統(tǒng)的網(wǎng)絡(luò)編程中,socket的readwrite調(diào)用都是同步阻塞的,即調(diào)用之后,如果內(nèi)核的緩沖區(qū)沒(méi)有處理完,當(dāng)前線程就會(huì)被阻塞住,無(wú)法響應(yīng)其他的調(diào)用。這顯然是動(dòng)輒上萬(wàn)連接的服務(wù)器所無(wú)法接受的,于是早期的服務(wù)器都是采用多線程的方式,一個(gè)線程持有一個(gè)socket,來(lái)響應(yīng)連接。這樣顯然也是靠不住的,在連接增加之后,線程調(diào)度本身所占用的CPU資源就變得非常巨大,這是不可忍受的。然后就有了IO多路復(fù)用技術(shù),來(lái)讓單線程也能有效的處理多個(gè)SOCKET的讀寫(xiě)請(qǐng)求。
原理也很簡(jiǎn)單,就是內(nèi)核提供這么一個(gè)接口,你可以告訴他你關(guān)心哪些socket,然后由他輪詢這些socket,一旦其中有一個(gè)或者多個(gè)socket變得可用(可讀或者可寫(xiě)),就返回一個(gè)隊(duì)列,告訴你這些是可以用的,你一個(gè)一個(gè)處理吧。
當(dāng)然這時(shí)候socket還是同步的,如果你不設(shè)置成非阻塞模式,那他也還是會(huì)阻塞你的調(diào)用,但我們不用擔(dān)心:第一是因?yàn)槲覀兛梢暂p易地把socket設(shè)置成非阻塞模式,這樣就算緩沖區(qū)沒(méi)有內(nèi)容他也不會(huì)阻塞我的線程,其次既然是被返回說(shuō)可以用了,那就說(shuō)明還是有點(diǎn)東西的。這樣線程可以一直不停地查詢哪些socket是可以用的,有可以用的就可以處理一下,處理完繼續(xù)監(jiān)聽(tīng);而不會(huì)被扯淡地阻塞在一個(gè)read調(diào)用上脫不開(kāi)身,達(dá)到CPU的高效利用。
select就是這個(gè)機(jī)制的一種實(shí)現(xiàn),它提供了三個(gè)傳入?yún)?shù),可以讓你告訴他哪些socket是你要輪詢的,每個(gè)傳入?yún)?shù)都用一個(gè)FDSET,也就是socketfd的位掩碼來(lái)表示,返回值也是用這個(gè)東西。然后它的內(nèi)部會(huì)把你傳進(jìn)來(lái)的這個(gè)fd放到一個(gè)雙向列表里,接著CPU就會(huì)真的去輪詢這些socketfd的狀態(tài),輪詢完給你返回。

2. epoll的原理,相對(duì)于select的優(yōu)勢(shì),select有沒(méi)有相對(duì)于epoll的優(yōu)勢(shì)場(chǎng)景?

epoll原理簡(jiǎn)述

epoll提供了三個(gè)接口,epoll_create, epoll_ctl, epoll_wait。
epoll_create會(huì)創(chuàng)建一個(gè)epoll的描述符,本質(zhì)也是一個(gè)fd,(你會(huì)發(fā)現(xiàn)linux到處都是fd),表示我要?jiǎng)?chuàng)建一個(gè)集合啦,被加進(jìn)來(lái)的你一個(gè)也跑不了。
epoll_ctl是控制集合成員的增加,刪除和變動(dòng),可以增加一個(gè)要監(jiān)聽(tīng)的fd,也可以刪除。其內(nèi)部是維護(hù)了一顆紅黑樹(shù)來(lái)表示這個(gè)集合,以支持快速的增加、查詢、刪除。如果你要增加一個(gè)fd進(jìn)去,除了這個(gè)fd會(huì)被加到這顆紅黑樹(shù)上之外,還會(huì)把要監(jiān)聽(tīng)的事件放到網(wǎng)絡(luò)棧的回調(diào)中,也就是說(shuō),當(dāng)有讀寫(xiě)事件發(fā)生的時(shí)候,會(huì)主動(dòng)通知到epoll,而不需要CPU像select那樣傻傻的輪詢了。
epoll_wait就是等待相應(yīng)集合中的事件發(fā)生,有事件發(fā)生的話,對(duì)應(yīng)的fd會(huì)被掛到一個(gè)雙向鏈表上,然后掛到作為參數(shù)傳入的數(shù)組中,這個(gè)數(shù)組指針已經(jīng)被mmap過(guò)了,所以又免除了一次復(fù)制。
然后你就可以開(kāi)心的處理這些準(zhǔn)備好的socket了。

epoll相對(duì)于select的優(yōu)勢(shì)

  1. 沒(méi)有select只能支持2048個(gè)文件描述符的限制。
  2. 拆分3個(gè)API,分開(kāi)處理文件描述符集合的增刪改查和等待時(shí)間的過(guò)程,免除了每次調(diào)用都要拷貝一次文件描述符到內(nèi)核中的開(kāi)銷。
  3. 使用紅黑樹(shù)處理文件描述符,比位映射這種線性表結(jié)構(gòu)復(fù)雜度低。
  4. 返回時(shí)的mmap,使得返回事件時(shí)內(nèi)核和用戶空間的拷貝開(kāi)銷也降低了很多。

epoll在哪些場(chǎng)景比select差

  1. 在文件描述符數(shù)量比較少的情況下,紅黑樹(shù)和雙向鏈表這種結(jié)構(gòu)就顯得比較重了。
  2. 在文件描述符活躍概率比較高的情況下,回調(diào)機(jī)制反而可能不如select的輪詢來(lái)的快。

3. epoll ET和LT的區(qū)別?ET模式如何使用?

ET: 邊緣觸發(fā),如果有事件來(lái),僅在由無(wú)到有的瞬間會(huì)通知用戶,如果這次沒(méi)有處理,不會(huì)再額外通知。優(yōu)點(diǎn):效率高;缺點(diǎn):容易丟失這次事件。
LT:水平觸發(fā),只要文件描述符是就緒狀態(tài)就會(huì)一直通知用戶。優(yōu)點(diǎn):容易操作;缺點(diǎn):效率相比ET低一些,但一般也夠用。
ET模式的使用:
為了防止事件丟失,就要把read放在循環(huán)里調(diào)用,就像這樣:

int len = 0;
while(len >= 0 && errno != E_WOULDBLOCK) {
  len = read(fd, buf, len);
}

一直讀到出錯(cuò)為止。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容