UDP也可以是“已連接”兴泥?
根據(jù)接觸到了 UDP 數(shù)據(jù)報(bào)協(xié)議相關(guān)的知識(shí)工育,在我們的腦海里,已經(jīng)深深印上了“UDP 等于無連接協(xié)議”的特性搓彻。
在沒有開啟服務(wù)端的情況下如绸,我們運(yùn)行一下這個(gè)程序:
看到這里你會(huì)不會(huì)覺得很奇怪:
不是說好 UDP 是“無連接”的協(xié)議嗎?
不是說好 UDP 客戶端只會(huì)阻塞在 recvfrom 這樣的調(diào)用上嗎旭贬?
怎么這里冒出一個(gè)“Connection refused”的錯(cuò)誤呢怔接?
從前面的例子中,你會(huì)發(fā)現(xiàn)骑篙,我們可以對(duì) UDP 套接字調(diào)用 connect 函數(shù)蜕提,但是和 TCP connect 調(diào)用引起 TCP 三次握手森书,建立 TCP 有效連接不同靶端,UDP connect 函數(shù)的調(diào)用,并不會(huì)引起和服務(wù)器目標(biāo)端的網(wǎng)絡(luò)交互凛膏,也就是說杨名,并不會(huì)觸發(fā)所謂的”握手“報(bào)文發(fā)送和應(yīng)答。
那么對(duì) UDP 套接字進(jìn)行 connect 操作到底有什么意義呢猖毫?
其實(shí)上面的例子已經(jīng)給出了答案台谍,這主要是為了讓應(yīng)用程序能夠接收”異步錯(cuò)誤“的信息。
如果我們回想一下吁断,不調(diào)用 connect 操作的客戶端程序趁蕊,在服務(wù)器端不開啟的情況下,客戶端程序是不會(huì)報(bào)錯(cuò)的仔役,程序只會(huì)阻塞在 recvfrom 上掷伙,等待返回(或者超時(shí))。
在這里又兵,我們通過對(duì) UDP 套接字進(jìn)行 connect 操作任柜,將 UDP 套接字建立了”上下文“卒废,該套接字和服務(wù)器端的地址和端口產(chǎn)生了聯(lián)系,正是這種綁定關(guān)系給了操作系統(tǒng)內(nèi)核必要的信息宙地,能夠?qū)⒉僮飨到y(tǒng)內(nèi)核收到的信息和對(duì)應(yīng)的套接字進(jìn)行關(guān)聯(lián)摔认。
我們可以展開討論一下。
事實(shí)上宅粥,當(dāng)我們調(diào)用 sendto 或者 send 操作函數(shù)時(shí)参袱,應(yīng)用程序報(bào)文被發(fā)送,我們的應(yīng)用程序返回秽梅,操作系統(tǒng)內(nèi)核接管了該報(bào)文蓖柔,之后操作系統(tǒng)開始嘗試往對(duì)應(yīng)的地址和端口發(fā)送,因?yàn)閷?duì)應(yīng)的地址和端口不可達(dá)风纠,一個(gè) ICMP 報(bào)文會(huì)返回給操作系統(tǒng)內(nèi)核况鸣,該 ICMP 報(bào)文含有目的地址和端口等信息。
如果我們不進(jìn)行 connect 操作竹观,建立(UDP 套接字——目的地址 + 端口)之間的映射關(guān)系镐捧,操作系統(tǒng)內(nèi)核就沒有辦法把 ICMP 不可達(dá)的信息和 UDP 套接字進(jìn)行關(guān)聯(lián),也就沒有辦法將 ICMP 信息通知給應(yīng)用程序臭增。
這就涉及到了網(wǎng)絡(luò)協(xié)議設(shè)計(jì)中的數(shù)據(jù)平面和控制平面了懂酱,對(duì)于控制平面的消息,可以是帶內(nèi)傳輸誊抛,也可以是帶外傳輸列牺。
實(shí)際上,ICMP拗窃,根據(jù)名稱就可以看出它是一種專門的控制協(xié)議瞎领,控制和指示IP層發(fā)生的事件。
ECONNREFUSED正是ICMP返回的随夸,然而并不是所有的UDP socket都可以享用ICMP帶來的錯(cuò)誤提示九默,畢竟帶外控制消息和協(xié)議本身的關(guān)聯(lián)太松散了。
UDP socket必須顯式的connect對(duì)端才可以宾毒。
如果我們進(jìn)行了 connect 操作驼修,幫助操作系統(tǒng)內(nèi)核從容建立了(UDP 套接字——目的地址 + 端口)之間的映射關(guān)系,該映射的作用正是為了和UDP帶外的ICMP控制通道捆綁在一起诈铛,使得UDP socket的接口含義更加豐滿乙各。
當(dāng)收到一個(gè) ICMP 不可達(dá)報(bào)文時(shí),操作系統(tǒng)內(nèi)核可以從映射表中找出是哪個(gè) UDP 套接字擁有該目的地址和端口幢竹,別忘了套接字在操作系統(tǒng)內(nèi)部是全局唯一的耳峦,當(dāng)我們?cè)谠撎捉幼稚显俅握{(diào)用 recvfrom 或 recv 方法時(shí),就可以收到操作系統(tǒng)內(nèi)核返回的”Connection Refused“的信息妨退。
一般來說妇萄,服務(wù)器端不會(huì)主動(dòng)發(fā)起 connect 操作蜕企,因?yàn)橐坏┤绱耍?wù)器端就只能響應(yīng)一個(gè)客戶端了冠句。
當(dāng)然轻掩,有時(shí)候也不排除這樣的情形,一旦一個(gè)客戶端和服務(wù)器端發(fā)送 UDP 報(bào)文之后懦底,該服務(wù)器端就要服務(wù)于這個(gè)唯一的客戶端唇牧。
一般來說,客戶端通過 connect 綁定服務(wù)端的地址和端口聚唐,對(duì) UDP 而言丐重,可以有一定程度的性能提升。
這是為什么呢杆查?
因?yàn)槿绻皇褂?connect 方式扮惦,每次發(fā)送報(bào)文都會(huì)需要這樣的過程:
連接套接字→發(fā)送報(bào)文→斷開套接字→連接套接字→發(fā)送報(bào)文→斷開套接字 →………
而如果使用 connect 方式,就會(huì)變成下面這樣:
連接套接字→發(fā)送報(bào)文→發(fā)送報(bào)文→……→最后斷開套接字
我們知道亲桦,連接套接字是需要一定開銷的崖蜜,比如需要查找路由表信息。
所以客峭,UDP 客戶端程序通過 connect 可以獲得一定的性能提升豫领。
我對(duì) UDP 套接字調(diào)用 connect 方法進(jìn)行了深入的分析。
之所以對(duì) UDP 使用 connect舔琅,綁定本地地址和端口等恐,是為了讓我們的程序可以快速獲取異步錯(cuò)誤信息的通知,同時(shí)也可以獲得一定性能上的提升备蚓。
不進(jìn)行connect操作课蔬,UDP套接字與服務(wù)端的地址和端口就沒有產(chǎn)生關(guān)系,那recvfrom是怎么收到對(duì)應(yīng)的報(bào)文呢星著?
UDP的connect操作购笆,會(huì)引發(fā)內(nèi)核的ICMP報(bào)文發(fā)送?如果不是虚循,ICMP是在什么時(shí)機(jī)下發(fā)送的?
1.是通過sendto函數(shù)來綁定服務(wù)端地址的样傍,之后再通過recvfrom引用到之前的socket横缔,這樣收到的報(bào)文就是指定的服務(wù)地址和端口了;
2.不是connect導(dǎo)致ICMP報(bào)文衫哥,而是對(duì)應(yīng)的地址和端口不可達(dá)時(shí)茎刚,一個(gè) ICMP 報(bào)文會(huì)返回。connect只是將這個(gè)信息傳遞變得可能了撤逢。
為什么UDP的sendto方法會(huì)有一個(gè)"連接"過程的性能損耗膛锭,直接按照目標(biāo)地址發(fā)過去不就可以了嗎粮坞?
操作系統(tǒng)會(huì)先用ICMP協(xié)議探一探目標(biāo)地址是否存在,然后再用UDP協(xié)議發(fā)送具體的數(shù)據(jù)初狰,不知道理解的對(duì)不莫杈?
我不覺得會(huì)發(fā)ICMP來探一探。ICMP是用的時(shí)候才觸發(fā)的奢入。
這里我想表達(dá)的是操作系統(tǒng)協(xié)議棧在每次sendto的時(shí)候都會(huì)需要一個(gè)地址初始化的過程筝闹,如果這個(gè)過程省略掉了,是可以得到一點(diǎn)點(diǎn)性能的提升的腥光。當(dāng)然关顷,其實(shí)這個(gè)是沒有那么大的。
udp 連接套接字 這個(gè)是什么過程武福? 斷開套接字這又是什么過程呢议双?
沒有斷開,這里都是一個(gè)系統(tǒng)調(diào)用捉片,告訴了一些系統(tǒng)內(nèi)核信息而已聋伦。
通常在服務(wù)器端不開啟的情況下,UDP客戶端程序是不會(huì)報(bào)錯(cuò)的界睁,程序只會(huì)阻塞在 recvfrom 上觉增,等待返回(或者超時(shí))。
UDP的connect并不是真正的conncect操作翻斟,它只是給UDP 套接字建立了“上下文”逾礁。
在客戶端的代碼中,在send之前進(jìn)行了connect访惜。為什么send函數(shù)返回是成功嘹履,而recv就返回失敗呢?send的時(shí)候不應(yīng)該也能知道icmp報(bào)文返回錯(cuò)誤而返回錯(cuò)誤么债热?
從函數(shù)設(shè)計(jì)的角度來說砾嫉,在UDP通信中, sendto的目的是將報(bào)文通過網(wǎng)絡(luò)傳送給對(duì)端,并不考慮是否能發(fā)送成功窒篱,僅僅考慮的是把報(bào)文通過緩沖區(qū)發(fā)送出去焕刮;
而recvfrom則是一個(gè)阻塞調(diào)用,它是需要知道是否成功的墙杯,包括超時(shí)配并,包括ICMP報(bào)文返回錯(cuò)誤。
不調(diào)用connect時(shí)高镐, 難道recvfrom不是收到后再填充from的地址的嗎溉旋,還可以指定從某個(gè)服務(wù)端收數(shù)據(jù)?
這是對(duì)UDP而言的嫉髓,通過sendto函數(shù)客戶端指定了發(fā)送的服務(wù)端地址观腊,再通過recvfrom就可以從之前指定的服務(wù)端地址來接收數(shù)據(jù)了邑闲。
這里,沒有說可以任意從某個(gè)服務(wù)端收數(shù)據(jù)的梧油,而是從之前指定的服務(wù)端收數(shù)據(jù)的苫耸。
UDP 連接和斷開套接字的過程是怎樣的?
UDP 連接套接字不是發(fā)起連接請(qǐng)求的過程婶溯,而是記錄目的地址和端口到套接字的映射關(guān)系鲸阔。
斷開套接字則相反,將刪除原來記錄的映射關(guān)系迄委。
在 UDP 中不進(jìn)行 connect褐筛,為什么客戶端會(huì)收到信息?
有人說叙身,如果按照我在文章中的說法渔扎,UDP 只有 connect 才建立 socket 和 IP 地址的映射,那么如果不進(jìn)行 connect信轿,收到信息后內(nèi)核又如何把數(shù)據(jù)交給對(duì)應(yīng)的 socket晃痴?
這個(gè)問題非常有意思。我剛剛看到這個(gè)問題的時(shí)候财忽,心里也在想倘核,是啊,我是不是說錯(cuò)了即彪?
其實(shí)呢紧唱,這對(duì)應(yīng)了兩個(gè)不同的 API 場(chǎng)景:
第一個(gè)場(chǎng)景就是我這里討論的 connect 場(chǎng)景,在這個(gè)場(chǎng)景里隶校,我們討論的是 ICMP 報(bào)文和 socket 之間的定位漏益。
我們知道,ICMP 報(bào)文發(fā)送的是一個(gè)不可達(dá)的信息深胳,不可達(dá)的信息是通過目的地址和端口來區(qū)分的绰疤,如果沒有 connect 操作,目的地址和端口就沒有辦法和 socket 套接字進(jìn)行對(duì)應(yīng)舞终,所以轻庆,即使收到了 ICMP 報(bào)文,內(nèi)核也沒有辦法通知到對(duì)應(yīng)的應(yīng)用程序权埠,告訴它連接地址不可達(dá)榨了。
那么為什么在不 connect 的情況下,我們的客戶端又可以收到服務(wù)器回顯的信息了攘蔽?
這就涉及到了第二個(gè)場(chǎng)景,也就是報(bào)文發(fā)送的場(chǎng)景呐粘。
注意服務(wù)器端程序满俗,先通過 recvfrom 函數(shù)調(diào)用獲取了客戶端的地址和端口信息转捕,這當(dāng)然是可以的,因?yàn)?UDP 報(bào)文里面包含了這部分信息唆垃。
然后我們看到服務(wù)器端又通過調(diào)用 sendto 函數(shù)五芝,把客戶端的地址和端口信息告訴了內(nèi)核協(xié)議棧,可以肯定的是辕万,之后發(fā)送的 UDP 報(bào)文就帶上了客戶端的地址和端口信息枢步,通過客戶端的地址和端口信息,可以找到對(duì)應(yīng)的套接字和應(yīng)用程序渐尿,完成數(shù)據(jù)的收發(fā)醉途。
參考
UDP編程有必要調(diào)用connect嗎?
https://zhuanlan.zhihu.com/p/380109394
UDP socket也可以使用connect系統(tǒng)調(diào)用
https://www.cnblogs.com/jingzhishen/p/5545787.html
關(guān) udp socket 連接問題分析
http://www.reibang.com/p/6ab38d2eefc2
UDP也可以是“已連接”砖茸?
https://time.geekbang.org/column/article/129807