本文由LearnLHC分享,原始出處:blog.csdn.net/LearnLHC/article/details/115268028曾雕,本文進行了排版和內容優(yōu)化。
1助被、引言
熟悉網(wǎng)絡編程的(尤其搞實時音視頻聊天技術的)同學們都有個約定俗成的主觀論調剖张,一提起UDP和TCP,馬上想到的是UDP沒有TCP可靠揩环,但UDP肯定比TCP高效搔弄。說到UDP比TCP高效,理由是什么呢丰滑?事實真是這樣嗎顾犹?跟著本文咱們一探究竟倒庵!
2、UDP報文格式
每個 UDP 報文分為 UDP 報頭和 UDP 數(shù)據(jù)區(qū)兩部分炫刷。報頭由 4 個 16 位長(2 字節(jié))字段組成擎宝,分別說明該報文的源端口、目的端口浑玛、報文長度和校驗值绍申。
UDP 報文格式如圖所示:
UDP 報文中每個字段的含義如下:
1)源端口: 16bits,發(fā)送端的端口顾彰;
2)目的端口:16bits极阅,即接收端的端口;
3)長度: 16bits涨享,UDP 數(shù)據(jù)包總的大薪畈:包頭+數(shù)據(jù),單位:字節(jié)厕隧;
4)校驗值:16bits奔脐,錯誤檢查碼,基于算法栏账,計算此 UDP 數(shù)據(jù)包是否損壞帖族。
PS:關于UDP協(xié)議可以進一步學習《TCP/IP詳解?-?第11章·UDP:用戶數(shù)據(jù)報協(xié)議》。
3挡爵、UDP有發(fā)送緩存區(qū)嗎竖般?
TCP 有 發(fā)送/接收 緩存區(qū),那 UDP 有么茶鹃?
3.1 先說結論
每個 UDP socket 都有一個接收緩沖區(qū)涣雕,但沒有發(fā)送緩沖區(qū)。
從概念上來說就是只要有數(shù)據(jù)就發(fā)闭翩,不管對方是否可以正確接收挣郭,所以不緩沖,不需要發(fā)送緩沖區(qū)疗韵。
UDP雖然有接收緩沖區(qū)兑障,但當套接口接收緩沖區(qū)滿時,新來的數(shù)據(jù)報無法進入接收緩沖區(qū)蕉汪,此數(shù)據(jù)報就被丟棄流译。因為UDP是沒有流量控制的,快的發(fā)送者可以很容易地就淹沒慢的接收者者疤,導致接收方的 UDP 丟棄數(shù)據(jù)報福澡。
而且:如果在傳輸過程中,一次傳輸被分成多個分片驹马,傳輸中有一個小分片丟失革砸,那接收端最終會舍棄整個文件除秀,導致傳輸失敗,這就是 UDP 不可靠的原因算利。
3.2 逐步分析
linux手冊中有設置 UDP 發(fā)送緩沖區(qū)相關屬性册踩,也明確提到了send buffer的概念:
那這是否意味著 UDP 是有發(fā)送緩沖區(qū)的嗎?我們再看一下《UNIX Network Programming》書中所述笔时,這本書的作者權威性我就不多說了吧棍好,在國內高校此書都是當做教材使用的仗岸。(PS:經(jīng)典書籍《UNIX網(wǎng)絡編程》最全下載(卷1+卷2允耿、中文版+英文版)[附件下載])
書中有下面兩幅圖:
如上圖所示:一張是 TCP 發(fā)送過程協(xié)議棧簡化圖,另一張是 UDP 的扒怖。
UDP 中的 send buffer 是用虛線框圈起來的较锡,具體的敘述我直接引用書中原文:
書中的描述很清楚了,UDP 是沒有發(fā)送緩沖區(qū)的盗痒,因為 UDP 是不可靠的蚂蕴,他不必像 TCP 一樣需要一個實質的發(fā)送buffer,而且真正 UDP 寫成功返回其實是傳遞到了鏈路層的 output queue 中俯邓。
4骡楼、UDP包最佳傳輸大小與分片
4.1 UDP 包最佳傳輸大小
數(shù)據(jù)鏈路層最大傳輸單元是 1500 字節(jié) (MTU) ,要想 IP 層不分包稽鞭,那么 UDP 數(shù)據(jù)包的最大大小應該是:1500字節(jié) – IP頭(20字節(jié)) – UDP頭(8字節(jié)) = 1472字節(jié)鸟整。
但,理論上 UDP 報文最大長度是 65507 字節(jié)朦蕴,那實際上發(fā)送這么大的數(shù)據(jù)包效果最好嗎篮条?
我們來看分析一下 “分片問題”。(關于 UDP 包大小吩抓、MTU相關知識涉茧,可以參考《最大可傳輸單元 MTU 對 UDP/TCP 包的大小限制》)
PS:另一篇相關文章也可以一讀《UDP中一個包的大小最大能多大?》疹娶。
4.2 分片問題
我們知道 UDP 是不可靠的傳輸協(xié)議伴栓,為了減少 UDP 包丟失的風險,我們最好能控制 UDP 包在 IP層協(xié)議的傳輸過程中不要被切割雨饺。
這是為什么呢钳垮?
如果 MTU 是1500,Client 發(fā)送一個 8000字節(jié)大小的 UDP 包沛膳,那么 Server 端阻塞模式下接包扔枫,在不丟包的情況下,recvfrom(9000)?是收到 1500锹安,還是 8000短荐。如果某個 IP 分片丟失了倚舀,recvfrom(9000),又返回什么呢忍宋?
根據(jù) UDP 通信的有界性痕貌,在 buf 足夠大的情況下,接收到的一定是一個完整的數(shù)據(jù)包糠排,UDP 數(shù)據(jù)在下層的分片和組片問題由 IP 層來處理舵稠,提交到 UDP 傳輸層一定是一個完整的 UDP 包,那么?recvfrom(9000)?將返回 8000入宦。如果某個 IP 分片丟失哺徊,udp 里有個 CRC 檢驗,如果包不完整就會丟棄乾闰,也不會通知是否接收成功落追,所以 UDP 是不可靠的傳輸協(xié)議,那么?recvfrom(9000)?將阻塞涯肩。
分片分的越多轿钠,雖然在傳輸層都是一次 send,一次 recv 病苗,但在傳輸過程中疗垛,會傳輸多次,那么丟包的概論就越大硫朦,如何解決丟包問題呢贷腕?
5、UDP丟包的原因
前提:在不考慮 IP 層的分片丟失阵幸,CRC 檢驗包不完整的情況下花履。
5.1 UDP緩沖區(qū)滿
如果 socke t緩沖區(qū)滿了,應用程序沒來得及處理在緩沖區(qū)中的 UDP 包挚赊,那么后續(xù)來的 UDP 包會被內核丟棄诡壁,造成丟包。
在 socket 緩沖區(qū)滿造成丟包的情況下荠割,可以通過增大緩沖區(qū)的方法來緩解UDP丟包問題妹卿。但是,如果服務已經(jīng)過載了蔑鹦,簡單的增大緩沖區(qū)并不能解決問題夺克,反而會造成滾雪球效應,造成請求全部超時嚎朽,服務不可用铺纽。
5.2 UDP緩沖區(qū)過小或文件過大
如果Client 發(fā)送的 UDP 報文很大,而 socket 緩沖區(qū)過小無法容下該 UDP 報文哟忍,那么該報文就會丟失狡门。
以前遇到過這種問題陷寝,我把接收緩沖設置成 64K 就解決了:
int?nRecvBuf=32*1024;//設置為32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const?char*)&nRecvBuf,sizeof(int));
5.3 ARP 緩存過期
ARP 的緩存時間約 10 分鐘,APR 緩存列表沒有對方的 MAC 地址或緩存過期的時候其馏,會發(fā)送 ARP 請求獲取 MAC 地址凤跑,
在沒有獲取到 MAC 地址之前,用戶發(fā)送出去的 UDP 數(shù)據(jù)包會被內核緩存到 arp_queue 這個隊列中叛复,默認最多緩存 3 個包仔引,多余的 UDP 包會被丟棄。
5.4 接收端處理時間過長
調用 recv 方法接收端收到數(shù)據(jù)后褐奥,處理數(shù)據(jù)花了一些時間咖耘,處理完后再次調用 recv 方法,在這二次調用間隔里抖僵,發(fā)過來的包可能丟失鲤看。
對于這種情況可以修改接收端缘揪,將包接收后存入一個緩沖區(qū)耍群,然后迅速返回繼續(xù) recv。
5.5 發(fā)送的包巨大
雖然 send 方法會幫你做大包切割成小包發(fā)送的事情找筝,但包太大也不行蹈垢。
例如超過 50K 的一個 udp 包,不切割直接通過send 方法發(fā)送也會導致這個包丟失袖裕。這種情況需要切割成小包再逐個 send曹抬。
5.6 發(fā)送的包頻率太快
雖然每個包的大小都小于 mtu size 但是頻率太快,例如 40 多個 mut size 的包連續(xù)發(fā)送中間不 sleep急鳄,也有可能導致丟包谤民。
這種情況也有時可以通過設置 socket 接收緩沖解決,但有時解決不了疾宏。
所以在發(fā)送頻率過快的時候還是考慮 sleep一下吧张足。
5.7 局域網(wǎng)內不丟包,公網(wǎng)上丟包
這個問題我也是通過切割小包并 slee p發(fā)送解決的坎藐。如果流量太大为牍,這個辦法也不靈了。
總之 udp 丟包總是會有的岩馍,如果出現(xiàn)了用我的方法解決不了碉咆,還有這個幾個方法:要么減小流量,要么換 tcp 協(xié)議傳輸蛀恩,要么做丟包重傳的工作疫铜。
6、UDP丟包的解決方案
6.1 從發(fā)送端解決——延遲發(fā)送
適用條件:
1)發(fā)送端是可以控制的双谆;
2)微秒數(shù)量級的延遲可以接受壳咕。
解決方法:
1)發(fā)送時使用 usleep(1) 延遲 1 微秒發(fā)送励稳,即發(fā)送頻率不要過快;
2)延遲1微妙發(fā)送囱井,可以很好的解決這個問題驹尼。
6.2 從接收端解決——數(shù)據(jù)接收與數(shù)據(jù)處理相分離
適用條件:無法控制發(fā)送端發(fā)送數(shù)據(jù)的頻率。
解決方法:
1)用 recvfrom 函數(shù)收到數(shù)據(jù)之后盡快返回庞呕,進行下一次 recvfrom新翎,可以通過 多線程+隊列 來解決;
2)收到數(shù)據(jù)之后將數(shù)據(jù)放入隊列中住练,另起一個線程去處理收到的數(shù)據(jù)地啰。
6.3 從接收端解決——修改接收緩存大小
適用條件:使用方法 2 依然出現(xiàn)大規(guī)模丟包的情況,需要進一步優(yōu)化
解決方法:使用 setsockopt 修改接收端的緩沖區(qū)大小讲逛。
int?rcv_size = 1024*1024;?//1M
int?optlen=sizeof(rcv_size);
//設置好緩沖區(qū)大小
int?err=setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(char?*)&rcv_size,optlen);
設置完畢可以通過下列函數(shù)亏吝,來查看當前 sock 的緩沖區(qū)大小:
setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(char?*)&rcv_size,(socklen_t *)&optlen);
但是,會發(fā)現(xiàn)查到的大小并不是1M而是256kb,后來發(fā)現(xiàn)原來是 linux 系統(tǒng)默認緩沖區(qū)大小為 128kb盏混,設置最大是這個的 2倍蔚鸥,所以需要通過修改系統(tǒng)默認緩沖區(qū)大小來解決。
使用root賬戶在命令行下輸入:
vi?/etc/sysctl.conf
添加一行記錄(1049576=1024*1024=1M):
net.core.rmem_max=1048576
保存之后輸入:
/sbin/sysctl?-p
使修改的配置生效:此時可以通過?sysctl -a|grep rmem_max?來看配置是否生效许赃。
生效之后可以再次運行程序來 getsockopt 看緩沖區(qū)是否變大了止喷,是否還會出現(xiàn)丟包現(xiàn)象了。
作者使用的是 方法2+方法3 雙管齊下混聊,已經(jīng)不會出現(xiàn)丟包現(xiàn)象了弹谁,如果還有不同程度的丟包 可以通過方法三種繼續(xù)增加緩沖區(qū)大小的方式來解決。
7句喜、UDP實現(xiàn)數(shù)據(jù)必達
7.1 UDP致命性缺點
UDP 是無連接的预愤,面向消息的數(shù)據(jù)傳輸協(xié)議。
與TCP相比咳胃,有兩個致命的缺點:
1):數(shù)據(jù)包容易丟失植康;
2):數(shù)據(jù)包無序。
7.2 解決方案1:回復 + 重發(fā) + 編號 機制
7.2.1)分析:
要實現(xiàn)文件的可靠傳輸拙绊,就必須在上層對數(shù)據(jù)丟包和亂序作特殊處理向图,必須要有要有 丟包重發(fā)機制 和 超時機制。
常見的可靠傳輸算法有模擬 TC P協(xié)議标沪,重發(fā)請求(ARQ)協(xié)議榄攀,它又可分為連續(xù) ARQ 協(xié)議、選擇重發(fā) ARQ 協(xié)議金句、滑動窗口協(xié)議等等檩赢。
如果只是小規(guī)模程序,也可以自己實現(xiàn)丟包處理,原理基本上就是給文件分塊贞瞒,每個數(shù)據(jù)包的頭部添加一個唯一標識序號的 ID 值偶房,當接收的包頭部 ID 不是期望中的 ID 號,則判定丟包军浆,將丟包 ID 發(fā)回服務端棕洋,服務器端接到丟包響應則重發(fā)丟失的數(shù)據(jù)包。
模擬 TCP 協(xié)議也相對簡單乒融,3 次握手的思想對丟包處理很有幫助掰盘。
7.2.2)回復 + 重發(fā) + 編號 機制:
1)接收方收到數(shù)據(jù)后,回復一個確認包:
如果你不回復赞季,那么發(fā)送端是不會知道接收方是否成功收到數(shù)據(jù)的愧捕。比如:A 要發(fā)數(shù)據(jù) “{data}” 到 B,那 B 收到后申钩,可以回復一個特定的確認包 “{OK}”次绘,表示成功收到。
但是如果只做上面的回復處理撒遣,還是有問題:比如 B 收到數(shù)據(jù)后回復給 A 的數(shù)據(jù) "{OK}" 的包邮偎,A 沒收到,怎么辦呢愉舔?
2)當 A 沒有收到B的 "{OK}" 包后钢猛,要做定時重發(fā)數(shù)據(jù):
定時重發(fā),直到成功接收到確認包為止轩缤,再發(fā)下面的數(shù)據(jù),當然贩绕,重發(fā)了一定數(shù)量后還是沒能收到確認包火的,可以執(zhí)行一下 ARP 的流程,防止對方網(wǎng)卡更換或別的原因淑倾。
但是這樣的話馏鹤,B 會收到很多重復的數(shù)據(jù),假如每次都是 B 回復確認包 A 收不到的話娇哆。
3)發(fā)送數(shù)據(jù)的包中加個標識符 - 編號:
比如 A 要發(fā)送的數(shù)據(jù) "標識符data" 到 B湃累,B 收到后,先回復 “{OK}" 確認包碍讨,再根據(jù)原有的標識符進行比較治力,如果標識符相同,則數(shù)據(jù)丟失勃黍,如果不相同宵统,則原有的標識符 = 接收標識符,且處理數(shù)據(jù)覆获。
當 A 發(fā)送數(shù)據(jù)包后马澈,沒有收到確認包瓢省,則每隔 x 秒,把數(shù)據(jù)重發(fā)一次痊班,直到收到確認包后勤婚,更新一下標識符,再進行后一包的數(shù)據(jù)發(fā)送涤伐。
經(jīng)過上面1)蛔六、2)、3)點的做法废亭,則可以保證數(shù)據(jù)百分百到達對方国章,當然,標識符用 ID 號來代替更好豆村。
7.3 解決方案2:冗余傳輸
在外網(wǎng)通信鏈路不穩(wěn)定的情況下液兽,有什么辦法可以降低UDP的丟包率呢?一個簡單的辦法來采用冗余傳輸?shù)姆绞健?/p>
如下圖:一般采用較多的是延時雙發(fā)掌动,雙發(fā)指的是將原本單發(fā)的前后連續(xù)的兩個包合并成一個大包發(fā)送四啰,這樣發(fā)送的數(shù)據(jù)量是原來的兩倍。
這種方式提高丟包率的原理比較簡單粗恢,例如本例的冗余發(fā)包方式柑晒,在偶數(shù)包全丟的情況下,依然能夠還原出完整的數(shù)據(jù)眷射,也就是在這種情況下匙赞,50%的丟包率,依然能夠達到100%的數(shù)據(jù)接收妖碉。
7.4 解決方3:RUDP
詳情請查看:《如何讓不可靠的UDP變的可靠涌庭?》。
文章摘錄如下:
UDP 實現(xiàn)可靠性既然那么麻煩欧宜,那直接用 TCP 好了坐榆!
確實很多人也都是這樣做的,TCP 是個基于公平性的可靠通信協(xié)議冗茸,但是在一些苛刻的網(wǎng)絡條件下 TCP 要么不能提供正常的通信質量保證席镀,要么成本過高。為什么要在 UDP 之上做可靠保證夏漱,究其原因就是在保證通信的時延和質量的條件下盡量降低成本豪诲。
RUDP 主要解決以下相關問題。
1)端對端連通性問題:一般終端直接和終端通信都會涉及到 NAT 穿越麻蹋,TCP 在 NAT 穿越實現(xiàn)非常困難跛溉,相對來說 UDP 穿越 NAT 卻簡單很多,如果是端到端的可靠通信一般用 RUDP 方式來解決,場景有:端到端的文件傳輸芳室、實時音視頻傳輸专肪、交互指令傳輸?shù)鹊取#║DP NAT穿越簡單很多)
2)弱網(wǎng)環(huán)境傳輸問題:在一些 Wi-Fi 或者 3G/4G 移動網(wǎng)下堪侯,需要做低延遲可靠通信嚎尤,如果用 TCP 通信延遲可能會非常大,這會影響用戶體驗伍宦。例如:實時的操作類網(wǎng)游通信芽死、語音對話、多方白板書寫等次洼,這些場景可以采用特殊的 RUDP 方式來解決這類問題关贵;(弱網(wǎng)傳輸UDP延長會低很多)
3)帶寬競爭問題:有時候客戶端數(shù)據(jù)上傳需要突破本身 TCP 公平性的限制來達到高速低延時和穩(wěn)定,也就是說要用特殊的流控算法來壓榨客戶端上傳帶寬卖毁,例如:直播音視頻推流揖曾,這類場景用 RUDP 來實現(xiàn)不僅能壓榨帶寬,也能更好地增加通信的穩(wěn)定性亥啦,避免類似 TCP 的頻繁斷開重連炭剪;
4)傳輸路徑優(yōu)化問題:在一些對延時要求很高的場景下,會用應用層 relay 的方式來做傳輸路由優(yōu)化翔脱,也就是動態(tài)智能選路奴拦,這時雙方采用 RUDP 方式來傳輸,中間的延遲進行 relay 選路優(yōu)化延時届吁。還有一類基于傳輸吞吐量的場景错妖,例如:服務與服務之間數(shù)據(jù)分發(fā)、數(shù)據(jù)備份等瓷产,這類場景一般會采用多點并聯(lián) relay 來提高傳輸?shù)乃俣日拘彩且⒃?RUDP 上的(這兩點在后面著重來描述);
5)資源優(yōu)化問題:某些場景為了避免 TCP 的三次握手和四次揮手的過程濒旦,會采用 RUDP 來優(yōu)化資源的占用率和響應時間,提高系統(tǒng)的并發(fā)能力再登,例如 QUIC尔邓。
8、UDP真的比TCP高效嗎
回到正題:相信很多同學都認為 UDP 無連接锉矢,無需重傳和處理確認梯嗽,UDP 肯定比較高效?
然而?UDP 在大多情況下并不一定比 TCP 高效沽损。TCP 發(fā)展至今天灯节,為了適應各種復雜的網(wǎng)絡環(huán)境,其算法已經(jīng)非常豐富,協(xié)議本身經(jīng)過了很多優(yōu)化炎疆,如果能夠合理配置 TCP 的各種參數(shù)選項卡骂,那么在多數(shù)的網(wǎng)絡環(huán)境下 TCP 是要比 UDP 更高效的。
影響 UDP 高效因素有以下3點形入。
1)UDP 無法智能利用空閑帶寬導致資源利用率低:
一個簡單的事實是 UDP 并不會受到 MTU 的影響全跨,MTU 只會影響下層的 IP 分片,對此 UDP 一無所知亿遂。
在極端情況下浓若,UDP 每次都是發(fā)小包,包是 MTU 的幾百分之一蛇数,這樣就造成 UDP 包的有效數(shù)據(jù)占比較小 (UDP 頭的封裝成本)挪钓;
或者,UDP 每次都是發(fā)巨大的 UDP 包耳舅,包大小是 MTU 的幾百倍碌上,這樣會造成下層 IP 層的大量分片,大量分片的情況下挽放,其中某個分片丟失了绍赛,就會導致整個 UDP 包的無效。
由于網(wǎng)絡情況是動態(tài)變化的辑畦,UDP 無法根據(jù)變化進行調整吗蚌,發(fā)包過大或過小,從而導致帶寬利用率低下纯出,有效吞吐量較低蚯妇。
而 TCP 有一套智能算法,當發(fā)現(xiàn)數(shù)據(jù)必須積攢的時候暂筝,就說明此時不積攢也不行箩言,TCP 的復雜算法會在延遲和吞吐量之間達到一個很好的平衡。
2)UDP無法動態(tài)調整發(fā)包:
由于 UDP 沒有確認機制焕襟,沒有流量控制和擁塞控制陨收,這樣在網(wǎng)絡出現(xiàn)擁塞 或 通信兩端處理能力不匹配的時候,UDP 并不會進行調整發(fā)送速率鸵赖,從而導致大量丟包务漩。
在丟包的時候符匾,不合理的簡單重傳策略會導致重傳風暴咐蚯,進一步加劇網(wǎng)絡的擁塞,從而導致丟包率雪上加霜啤贩。
更加嚴重的是茫打,UDP 的 無秩序性和自私性居触,一個瘋狂的 UDP 程序可能會導致這個網(wǎng)絡的擁塞妖混,擠壓其他程序的流量帶寬,導致所有業(yè)務質量都下降轮洋。
3)改進 UDP 的成本較高:
可能有同學想到針對 UDP 的一些缺點制市,在用戶態(tài)做些調整改進,添加上簡單的重傳和動態(tài)發(fā)包大小優(yōu)化砖瞧。
然而息堂,這樣的改進并比簡單的,UDP 編程可是比 TCP 要難不少的块促,考慮到改造成本荣堰,為什么不直接用TCP呢?
當然可以拿開源的一些實現(xiàn)來抄一下(例如:libjingle)竭翠,或者擁抱一下 Google 的 QUIC 協(xié)議振坚,然而,這些都需要不少成本的斋扰。
上面說了這么多渡八,難道真的不該用UDP了嗎?其實也不是的传货,在某些場景下屎鳍,我們還是必須 UDP 才行的。那么 UDP 的較為合適的使用場景是哪些呢问裕?
9逮壁、UDP協(xié)議的最佳使用場合
9.1 高實時性和低持續(xù)性場景
在分組交換通信當中,協(xié)議棧的成本主要表現(xiàn)在以下兩方面:
1)?封裝帶來的空間復雜度粮宛;
2)緩存帶來的時間復雜度窥淆。
以上兩者是對立影響的,如果想減少封裝消耗巍杈,那么就必須緩存用戶數(shù)據(jù)到一定量在一次性封裝發(fā)送出去忧饭,這樣每個協(xié)議包的有效載荷將達到最大化,這無疑是節(jié)省了帶寬空間筷畦,帶寬利用率較高词裤,但是延時增大了。
如果想降低延時鳖宾,那么就需要將用戶數(shù)據(jù)立馬封裝發(fā)出去亚斋,這樣顯然會造成消耗更多的協(xié)議頭等消耗,浪費帶寬空間攘滩。
因此,我們進行協(xié)議選擇的時候纸泡,需要重點考慮一下空間復雜度和時間復雜度間的平衡漂问。
通信的持續(xù)性對兩者的影響比較大赖瞒,根據(jù)通信的持續(xù)性有兩種通信類型:
1)?短連接通信;
2)?長連接通信蚤假。
對于短連接通信:一方面如果業(yè)務只需要發(fā)一兩個包并且對丟包有一定的容忍度栏饮,同時業(yè)務自己有簡單的輪詢或重復機制,那么采用 UDP 會較為好些磷仰。在這樣的場景下袍嬉,如果用 TCP,僅僅握手就需要 3 個包灶平,這樣顯然有點不劃算伺通,一個典型的例子是 DNS 查詢。
另一方面:如果業(yè)務實時性要求非常高逢享,并且不能忍受重傳罐监,那么首先就是 UDP 了或者只能用 UDP 了,例如 NTP 協(xié)議瞒爬,重傳 NTP 消息純屬添亂(為什么呢弓柱?重傳一個過期的時間包過來,還不如發(fā)一個新的 UDP 包同步新的時間過來)侧但。
如果 NTP 協(xié)議采用 TCP矢空,撇開握手消耗較多數(shù)據(jù)包交互的問題,由于 TCP 受 Nagel 算法等影響禀横,用戶數(shù)據(jù)會在一定情況下會被內核緩存延后發(fā)送出去屁药,這樣時間同步就會出現(xiàn)比較大的偏差,協(xié)議將不可用燕侠。
9.2 多點通信場景
對于一些多點通信的場景者祖,如果采用有連接的 TCP,那么就需要和多個通信節(jié)點建立其雙向連接绢彤,然后有時在 NAT 環(huán)境下七问,兩個通信節(jié)點建立其直接的 TCP 連接不是一個容易的事情,在涉及 NAT 穿越的時候茫舶,UDP 協(xié)議的無連接性使得穿透成功率更高械巡。
(原因詳見:由于 UDP 的無連接性,那么其完全可以向一個組播地址發(fā)送數(shù)據(jù)或者輪轉地向多個目的地持續(xù)發(fā)送相同的數(shù)據(jù)饶氏,從而更為容易實現(xiàn)多點通信讥耗。)
一個典型的場景是:多人實時音視頻通信,這種場景下實時性要求比較高疹启,可以容忍一定的丟包率古程。
比如:對于音頻,對端連續(xù)發(fā)送 p1喊崖、p2挣磨、p3 三個包雇逞,另一端收到了 p1 和 p3,在沒收到 p2 的保持 p1 的最后一個音(也是為什么有時候網(wǎng)絡丟包就會聽到嗞嗞嗞嗞嗞嗞…或者卟卟卟卟卟卟卟卟…重音的原因)茁裙,等到到 p3 就接著播 p3 了塘砸,不需要也不能補幀,一補就越來越大的延時晤锥。
對于這樣的場景就比較合適用 UDP 了掉蔬,如果采用 TCP,那么在出現(xiàn)丟包的時候矾瘾,就可能會出現(xiàn)比較大的延時女轿。
9.3 其它UDP應用舉例
通常情況下,UDP 的使用范圍是較小的霜威,但在以下的場景下谈喳,使用 UDP 才是明智的:
1)實時性要求很高,并且?guī)缀醪荒苋萑讨貍鳎豪樱篘TP 協(xié)議戈泼,實時音視頻通信婿禽,直播、實時游戲大猛、多人動作類游戲中人物動作扭倾、位置;
2)?TCP 實在不方便實現(xiàn)多點傳輸?shù)那闆r挽绩;
3)需要進行 NAT 穿越膛壹;
4)?對網(wǎng)絡狀態(tài)很熟悉,確保 udp 網(wǎng)絡中沒有氓流行為唉堪,瘋狂搶帶寬模聋;
5)熟悉 UDP 編程。
UDP本身是不可靠唠亚,現(xiàn)在需要保證可靠链方,在不改變 UDP 協(xié)議的情況下能夠想到的是在應用層做可靠性設計,但是應用層做可能通用性會差一些灶搜,那么在傳輸層和應用層之間加一層實現(xiàn)UDP的可靠性呢祟蚀?
基于這個想法提出了RUDP(Reliable UDP),實際上割卖,已經(jīng)有項目在這么做了前酿,比如 Google 的 QUIC(《一泡尿的時間,快速讀懂QUIC協(xié)議》) 和 WebRTC(《零基礎快速入門WebRTC:基本概念鹏溯、關鍵技術罢维、與WebSocket的區(qū)別等》)。
據(jù)了解丙挽,目前國內廠商做實時傳輸一般都會考慮 RUDP言津。
9.4 QQ聊天中使用的udp協(xié)議淺析
曾今的一個討論貼:《為什么QQ用的是UDP協(xié)議而不是TCP協(xié)議攻人?》,可以一并讀一讀悬槽。
1)用 tcp 長連接,對服務器的負擔很大:
首先每一個 QQ 客戶端實際上都適合服務器交互瞬浓,再由服務器轉發(fā)給正在通信的用戶初婆,如果每一個 QQ 從一上線到下線的這段時間全部采用 tcp 長連接,這對服務器的負擔很大猿棉,而如果采用 tcp 短連接磅叛,頻繁的連接斷開也會造成網(wǎng)絡負擔,而采用 udp 則可以避開上述麻煩萨赁,減少服務器的負擔弊琴。
不管 udp 還是 tcp,最終登陸成功之后杖爽,QQ 都會有一個 tcp 連接來保持在線狀態(tài)敲董。這個 tcp 連接的遠程端口一般是80,采用 udp 方式登陸的時候慰安,端口是8000腋寨。
udp 協(xié)議是無連接方式的協(xié)議,它的效率高化焕,速度快萄窜,占資源少,但是其傳輸機制為不可靠傳送撒桨,必須依靠輔助的算法來完成傳輸控制查刻。
QQ 采用的通信協(xié)議以 udp 為主,輔以 tcp 協(xié)議凤类。由于 QQ 的服務器設計容量是海量級的應用穗泵,一臺服務器要同時容納十幾萬的并發(fā)連接,因此服務器端只有采用 udp 協(xié)議與客戶端進行通訊才能保證這種超大規(guī)模的服務踱蠢。
2)tcp 較難實現(xiàn) NAT 穿越:
QQ 客戶端之間的消息傳送也采用了 udp 模式火欧,因為國內的網(wǎng)絡環(huán)境非常復雜,而且很多用戶采用的方式是通過代理服務器共享一條線路上網(wǎng)的方式茎截。
在這些復雜的情況下苇侵,客戶端之間能彼此建立起來 tcp 連接的概率較小,嚴重影響傳送信息的效率企锌。
而 udp 包能夠穿透大部分的代理服務器榆浓,因此 QQ 選擇了 udp 作為客戶之間的主要通信協(xié)議。采用 udp 協(xié)議撕攒,通過服務器中轉方式陡鹃。因此烘浦,現(xiàn)在的 IP 偵探在你僅僅跟對方發(fā)送聊天消息的時候是無法獲取到IP的。
3)讓 UDP 變得可靠:
大家都知道萍鲸,udp 協(xié)議是不可靠協(xié)議闷叉,它只管發(fā)送,不管對方是否收到的脊阴,但它的傳輸很高效握侧。
但是作為聊天軟件,怎么可以采用這樣的不可靠方式來傳輸消息呢嘿期?
于是品擎,騰訊采用了上層協(xié)議來保證可靠傳輸:如果客戶端使用 udp 協(xié)議發(fā)出消息后,服務器收到該包备徐,需要使用 udp 協(xié)議發(fā)回一個應答包萄传,如此來保證消息可以無遺漏傳輸。
之所以會發(fā)生在客戶端明明看到“消息發(fā)送失敗”但對方又收到了這個消息的情況蜜猾,就是因為客戶端發(fā)出的消息服務器已經(jīng)收到并轉發(fā)成功秀菱,但客戶端由于網(wǎng)絡原因沒有收到服務器的應答包引起的。
QQ 并不是端對端的聊天軟件瓣铣,是得經(jīng)過服務器轉發(fā)消息的答朋,通過 QQ 聊天,數(shù)據(jù)是 A 發(fā)到服務器棠笑,服務器再轉發(fā)到 B梦碗。
10、系列文章
本文是系列文章中的第?18篇蓖救,本系列文章的大綱如下:
《不為人知的網(wǎng)絡編程(一):淺析TCP協(xié)議中的疑難雜癥(上篇)》
《不為人知的網(wǎng)絡編程(二):淺析TCP協(xié)議中的疑難雜癥(下篇)》
《不為人知的網(wǎng)絡編程(三):關閉TCP連接時為什么會TIME_WAIT洪规、CLOSE_WAIT》
《不為人知的網(wǎng)絡編程(四):深入研究分析TCP的異常關閉》
《不為人知的網(wǎng)絡編程(五):UDP的連接性和負載均衡》
《不為人知的網(wǎng)絡編程(六):深入地理解UDP協(xié)議并用好它》
《不為人知的網(wǎng)絡編程(七):如何讓不可靠的UDP變的可靠?》
《不為人知的網(wǎng)絡編程(八):從數(shù)據(jù)傳輸層深度解密HTTP》
《不為人知的網(wǎng)絡編程(九):理論聯(lián)系實際循捺,全方位深入理解DNS》
《不為人知的網(wǎng)絡編程(十):深入操作系統(tǒng)斩例,從內核理解網(wǎng)絡包的接收過程(Linux篇)》
《不為人知的網(wǎng)絡編程(十一):從底層入手,深度分析TCP連接耗時的秘密》
《不為人知的網(wǎng)絡編程(十二):徹底搞懂TCP協(xié)議層的KeepAlive贝娱伲活機制》
《不為人知的網(wǎng)絡編程(十三):深入操作系統(tǒng)念赶,徹底搞懂127.0.0.1本機網(wǎng)絡通信》
《不為人知的網(wǎng)絡編程(十四):拔掉網(wǎng)線再插上,TCP連接還在嗎恰力?一文即懂叉谜!》
《不為人知的網(wǎng)絡編程(十五):深入操作系統(tǒng),一文搞懂Socket到底是什么》
《不為人知的網(wǎng)絡編程(十六):深入分析與解決TCP的RST經(jīng)典異常問題》
《不為人知的網(wǎng)絡編程(十七):冰山之下踩萎,一次網(wǎng)絡請求背后的技術秘密》
《不為人知的網(wǎng)絡編程(十八):UDP比TCP高效停局?還真不一定!》(* 本文)
11、參考資料
[1]?TCP/IP詳解?-?第11章·UDP:用戶數(shù)據(jù)報協(xié)議
[2]?TCP/IP詳解?-?第17章·TCP:傳輸控制協(xié)議
[4]?快速理解TCP和UDP的差異
[5]?快速理解為什么說UDP有時比TCP更有優(yōu)勢
[6]?一泡尿的時間董栽,快速搞懂TCP和UDP的區(qū)別
[7]?技術掃盲:新一代基于UDP的低延時網(wǎng)絡傳輸層協(xié)議——QUIC詳解
[8]?什么是公網(wǎng)IP和內網(wǎng)IP码倦?NAT轉換又是什么鬼?
[9]?假如你來設計TCP協(xié)議锭碳,會怎么做袁稽?
[10]?深入地理解UDP協(xié)議并用好它
[11]?如何讓不可靠的UDP變的可靠?
[13]?深入操作系統(tǒng)运提,從內核理解網(wǎng)絡包的接收過程(Linux篇)
[15]?UDP中一個包的大小最大能多大?