新聞中心
哈哈,但讓我疑惑的問題是 udp 是如何檢測對端掛了?

err: write udp 172.16.44.62:62651->172.16.0.46:29999: write: connection refused
err: write udp 172.16.44.62:62651->172.16.0.46:29999: write: connection refused
err: write udp 172.16.44.62:62651->172.16.0.46:29999: write: connection refused
...
UDP 協(xié)議既沒有三次握手,又沒有 TCP 那樣的狀態(tài)控制報(bào)文,那么如何判定對端的 UDP 端口是否已打開?
通過抓包可以發(fā)現(xiàn),當(dāng)服務(wù)端的端口沒有打開時(shí),服務(wù)端的系統(tǒng)向客戶端返回 icmp ECONNREFUSED 報(bào)文,表明該連接異常。
通過抓包可以發(fā)現(xiàn)返回的協(xié)議為 ICMP,但含有源端口和目的端口,客戶端系統(tǒng)解析該報(bào)文時(shí),通過五元組找到對應(yīng)的 socket,并 errno 返回異常錯誤,如果客戶端陷入等待,則喚醒起來,設(shè)置錯誤狀態(tài)。
(上面是 udp 異常下的 icmp,下面是正常 icmp)
當(dāng) UDP 連接異常時(shí),可以通過 tcpdump 工具指定 ICMP 協(xié)議來抓取該異常報(bào)文,畢竟對方是通過 icmp 返回的 ECONNREFUSED。
使用 tcpdump 抓包
請求命令:
先找到一個(gè)可以 ping 通的主機(jī),然后用 nc 模擬 udp 客戶端去請求不存在的端口,出現(xiàn) Connection refused。
[root@ocean ~]# nc -vzu 172.16.0.46 8888
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 172.16.0.46:8888.
Ncat: Connection refused.
抓包信息如下:
[root@ocean ~]# tcpdump -i any icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:01:14.075617 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
17:01:17.326145 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
17:01:17.927480 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
17:01:18.489560 IP 172.16.0.46 > 172.16.0.62: ICMP 172.16.0.46 udp port 8888 unreachable, length 37
還需要注意的是 telnet 不支持 udp,只支持 tcp,建議使用 nc 來探測 udp。
各種case的測試
case小結(jié)
- 當(dāng) ip 無法連通時(shí),udp 客戶端連接時(shí),通常會顯示成功。
- 當(dāng) udp 服務(wù)端程序關(guān)閉,但系統(tǒng)還存在時(shí),對方系統(tǒng)會 `icmp ECONNREFUSE 錯誤。
- 當(dāng)對方有操作 iptables udp port drop 時(shí),通??蛻舳艘矔@示成功。
IP 無法聯(lián)通時(shí):
[root@host-46 ~ ]$ ping 172.16.0.65
PING 172.16.0.65 (172.16.0.65) 56(84) bytes of data.
From 172.16.0.46 icmp_seq=1 Destination Host Unreachable
From 172.16.0.46 icmp_seq=2 Destination Host Unreachable
From 172.16.0.46 icmp_seq=3 Destination Host Unreachable
From 172.16.0.46 icmp_seq=4 Destination Host Unreachable
From 172.16.0.46 icmp_seq=5 Destination Host Unreachable
From 172.16.0.46 icmp_seq=6 Destination Host Unreachable
^C
--- 172.16.0.65 ping statistics ---
6 packets transmitted, 0 received, +6 errors, 100% packet loss, time 4999ms
pipe 4
[root@host-46 ~ ]$ nc -vzu 172.16.0.65 8888
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 172.16.0.65:8888.
Ncat: UDP packet sent successfully
Ncat: 1 bytes sent, 0 bytes received in 2.02 seconds.
另外再次明確一點(diǎn) udp 沒有類似 tcp 那樣的狀態(tài)報(bào)文,所以單純對 UDP 抓包是看不到啥異常信息。
那么當(dāng) IP 不通時(shí),為啥 NC UDP 命令顯示成功?
netcat nc udp 的邏輯
為什么當(dāng) ip 不連通或者報(bào)文被 DROP 時(shí),返回連接成功?
因?yàn)?nc 默認(rèn)的探測邏輯很簡單,只要在 2 秒鐘內(nèi)沒有收到 icmp ECONNREFUSED 異常報(bào)文,那么就認(rèn)為 UDP 連接成功。
下面是 nc udp 命令執(zhí)行的過程。
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(30000), sin_addr=inet_addr("172.16.0.111")}, 16) = 0
select(4, [3], [3], [3], NULL) = 1 (out [3])
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
write(2, "Ncat: ", 6Ncat: ) = 6
write(2, "Connected to 172.16.0.111:29999."..., 33Connected to 172.16.0.111:29999.
) = 33
sendto(3, "\0", 1, 0, NULL, 0) = 1
// select 多路復(fù)用方法里加入了超時(shí)邏輯。
select(4, [3], [], [], {tv_sec=2, tv_usec=0}) = 0 (Timeout)
write(2, "Ncat: ", 6Ncat: ) = 6
write(2, "UDP packet sent successfully\n", 29UDP packet sent successfully
) = 29
write(2, "Ncat: ", 6Ncat: ) = 6
write(2, "1 bytes sent, 0 bytes received i"..., 481 bytes sent, 0 bytes received in 2.02 seconds.
) = 48
close(3) = 0
使用 golang/ python 編寫的 UDP 客戶端,給無法連通的地址發(fā) UDP 報(bào)文時(shí),其實(shí)也不會報(bào)錯,這時(shí)候通常會認(rèn)為發(fā)送成功。
還是那句話,UDP 沒有 TCP 那樣的握手步驟,像 TCP 發(fā)送 syn 總得不到回報(bào)時(shí),協(xié)議棧會在時(shí)間退避下嘗試 6 次,當(dāng) 6 次還得不到回應(yīng),內(nèi)核會給與錯誤的 errno 值。
UDP 連接信息
在客戶端的主機(jī)上,通過 ss lsof netstat 可以看到 UDP 五元組連接信息。
[root@host-46 ~ ]$ netstat -tunalp|grep 29999
udp 0 0 172.16.0.46:44136 172.16.0.46:29999
通常在服務(wù)端上看不到 UDP 連接信息,只可以看到 udp listen 信息!
[root@host-62 ~ ]# netstat -tunalp|grep 29999
udp 0 0 :::29999 :::* 4038720/ss
客戶端重新實(shí)例化問題?
當(dāng) client 跟 server 已連接,server 端手動重啟后,客戶端無需再次重新實(shí)例化連接,可以繼續(xù)發(fā)送數(shù)據(jù),當(dāng)服務(wù)端再次啟動后,照樣可以收到客戶端發(fā)來的報(bào)文。
udp 本就無握手的過程,他的 udp connect() 也只是在本地創(chuàng)建 socket 信息。在服務(wù)端使用 netstat 是看不到 udp 五元組的 socket。
Golang 測試代碼
服務(wù)端代碼:
package main
import (
"fmt"
"net"
)
// UDP 服務(wù)端
func main() {
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 29999,
})
if err != nil {
fmt.Println("Listen failed, err: ", err)
return
}
defer listen.Close()
for {
var data [1024]byte
n, addr, err := listen.ReadFromUDP(data[:])
if err != nil {
fmt.Println("read udp failed, err: ", err)
continue
}
fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
}
}
客戶端代碼:
package main
import (
"fmt"
"net"
"time"
)
// UDP 客戶端
func main() {
socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(172, 16, 0, 46),
Port: 29999,
})
if err != nil {
fmt.Println("連接UDP服務(wù)器失敗,err: ", err)
return
}
defer socket.Close()
for {
time.Sleep(1e9 * 2)
sendData := []byte("Hello Server")
_, err = socket.Write(sendData)
if err != nil {
fmt.Println("發(fā)送數(shù)據(jù)失敗,err: ", err)
continue
}
fmt.Println("已發(fā)送")
}
}
總結(jié)
當(dāng) udp 服務(wù)端的機(jī)器可以連通且無異常時(shí),客戶端通常會顯示成功。但當(dāng)有異常時(shí),會有以下的情況:
當(dāng) ip 地址無法連通時(shí),udp 客戶端連接時(shí),通常會顯示成功。
當(dāng) udp 服務(wù)端程序關(guān)閉,但系統(tǒng)還存在時(shí),對方系統(tǒng)通過 icmp ECONNREFUSE 返回錯誤,客戶端會報(bào)錯。
當(dāng)對方有操作 iptables udp port drop 時(shí),客戶端也會顯示成功。
客戶端和服務(wù)端互通數(shù)據(jù),當(dāng)服務(wù)進(jìn)程掛了時(shí),UDP 客戶端不能立馬感知關(guān)閉狀態(tài),只有當(dāng)再次發(fā)數(shù)據(jù)時(shí)才會被對方系統(tǒng)回應(yīng) icmp ECONNREFUSE 異常報(bào)文,客戶端才能感知對方掛了。
本文名稱:讓人迷糊的SocketUDP連接問題
當(dāng)前路徑:http://fisionsoft.com.cn/article/dhjioeg.html


咨詢
建站咨詢
