一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

您好,歡迎來電子發(fā)燒友網(wǎng)! ,新用戶?[免費注冊]

您的位置:電子發(fā)燒友網(wǎng)>電子百科>通信技術(shù)>數(shù)據(jù)通信>

TCP一定能保證數(shù)據(jù)不丟失嗎?

2022年08月05日 10:21 小林coding 作者:小林coding 用戶評論(0

?

?

TCP 是一個可靠的傳輸協(xié)議,那它一定能保證數(shù)據(jù)不丟失嗎?

這次,就跟大家探討這個問題。

數(shù)據(jù)包的發(fā)送流程

首先,我們兩個手機的綠皮聊天軟件客戶端,要通信,中間會通過它們家服務器。大概長這樣。

e8a4c5cc-1464-11ed-ba43-dac502259ad0.png聊天軟件三端通信

但為了簡化模型,我們把中間的服務器給省略掉,假設這是個端到端的通信。且為了保證消息的可靠性,我們盲猜它們之間用的是TCP協(xié)議進行通信。

e8d31df0-1464-11ed-ba43-dac502259ad0.png聊天軟件兩端通信

為了發(fā)送數(shù)據(jù)包,兩端首先會通過三次握手,建立TCP連接。

一個數(shù)據(jù)包,從聊天框里發(fā)出,消息會從聊天軟件所在的用戶空間拷貝到內(nèi)核空間發(fā)送緩沖區(qū)(send buffer),數(shù)據(jù)包就這樣順著傳輸層、網(wǎng)絡層,進入到數(shù)據(jù)鏈路層,在這里數(shù)據(jù)包會經(jīng)過流控(qdisc),再通過RingBuffer發(fā)到物理層的網(wǎng)卡。數(shù)據(jù)就這樣順著網(wǎng)卡發(fā)到了紛繁復雜的網(wǎng)絡世界里。這里頭數(shù)據(jù)會經(jīng)過n多個路由器和交換機之間的跳轉(zhuǎn),最后到達目的機器的網(wǎng)卡處。

此時目的機器的網(wǎng)卡會通知DMA將數(shù)據(jù)包信息放到RingBuffer中,再觸發(fā)一個硬中斷CPU,CPU觸發(fā)軟中斷ksoftirqdRingBuffer收包,于是一個數(shù)據(jù)包就這樣順著物理層,數(shù)據(jù)鏈路層,網(wǎng)絡層,傳輸層,最后從內(nèi)核空間拷貝到用戶空間里的聊天軟件里。

e8e57ebe-1464-11ed-ba43-dac502259ad0.png網(wǎng)絡發(fā)包收包全景圖

畫了那么大一張圖,只水了200字做解釋,我多少是有些心痛的。

到這里,拋開一些細節(jié),大家大概知道了一個數(shù)據(jù)包從發(fā)送到接收的宏觀過程。

可以看到,這上面全是密密麻麻的名詞。

整條鏈路下來,有不少地方可能會發(fā)生丟包。

但為了不讓大家保持蹲姿太久影響身體健康,我這邊只重點講下幾個常見容易發(fā)生丟包的場景。

建立連接時丟包

TCP協(xié)議會通過三次握手建立連接。大概長下面這樣。

e90b4e6e-1464-11ed-ba43-dac502259ad0.pngTCP三次握手

在服務端,第一次握手之后,會先建立個半連接,然后再發(fā)出第二次握手。這時候需要有個地方可以暫存這些半連接。這個地方就叫半連接隊列。

如果之后第三次握手來了,半連接就會升級為全連接,然后暫存到另外一個叫全連接隊列的地方,坐等程序執(zhí)行accept()方法將其取走使用。

e917b82a-1464-11ed-ba43-dac502259ad0.png半連接隊列和全連接隊列

是隊列就有長度,有長度就有可能會滿,如果它們滿了,那新來的包就會被丟棄。

可以通過下面的方式查看是否存在這種丟包行為。

#?全連接隊列溢出次數(shù)
#?netstat?-s?|?grep?overflowed
????4343?times?the?listen?queue?of?a?socket?overflowed

#?半連接隊列溢出次數(shù)
#?netstat?-s?|?grep?-i?"SYNs?to?LISTEN?sockets?dropped"
????109?times?the?listen?queue?of?a?socket?overflowed?

從現(xiàn)象來看就是連接建立失敗。

e9241a52-1464-11ed-ba43-dac502259ad0.png

流量控制丟包

應用層能發(fā)網(wǎng)絡數(shù)據(jù)包的軟件有那么多,如果所有數(shù)據(jù)不加控制一股腦沖入到網(wǎng)卡,網(wǎng)卡會吃不消,那怎么辦?讓數(shù)據(jù)按一定的規(guī)則排個隊依次處理,也就是所謂的qdisc(Queueing Disciplines,排隊規(guī)則),這也是我們常說的流量控制機制。

排隊,得先有個隊列,而隊列有個長度

我們可以通過下面的ifconfig命令查看到,里面涉及到的txqueuelen后面的數(shù)字1000,其實就是流控隊列的長度。

當發(fā)送數(shù)據(jù)過快,流控隊列長度txqueuelen又不夠大時,就容易出現(xiàn)丟包現(xiàn)象。

e92e9388-1464-11ed-ba43-dac502259ad0.pngqdisc丟包

可以通過下面的ifconfig命令,查看TX下的dropped字段,當它大于0時,則有可能是發(fā)生了流控丟包。

#?ifconfig?eth0
eth0:?flags=4163<UP,BROADCAST,RUNNING,MULTICAST>??mtu?1500
????????inet?172.21.66.69??netmask?255.255.240.0??broadcast?172.21.79.255
????????inet6?fe80:3eff269f??prefixlen?64??scopeid?0x20<link>
????????ether?003e26:9f??txqueuelen?1000??(Ethernet)
????????RX?packets?6962682??bytes?1119047079?(1.0?GiB)
????????RX?errors?0??dropped?0??overruns?0??frame?0
????????TX?packets?9688919??bytes?2072511384?(1.9?GiB)
????????TX?errors?0??dropped?0?overruns?0??carrier?0??collisions?0

當遇到這種情況時,我們可以嘗試修改下流控隊列的長度。比如像下面這樣將eth0網(wǎng)卡的流控隊列長度從1000提升為1500.

#?ifconfig?eth0?txqueuelen?1500

網(wǎng)卡丟包

網(wǎng)卡和它的驅(qū)動導致丟包的場景也比較常見,原因很多,比如網(wǎng)線質(zhì)量差,接觸不良。除此之外,我們來聊幾個常見的場景。

RingBuffer過小導致丟包

上面提到,在接收數(shù)據(jù)時,會將數(shù)據(jù)暫存到RingBuffer接收緩沖區(qū)中,然后等著內(nèi)核觸發(fā)軟中斷慢慢收走。如果這個緩沖區(qū)過小,而這時候發(fā)送的數(shù)據(jù)又過快,就有可能發(fā)生溢出,此時也會產(chǎn)生丟包。

e94001cc-1464-11ed-ba43-dac502259ad0.pngRingBuffer滿了導致丟包

我們可以通過下面的命令去查看是否發(fā)生過這樣的事情。

#?ifconfig
eth0:??RX?errors?0??dropped?0??overruns?0??frame?0

查看上面的overruns指標,它記錄了由于RingBuffer長度不足導致的溢出次數(shù)。

當然,用ethtool命令也能查看。

#?ethtool?-S?eth0|grep?rx_queue_0_drops

但這里需要注意的是,因為一個網(wǎng)卡里是可以有多個RingBuffer的,所以上面的rx_queue_0_drops里的0代表的是第0個RingBuffer的丟包數(shù),對于多隊列的網(wǎng)卡,這個0還可以改成其他數(shù)字。但我的家庭條件不允許我看其他隊列的丟包數(shù),所以上面的命令對我來說是夠用了。。。

當發(fā)現(xiàn)有這類型丟包的時候,可以通過下面的命令查看當前網(wǎng)卡的配置。

#ethtool?-g?eth0
Ring?parameters?for?eth0:
Pre-set?maximums:
RX:????????4096
RX?Mini:????0
RX?Jumbo:????0
TX:????????4096
Current?hardware?settings:
RX:????????1024
RX?Mini:????0
RX?Jumbo:????0
TX:????????1024

上面的輸出內(nèi)容,含義是RingBuffer最大支持4096的長度,但現(xiàn)在實際只用了1024。

想要修改這個長度可以執(zhí)行ethtool -G eth1 rx 4096 tx 4096將發(fā)送和接收RingBuffer的長度都改為4096。

RingBuffer增大之后,可以減少因為容量小而導致的丟包情況。

網(wǎng)卡性能不足

網(wǎng)卡作為硬件傳輸速度是有上限的。當網(wǎng)絡傳輸速度過大,達到網(wǎng)卡上限時,就會發(fā)生丟包。這種情況一般常見于壓測場景。

我們可以通過ethtool加網(wǎng)卡名,獲得當前網(wǎng)卡支持的最大速度。

#?ethtool?eth0
Settings?for?eth0:
????Speed:?10000Mb/s

可以看到,我這邊用的網(wǎng)卡能支持的最大傳輸速度speed=1000Mb/s。

也就是俗稱的千兆網(wǎng)卡,但注意這里的單位是Mb,這里的b是指bit,而不是Byte。1Byte=8bit。所以10000Mb/s還要除以8,也就是理論上網(wǎng)卡最大傳輸速度是1000/8 = 125MB/s。

我們可以通過sar命令從網(wǎng)絡接口層面來分析數(shù)據(jù)包的收發(fā)情況。

#?sar?-n?DEV?1
Linux?3.10.0-1127.19.1.el7.x86_64??????2022年07月27日?????_x86_64_????(1?CPU)

08時35分39秒?????IFACE???rxpck/s???txpck/s????rxkB/s????txkB/s????rxcmp/s???txcmp/s??rxmcst/s
08時35分40秒??????eth0??????6.06??????4.04??????0.35????121682.33???0.00????0.00?????0.00

其中 txkB/s是指當前每秒發(fā)送的字節(jié)(byte)總數(shù),rxkB/s是指每秒接收的字節(jié)(byte)總數(shù)。

當兩者加起來的值約等于12~13w字節(jié)的時候,也就對應大概125MB/s的傳輸速度。此時達到網(wǎng)卡性能極限,就會開始丟包。

遇到這個問題,優(yōu)先看下你的服務是不是真有這么大的真實流量,如果是的話可以考慮下拆分服務,或者就忍痛充錢升級下配置吧。

接收緩沖區(qū)丟包

我們一般使用TCP socket進行網(wǎng)絡編程的時候,內(nèi)核都會分配一個發(fā)送緩沖區(qū)和一個接收緩沖區(qū)

當我們想要發(fā)一個數(shù)據(jù)包,會在代碼里執(zhí)行send(msg),這時候數(shù)據(jù)包并不是一把梭直接就走網(wǎng)卡飛出去的。而是將數(shù)據(jù)拷貝到內(nèi)核發(fā)送緩沖區(qū)就完事返回了,至于什么時候發(fā)數(shù)據(jù),發(fā)多少數(shù)據(jù),這個后續(xù)由內(nèi)核自己做決定。

e9593bce-1464-11ed-ba43-dac502259ad0.pngtcp_sendmsg邏輯

接收緩沖區(qū)作用也類似,從外部網(wǎng)絡收到的數(shù)據(jù)包就暫存在這個地方,然后坐等用戶空間的應用程序?qū)?shù)據(jù)包取走。

這兩個緩沖區(qū)是有大小限制的,可以通過下面的命令去查看。

#?查看接收緩沖區(qū)
#?sysctl?net.ipv4.tcp_rmem
net.ipv4.tcp_rmem?=?4096????87380???6291456

#?查看發(fā)送緩沖區(qū)
#?sysctl?net.ipv4.tcp_wmem
net.ipv4.tcp_wmem?=?4096????16384???4194304

不管是接收緩沖區(qū)還是發(fā)送緩沖區(qū),都能看到三個數(shù)值,分別對應緩沖區(qū)的最小值,默認值和最大值 (min、default、max)。緩沖區(qū)會在min和max之間動態(tài)調(diào)整。

那么問題來了,如果緩沖區(qū)設置過小會怎么樣?

對于發(fā)送緩沖區(qū),執(zhí)行send的時候,如果是阻塞調(diào)用,那就會等,等到緩沖區(qū)有空位可以發(fā)數(shù)據(jù)。

e9677dc4-1464-11ed-ba43-dac502259ad0.gifsend阻塞

如果是非阻塞調(diào)用,就會立刻返回一個 EAGAIN 錯誤信息,意思是 ?Try again。讓應用程序下次再重試。這種情況下一般不會發(fā)生丟包。

e9784e9c-1464-11ed-ba43-dac502259ad0.gifsend非阻塞

當接受緩沖區(qū)滿了,事情就不一樣了,它的TCP接收窗口會變?yōu)?,也就是所謂的零窗口,并且會通過數(shù)據(jù)包里的win=0,告訴發(fā)送端,"球球了,頂不住了,別發(fā)了"。一般這種情況下,發(fā)送端就該停止發(fā)消息了,但如果這時候確實還有數(shù)據(jù)發(fā)來,就會發(fā)生丟包

e989cffa-1464-11ed-ba43-dac502259ad0.pngrecv_buffer丟包

我們可以通過下面的命令里的TCPRcvQDrop查看到有沒有發(fā)生過這種丟包現(xiàn)象。

cat?/proc/net/netstat
TcpExt:?SyncookiesSent?TCPRcvQDrop?SyncookiesFailed
TcpExt:?0??????????????157??????????????60116

但是說個傷心的事情,我們一般也看不到這個TCPRcvQDrop,因為這個是5.9版本里引入的打點,而我們的服務器用的一般是2.x~3.x左右版本。你可以通過下面的命令查看下你用的是什么版本的linux內(nèi)核。

#?cat?/proc/version
Linux?version?3.10.0-1127.19.1.el7.x86_64

兩端之間的網(wǎng)絡丟包

前面提到的是兩端機器內(nèi)部的網(wǎng)絡丟包,除此之外,兩端之間那么長的一條鏈路都屬于外部網(wǎng)絡,這中間有各種路由器和交換機還有光纜啥的,丟包也是很經(jīng)常發(fā)生的。

這些丟包行為發(fā)生在中間鏈路的某些個機器上,我們當然是沒權(quán)限去登錄這些機器。但我們可以通過一些命令觀察整個鏈路的連通情況。

ping命令查看丟包

比如我們知道目的地的域名是 baidu.com。想知道你的機器到baidu服務器之間,有沒有產(chǎn)生丟包行為??梢允褂胮ing命令。

e99588d6-1464-11ed-ba43-dac502259ad0.pngping查看丟包

倒數(shù)第二行里有個100% packet loss,意思是丟包率100%。

但這樣其實你只能知道你的機器和目的機器之間有沒有丟包。

那如果你想知道你和目的機器之間的這條鏈路,哪個節(jié)點丟包了,有沒有辦法呢?

有。

mtr命令

mtr命令可以查看到你的機器和目的機器之間的每個節(jié)點的丟包情況。

像下面這樣執(zhí)行命令。

e9a4c13e-1464-11ed-ba43-dac502259ad0.pngmtr_icmp

其中 -r 是指report,以報告的形式打印結(jié)果。

可以看到Host那一列,出現(xiàn)的都是鏈路中間每一跳的機器,Loss的那一列就是指這一跳對應的丟包率。

需要注意的是,中間有一些是host是???,那個是因為mtr默認用的是ICMP包,有些節(jié)點限制了ICMP包,導致不能正常展示。

我們可以在mtr命令里加個-u,也就是使用udp包,就能看到部分???對應的IP。

e9b5111a-1464-11ed-ba43-dac502259ad0.pngmtr-udp

ICMP包和UDP包的結(jié)果拼在一起看,就是比較完整的鏈路圖了。

還有個小細節(jié),Loss那一列,我們在icmp的場景下,關(guān)注最后一行,如果是0%,那不管前面loss是100%還是80%都無所謂,那些都是節(jié)點限制導致的虛報。

但如果最后一行是20%,再往前幾行都是20%左右,那說明丟包就是從最接近的那一行開始產(chǎn)生的,長時間是這樣,那很可能這一跳出了點問題。如果是公司內(nèi)網(wǎng)的話,你可以帶著這條線索去找對應的網(wǎng)絡同事。如果是外網(wǎng)的話,那耐心點等等吧,別人家的開發(fā)會比你更著急。

e9c8499c-1464-11ed-ba43-dac502259ad0.png

發(fā)生丟包了怎么辦

說了這么多。只是想告訴大家,丟包是很常見的,幾乎不可避免的一件事情。

但問題來了,發(fā)生丟包了怎么辦?

這個好辦,用TCP協(xié)議去做傳輸。

e9d9cfdc-1464-11ed-ba43-dac502259ad0.pngTCP是什么

建立了TCP連接的兩端,發(fā)送端在發(fā)出數(shù)據(jù)后會等待接收端回復ack包,ack包的目的是為了告訴對方自己確實收到了數(shù)據(jù),但如果中間鏈路發(fā)生了丟包,那發(fā)送端會遲遲收不到確認ack,于是就會進行重傳。以此來保證每個數(shù)據(jù)包都確確實實到達了接收端。

假設現(xiàn)在網(wǎng)斷了,我們還用聊天軟件發(fā)消息,聊天軟件會使用TCP不斷嘗試重傳數(shù)據(jù),如果重傳期間網(wǎng)絡恢復了,那數(shù)據(jù)就能正常發(fā)過去。但如果多次重試直到超時都還是失敗,這時候你將收獲一個紅色感嘆號。

?

這時候問題又來了。

假設某綠皮聊天軟件用的就是TCP協(xié)議。

那文章開頭提到的女生,她男朋友回她的消息時為什么還會丟包?畢竟丟包了會重試,重試失敗了還會出現(xiàn)紅色感嘆號。

于是乎,問題就變成了,用了TCP協(xié)議,就一定不會丟包嗎?

用了TCP協(xié)議就一定不會丟包嗎

我們知道TCP位于傳輸層,在它的上面還有各種應用層協(xié)議,比如常見的HTTP或者各類RPC協(xié)議。

e9f93e1c-1464-11ed-ba43-dac502259ad0.png四層網(wǎng)絡協(xié)議

TCP保證的可靠性,是傳輸層的可靠性。也就是說,TCP只保證數(shù)據(jù)從A機器的傳輸層可靠地發(fā)到B機器的傳輸層。

至于數(shù)據(jù)到了接收端的傳輸層之后,能不能保證到應用層,TCP并不管。

假設現(xiàn)在,我們輸入一條消息,從聊天框發(fā)出,走到傳輸層TCP協(xié)議的發(fā)送緩沖區(qū),不管中間有沒有丟包,最后通過重傳都保證發(fā)到了對方的傳輸層TCP接收緩沖區(qū),此時接收端回復了一個ack,發(fā)送端收到這個ack后就會將自己發(fā)送緩沖區(qū)里的消息給扔掉。到這里TCP的任務就結(jié)束了。

TCP任務是結(jié)束了,但聊天軟件的任務沒結(jié)束。

聊天軟件還需要將數(shù)據(jù)從TCP的接收緩沖區(qū)里讀出來,如果在讀出來這一刻,手機由于內(nèi)存不足或其他各種原因,導致軟件崩潰閃退了。

發(fā)送端以為自己發(fā)的消息已經(jīng)發(fā)給對方了,但接收端卻并沒有收到這條消息。

于是乎,消息就丟了。

ea05392e-1464-11ed-ba43-dac502259ad0.png使用TCP協(xié)議卻發(fā)生丟包

雖然概率很小,但它就是發(fā)生了。

合情合理,邏輯自洽。

這類丟包問題怎么解決?

故事到這里也到尾聲了,感動之余,我們來聊點掏心窩子的話。

其實前面說的都對,沒有一句是假話

但某綠皮聊天軟件這么成熟,怎么可能沒考慮過這一點呢。

大家應該還記得我們文章開頭提到過,為了簡單,就將服務器那一方給省略了,從三端通信變成了兩端通信,所以才有了這個丟包問題。

現(xiàn)在我們重新將服務器加回來。

e8a4c5cc-1464-11ed-ba43-dac502259ad0.png聊天軟件三端通信

大家有沒有發(fā)現(xiàn),有時候我們在手機里聊了一大堆內(nèi)容,然后登錄電腦版,它能將最近的聊天記錄都同步到電腦版上。也就是說服務器可能記錄了我們最近發(fā)過什么數(shù)據(jù),假設每條消息都有個id,服務器和聊天軟件每次都拿最新消息的id進行對比,就能知道兩端消息是否一致,就像對賬一樣。

對于發(fā)送方,只要定時跟服務端的內(nèi)容對賬一下,就知道哪條消息沒發(fā)送成功,直接重發(fā)就好了。

如果接收方的聊天軟件崩潰了,重啟后跟服務器稍微通信一下就知道少了哪條數(shù)據(jù),同步上來就是了,所以也不存在上面提到的丟包情況。

可以看出,TCP只保證傳輸層的消息可靠性,并不保證應用層的消息可靠性。如果我們還想保證應用層的消息可靠性,就需要應用層自己去實現(xiàn)邏輯做保證。

那么問題叒來了,兩端通信的時候也能對賬,為什么還要引入第三端服務器?

主要有三個原因。

  • 第一,如果是兩端通信,你聊天軟件里有1000個好友,你就得建立1000個連接。但如果引入服務端,你只需要跟服務器建立1個連接就夠了,聊天軟件消耗的資源越少,手機就越省電。
  • 第二,就是安全問題,如果還是兩端通信,隨便一個人找你對賬一下,你就把聊天記錄給同步過去了,這并不合適吧。如果對方別有用心,信息就泄露了。引入第三方服務端就可以很方便的做各種鑒權(quán)校驗。
  • 第三,是軟件版本問題。軟件裝到用戶手機之后,軟件更不更新就是由用戶說了算了。如果還是兩端通信,且兩端的軟件版本跨度太大,很容易產(chǎn)生各種兼容性問題,但引入第三端服務器,就可以強制部分過低版本升級,否則不能使用軟件。但對于大部分兼容性問題,給服務端加兼容邏輯就好了,不需要強制用戶更新軟件。

所以看到這里大家應該明白了,我把服務端去掉,并不單純是為了簡單。

總結(jié)

  • 數(shù)據(jù)從發(fā)送端到接收端,鏈路很長,任何一個地方都可能發(fā)生丟包,幾乎可以說丟包不可避免。
  • 平時沒事也不用關(guān)注丟包,大部分時候TCP的重傳機制保證了消息可靠性。
  • 當你發(fā)現(xiàn)服務異常的時候,比如接口延時很高,總是失敗的時候,可以用ping或者mtr命令看下是不是中間鏈路發(fā)生了丟包。
  • TCP只保證傳輸層的消息可靠性,并不保證應用層的消息可靠性。如果我們還想保證應用層的消息可靠性,就需要應用層自己去實現(xiàn)邏輯做保證。

審核編輯 :李倩
?


非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

( 發(fā)表人:李倩 )

      發(fā)表評論

      用戶評論
      評價:好評中評差評

      發(fā)表評論,獲取積分! 請遵守相關(guān)規(guī)定!

      ?