日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

tcp的无延时发送_高并发架构的TCP知识介绍

發(fā)布時間:2025/3/15 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 tcp的无延时发送_高并发架构的TCP知识介绍 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這是關(guān)于高并發(fā)架構(gòu)網(wǎng)絡(luò)協(xié)議基礎(chǔ)知識的第二篇,編程路上的基礎(chǔ)心法!

做為一個有追求的程序員,不能只滿足增刪改查,我們要對系統(tǒng)全方面無死角掌控。掌握了這些基本的網(wǎng)絡(luò)知識后,相信一方面日常排錯中會事半功倍,另一方面日常架構(gòu)中不得不考慮的高并發(fā)問題,理解了這些底層協(xié)議也是會如虎添翼。

本文不會單純給大家講講TCP三次握手、四次揮手就完事了。如果只是哪樣的話,我直接貼幾個連接就完事了。我希望把實際工作中的很多點能夠串起來講給大家。當然為了文章完整,我依然會從?三次握手?起頭。

再說TCP狀態(tài)變更過程

不管是三次握手、還是四次揮手,他們都是完成了TCP不同狀態(tài)的切換。進而影響各種數(shù)據(jù)的傳輸情況。下面從三次握手開始分析。

本文圖片有部分來自網(wǎng)絡(luò),若有侵權(quán),告知即焚

三次握手

來看看三次握手的圖,估計大家看這圖都快看吐了,不過為什么每次面試、回憶的時候還是想不起呢?我再來抄抄這鍋剩飯吧

首先當服務(wù)端處于?listen?狀態(tài)的時候,我們就可以再客戶端發(fā)起監(jiān)聽了,此時客戶端會處于?SYN_SENT?狀態(tài)。服務(wù)端收到這個消息會返回一個?SYN?并且同時?ACK?客戶端的請求,之后服務(wù)端便處于?SYN_RCVD?狀態(tài)。這個時候客戶端收到了服務(wù)端的?SYN&ACK,就會發(fā)送對服務(wù)端的?ACK,之后便處于?ESTABLISHED?狀態(tài)。服務(wù)端收到了對自己的?ACK?后也會處于?ESTABLISHED?狀態(tài)。

經(jīng)常在面試中可能有人提問:為什么握手要3次,不是2次或者4次呢?

首先說4次握手,其實為了保證可靠性,這個握手次數(shù)可以一直循環(huán)下去;但是這沒有一個終止就沒有意義了。所以3次,保證了各方消息有來有回就足夠了。當然這里可能有一種情況是,客戶端發(fā)送的?ACK?在網(wǎng)絡(luò)中被丟了。那怎么辦?

  • 其實大部分時候,我們連接建立完成就會立刻發(fā)送數(shù)據(jù),所以如果服務(wù)端沒有收到?ACK?沒關(guān)系,當收到數(shù)據(jù)就會認為連接已經(jīng)建立;

  • 如果連接建立后不立馬傳輸數(shù)據(jù),那么服務(wù)端認為連接沒有建立成功會周期性重發(fā)?SYN&ACK?直到客戶端確認成功。

  • 再說為什么2次握手不行呢?2次握手我們可以想象是沒有三次握手最后的?ACK, 在實際中確實會出現(xiàn)客戶端發(fā)送?ACK?服務(wù)端沒有收到的情況(上面的情況一),那么這是否說明兩次握手也是可行的呢?
    看下情況二,2次握手當服務(wù)端發(fā)送消息后,就認為建立成功,而恰巧此時又沒有數(shù)據(jù)傳輸。這就會帶來一種資源浪費的情況。比如:客戶端可能由于延時發(fā)送了多個連接情況,當服務(wù)端每收到一個請求回復(fù)后就認為連接建立成功,但是這其中很多求情都是延時產(chǎn)生的重復(fù)連接,浪費了很多寶貴的資源。

    因此綜上所述,從資源節(jié)省、效率3次握手都是最合適的。話又回來三次握手的真實意義其實就是協(xié)商傳輸數(shù)據(jù)用的:序列號與窗口大小

    下面我們通過抓包再來看一下真實的情況是否如上所述。

    12320:33:26.583598 IP 192.168.0.102.58165 > 103.235.46.39.80: Flags [S], seq 621839080, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1050275400 ecr 0,sackOK,eol], length 020:33:26.660754 IP 103.235.46.39.80 > 192.168.0.102.58165: Flags [S.], seq 1754967387, ack 621839081, win 8192, options [mss 1452,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 020:33:26.660819 IP 192.168.0.102.58165 > 103.235.46.39.80: Flags [.], ack 1754967388, win 4096, length 0

    抓包:?sudo tcpdump -n host www.baidu.com -S

    • S?表示 SYN

    • .?表示 ACK

    • P?表示 傳輸數(shù)據(jù)

    • F?表示 FIN

    四次揮手

    揮手,就是說數(shù)據(jù)傳完了,同志們再見!

    這里有個問題需要注意下,其實客戶端、服務(wù)端都能夠主動發(fā)起關(guān)閉操作,誰調(diào)用?close()?就先發(fā)送關(guān)閉的請求。當然一般的流程,發(fā)起建立連接的一方會主動發(fā)起關(guān)閉請求(http中)。

    關(guān)于4次揮手的過程,我就不多解釋了,這里有兩個重要的狀態(tài)我需要解釋下,這都是我親自經(jīng)歷過的線上故障,close_wait?與?time_wait

    先給大家一個命令,統(tǒng)計tcp的各種狀態(tài)情況。下面表格內(nèi)容就來自這個命令的統(tǒng)計。

    netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’

    Tcp狀態(tài)連接數(shù)
    CLOSE_WAIT505
    ESTABLISHED808
    TIME_WAIT3481
    SYN_SENT1
    SYN_RECV1
    LAST_ACK2
    FIN_WAIT22
    FIN_WAIT11


    大量的CLOSE_WAIT
    ?這個在我之前的一篇文章?線上大量CLOSE_WAIT原因分析?已經(jīng)有過介紹,它會導(dǎo)致大量的socket無法釋放。而每個socket都是一個文件,是會占用資源的。這個問題主要是代碼問題。它出現(xiàn)在被動關(guān)閉的一方(習(xí)慣稱為server)。

    大量的TIME_WAIT?這個問題在日常中經(jīng)常看到,流量一高就出現(xiàn)大量的該情況。該狀態(tài)出現(xiàn)在主動發(fā)起關(guān)閉的一方。該狀態(tài)一般等待的時間設(shè)為 2MSL后自動關(guān)閉,MSL是Maximum Segment Lifetime,報文最大生存時間,如果報文超過這個時間,就會被丟棄。處于該狀態(tài)下的socket也是不能被回收使用的。線上我就遇到這種情況,每次大流量的時候,每臺機器處于該狀態(tài)的socket就多達10w+,遠遠比處于?Established?狀態(tài)的socket多的多,導(dǎo)致很多時候服務(wù)響應(yīng)能力下降。這個一方面可以通過調(diào)整內(nèi)核參數(shù)處理,另一方面避免使用太多的短鏈接,可以采用連接池來提升性能。另外在代碼層面可能是由于某些地方?jīng)]有關(guān)閉連接導(dǎo)致的,也需要檢查業(yè)務(wù)代碼。

    上面兩個狀態(tài)一定要牢記發(fā)生在哪一方,這方便我們快速定位問題。

    最后這里還是放上揮手時的抓包數(shù)據(jù):

    123420:33:26.750607 IP 192.168.0.102.58165 > 103.235.46.39.80: Flags [F.], seq 621839159, ack 1754967720, win 4096, length 020:33:26.827472 IP 103.235.46.39.80 > 192.168.0.102.58165: Flags [.], ack 621839160, win 776, length 020:33:26.827677 IP 103.235.46.39.80 > 192.168.0.102.58165: Flags [F.], seq 1754967720, ack 621839160, win 776, length 020:33:26.827729 IP 192.168.0.102.58165 > 103.235.46.39.80: Flags [.], ack 1754967721, win 4096, length 0

    不多不少,剛好4次。

    TCP狀態(tài)變更

    網(wǎng)絡(luò)上有一張TCP狀態(tài)機的圖,我覺得太復(fù)雜了,用自己的方式搞個簡單點的容易理解的。我從兩個角度來說明狀態(tài)的變更。

    • 一個是客戶端

    • 一個是服務(wù)端

    看下面兩張圖的時候,請一定結(jié)合上面三次握手、四次揮手的時序圖一起看,加深理解。

    客戶端狀態(tài)變更

    通過這張圖,大家是否能夠清晰明了的知道 TCP 在客戶端上的變更情況了呢?

    服務(wù)端狀態(tài)變更

    這一張圖描述了 TCP 狀態(tài)在服務(wù)端的變遷。

    TCP的流量控制與擁塞控制

    我們常說TCP是面向連接的,UDP是無連接的。那么TCP這個面向連接主要解決的是什么問題呢?

    這里繼續(xù)把三次握手的抓包數(shù)據(jù)貼出來分析下:

    12320:33:26.583598 IP 192.168.0.102.58165 > 103.235.46.39.80: Flags [S], seq 621839080, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1050275400 ecr 0,sackOK,eol], length 020:33:26.660754 IP 103.235.46.39.80 > 192.168.0.102.58165: Flags [S.], seq 1754967387, ack 621839081, win 8192, options [mss 1452,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 020:33:26.660819 IP 192.168.0.102.58165 > 103.235.46.39.80: Flags [.], ack 1754967388, win 4096, length 0

    上面我們說到?TCP?的三次握手最重要的就是協(xié)商傳輸數(shù)據(jù)用的序列號。那這個序列號究竟有些什么用呢?這個序號能夠幫助后續(xù)兩端進行確認數(shù)據(jù)包是否收到,解決順序、丟包問題;另外我們還可以看到有一個?win?字段,這是雙方交流的窗口大小,這在每次傳輸數(shù)據(jù)過程中也會攜帶。主要是告訴對方,我窗口是這么大,別發(fā)多了或者別發(fā)太少。

    總結(jié)下,TCP的幾個特點是:

    • 順序問題,依靠序號

    • 丟包問題,依靠序號

    • 流量控制,依靠滑動窗口

    • 擁塞控制,依靠擁塞窗口+滑動窗口

    • 連接維護,三次握手/四次揮手

    順序與丟包問題

    這個問題其實應(yīng)該很好理解。由于數(shù)據(jù)在傳輸前我們已經(jīng)有序號了,這里注意一下這個序號是隨機的,重復(fù)的概率極地,避免了程序發(fā)生亂入的可能性。

    由于我們每個數(shù)據(jù)包有序號,雖然發(fā)送與到達可能不是順序的,但是TCP層收到數(shù)據(jù)后,可以根據(jù)序號進行重新排列;另外在這個排列過程中,發(fā)現(xiàn)有了1,2,3,5,6這幾個包,一檢查就知道4要么延時未到達,要么丟包了,等待重傳。

    這里需要重要說明的一點是。為了提升效率,TCP其實并不是收到一個包就發(fā)一個ack。那是如何ACK的呢?還是以上面為例,TCP收到了1,2,3,5,6這幾個包,它可能會發(fā)送一個?ack ,seq=3?的確認包,這樣次一次確認了3個包。但是它不會發(fā)送 5,6 的ack。因為4沒有收到啊!一旦4延時到達或者重發(fā)到達,就會發(fā)送一個?ack, seq=6,又一次確認了3個包。

    流量控制與擁塞控制

    這兩個概念說實話,讓我理解了挺長時間,主要是對它們各自控制的內(nèi)容以及相互之間是否有作用一直沒有鬧清楚。

    先大概說下:

    • 流量控制:是根據(jù)接收方的窗口大小來感知我這次能夠傳多少數(shù)據(jù)給對方;—— 滑動窗口

    • 擁塞控制:而擁塞控制主要是避免網(wǎng)絡(luò)擁塞,它考慮的問題更多。根據(jù)綜合因素來覺得發(fā)多少數(shù)據(jù)給對方;—— 滑動窗口&擁塞窗口

    舉個例子說下,比如:A給B發(fā)送數(shù)據(jù),通過握手后,A知道B一次可以收1000的數(shù)據(jù)(B有這么大的處理能力),那么這個時候滑動窗口就可以設(shè)置成1000。那是不是最后真的可以一次發(fā)這么多數(shù)據(jù)給B呢?還不是,這時候得問問擁塞窗口,老兄,現(xiàn)在網(wǎng)絡(luò)情況怎么樣?一次運1000的數(shù)據(jù)有壓力嗎?擁塞窗口一通計算說不行,現(xiàn)在是高峰期,最多只能有600的貨上路。最終這次傳數(shù)據(jù)的時候就是 600 的標注。大家也可以關(guān)注抓包數(shù)據(jù)的?win?值,一直在動態(tài)調(diào)整。

    當然另外一種情況是滑動窗口比擁塞窗口小,雖然運輸能力強,但是接收能力有限,這時候就要取滑動窗口的值來實際發(fā)生。所以它們二者之間是有關(guān)系的。

    所以具體到每次能夠發(fā)送多少數(shù)據(jù),有這么一個公式:

    LastByteSend - LastByteAcked <= min{cwnd,rwnd}

    • LastByteSend 是最后一個發(fā)送的字節(jié)的序號

    • LastByteAcked 最后一個被確認的字節(jié)的序號

    這兩個相減得到的是本次能夠發(fā)送的數(shù)據(jù),這個數(shù)據(jù)一定小于或等于 cwnd 與 rwnd 中最小的一個值。相信大家能夠理清楚。

    那么這部分知識對于實際工作中有什么作用呢?指導(dǎo)意義就是:如果你的業(yè)務(wù)很重要、很核心一定不要混布;二是如果你的服務(wù)忽快忽慢,而確信依賴服務(wù)沒有問題,檢查下機器對應(yīng)的網(wǎng)絡(luò)情況;三是窗口這個速度控制機制,在我們進行服務(wù)設(shè)計的時候,非常具有參考意義。是不是有點消息隊列的感覺?(很多消息隊列都是勻速的,我們是否可以加一個窗口的概念來進行優(yōu)化呢?)

    是什么限制了你的連接

    到了最關(guān)鍵的地方了,精華我都是留到最后講。下面放一張網(wǎng)上找的socket操作步驟圖,畫的太好了我就直接用了。

    我們假設(shè)我的服務(wù)端就是?Nginx?,我來嘗試解讀一下。當客戶端調(diào)用?connect()?時候就會發(fā)起三次握手,這次握手的時候有幾個元素唯一確定了這次通信(或者說這個socket),[源IP:源Port, 目的IP:目的Port]?,當然這個socket還不是最終用來傳輸數(shù)據(jù)的socket,一旦握手完成后,服務(wù)端會在返回一個?socket?專門用來后續(xù)的數(shù)據(jù)傳輸。這里暫且把第一個socket叫?監(jiān)聽socket,第二個叫?傳輸socket?方便后文敘述。

    為什么要這么設(shè)計呢?大家想一想,如果監(jiān)聽的socket還要負責數(shù)據(jù)的收發(fā),請問這個服務(wù)端的效率如何提升?什么東西、誰都往這個socket里邊丟,太復(fù)雜!

    提高連接常用套路

    到了這一步,我們現(xiàn)在先停下來算算自己的服務(wù)器機器能夠有多少連接呢?這個極限又是如何一步步被突破呢?

    先說?監(jiān)聽socket?,服務(wù)器的prot一般都是固定的,服務(wù)器的ip當然也是固定的(單機)。那么上面的結(jié)構(gòu)?[源IP:源Port, 目的IP:目的Port]?其實只有客戶端的ip與端口可以發(fā)生變化。假設(shè)客戶端用的是IPv4,那么理論連接數(shù)是:2^32(ip數(shù)) * 2^16(端口數(shù)) = 2^48。

    看起來這個值蠻大的。但是真的能夠有這么多連接嗎?不可能的,因為每一個socket都需要消耗內(nèi)存;以及每一個進程的文件描述符是有上限的。這些都限制了最終的連接數(shù)。

    那么如何進行調(diào)和呢?我知道的操作有:多進程、多線程、IO多路服用、協(xié)程等手段組合使用。

    多進程

    也就是監(jiān)聽是一個進程,一旦accept后,對于?傳輸socket?我們就fork一個新的子進程來處理。但是這種方式太重,fork一個進程、銷毀一個進程都是特別費事的。單機對進程的創(chuàng)建上限也是有限制的。

    多線程

    線程比進程要輕量級的多,它會共享父進程的很多資源,比如:文件描述符、進程空間,它就是多了一個引用。因此它的創(chuàng)建、銷毀更加容易。每一個?傳輸socket?在這里就交給了線程來處理。

    但是不管是多進程、還是多線程都存在一個問題,一個連接對應(yīng)一個進程或者協(xié)程。這都很難逃脫?C10K?的問題。那么該怎么辦呢?

    IO多路復(fù)用

    IO多路復(fù)用是什么意思呢?在上面單純的多進程、多線程模型中,一個進程或線程只能處理一個連接。用了IO多路復(fù)用后,我一個進程或線程就能處理多個連接。

    我們都知道?Nginx?非常高效,它的結(jié)構(gòu)是:master + worker,worker 會在 80、443端口上來監(jiān)聽請求。它的worker一般設(shè)置為 cpu 的cores數(shù),那么這么少的子進程是如何解決超多連接的呢?這里其實每個worker就采用了 epoll 模型(當然IO多路復(fù)用還有個select,這里就不說了)。

    處于監(jiān)聽狀態(tài)的worker,會把所有?監(jiān)聽socket?加入到自己的epoll中。當這些socket都在epoll中時,如果某個socket有事件發(fā)生就會立即被回調(diào)喚醒(這涉及epoll的紅黑樹,講不清楚不細說了)。這種模式,大大增加了每個進程可以管理的socket數(shù)量,上限直接可以上升到進程能夠操作的最大文件描述符。

    一般機器可以設(shè)置百萬級別文件描述符,所以單機單進程就是百萬連接,epoll是解決C10K的利器,很多開源軟件用到了它。

    這里說下,并不是所有的worker都是同時處于監(jiān)聽端口的狀態(tài),這涉及到nginx驚群、搶自旋鎖的問題,不再本文范圍內(nèi)不多說。

    關(guān)于ulimit

    在文章的最后,補充一些單機文件描述符設(shè)置的問題。我們常說連接數(shù)受限于文件描述符,這是為什么?

    因為在linux上一切皆文件,故每一個socket都是被當作一個文件看待,那么每個文件就會有一個文件描述符。在linux中每一個進程中都有一個數(shù)組保存了該進程需要的所有文件描述符。這個文件描述符其實就是這個數(shù)組的?key?,它的?value?是一個指針,指向的就是打開的對應(yīng)文件。

    關(guān)于文件描述符有兩點注意:

  • 它對應(yīng)的其實是一個linux上的文件

  • 文件描述符本身這個值在不同進程中是可以重復(fù)的

  • 另外補充一點,單機設(shè)置的ulimit的上線受限與系統(tǒng)的兩個配置:

    fs.nr_open,進程級別

    fs.file-max,系統(tǒng)級別

    fs.nr_open 總是應(yīng)該小于等于 fs.file-max,這兩個值的設(shè)置也不是隨意可以操作,因為設(shè)置的越大,系統(tǒng)資源消耗越多,所以需要根據(jù)真實情況來進行設(shè)置。


    至此,本篇長文就完結(jié)了。這跟上篇?高并發(fā)架構(gòu)的CDN知識介紹?屬于一個系列,高并發(fā)架構(gòu)需要理解的網(wǎng)絡(luò)基礎(chǔ)知識。

    后面還會寫一下 HTTP/HTTPS 的知識。然后關(guān)于高并發(fā)網(wǎng)絡(luò)相關(guān)的東西就算完結(jié)。我會開啟下一個篇章。


    如果你想對網(wǎng)絡(luò)協(xié)議了解更多,推薦一個課程:

    總結(jié)

    以上是生活随笔為你收集整理的tcp的无延时发送_高并发架构的TCP知识介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。