一次UDP通迅的問題排查

通迅模型

(A-->B-->A):A經過UDP發送數據給B(A能夠是指定目的地,也能夠是廣播發送消息給B),B收到消息後根據來源地址和端口向A回發消息,就這麼簡單的一個通迅過程。html

關於golang udp方面的講解能夠參考下https://colobu.com/2016/10/19/Go-UDP-Programming/這篇文件,講的挺詳細。何時udp socket是connected狀態,何時是unconnected狀態,何時用read/write,何時用readFromUDP/writeToUDP都有說明,寫的挺好。golang

來段code demo吧

服務端:編程

func main() {
    // 建立監聽
    sock, err := net.ListenUDP("udp4", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 9000,
    })
    if err != nil {
        fmt.Println("listen udp failure!", err)
        return
    }
    defer sock.Close()

    for {
        // 讀取數據
        data := make([]byte, 4096)
        readNum, rAddr, err := sock.ReadFromUDP(data)
        if err != nil {
            fmt.Println("read data failure!", err)
            continue
        }
        fmt.Println("read byte number:",readNum, "remote addr:", rAddr)
        fmt.Println("received: ", string(data[:readNum]))

        // 發送數據
        sendBts := []byte("hello client!")
        _, err = sock.WriteToUDP(sendBts, rAddr)
        if err != nil {
            fmt.Println("send msg back failure!", err)
            return
        } else {
            fmt.Println("send msg mack ok.", string(sendBts), "to:", rAddr)
        }
    }
}

客戶端socket

func main() {
    sock, err := net.DialUDP("udp", nil, &net.UDPAddr{
        IP:   net.IPv4(255, 255, 255, 255), // 廣播地址
        Port: 9000,
    })
    // 建立監聽
    //soct, err := net.ListenUDP("udp4", &net.UDPAddr{
    //    IP:   net.IPv4(0, 0, 0, 0),
    //    Port: 9001,
    //})
    if err != nil {
        fmt.Println("connect udp failure!", err)
        return
    }
    defer sock.Close()

    // 發送數據
    sendBts := []byte("hello server!")
    _, err = sock.Write(sendBts)
    if err != nil {
        fmt.Println("send msg failure!", err)
        return
    }

    // 接收數據
    data := make([]byte, 4096)
    for {
        readNum, rAddr, err := sock.ReadFromUDP(data)
        if err != nil {
            fmt.Println("read udp failure!", err)
            return
        }
        fmt.Println("read byte number:", readNum, "from:", rAddr)
        fmt.Println("received: ", string(data[:readNum]))
    }
}

問題呈現

運行下上面的demo:
服務端測試

read byte number: 13 remote addr: 10.200.2.50:54404
received:  hello server!
send msg mack ok. hello client! to: 10.200.2.50:54404

客戶端spa

// nothing

發現客戶端啥都沒輸出,可見我們遇到問題了。服務端明明發送數據了,並且也指定了目的ip和端口號了!爲何客戶端沒收到呢?客戶端dial的時候,發廣播消息不會幀聽本地端口??或者說廣播消息的時候,偵聽的不是同一個網卡?帶着問題再來改下客戶端的代碼。code

// 啓用客戶端註釋掉的那段代碼
func main() {
    // 建立監聽
    sock, err := net.ListenUDP("udp4", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 9001, // 再同一臺主機上測試,要跟服務端綁定不一樣的端口;若是是不一樣的主機,能夠用相同的端口
    })
    if err != nil {
        fmt.Println("connect udp failure!", err)
        return
    }
    defer sock.Close()

    // 發送數據
    sendBts := []byte("hello server!")
    _, err = sock.WriteToUDP(sendBts, &net.UDPAddr{
        IP:   net.IPv4(255, 255, 255, 255),
        Port: 9000,
    })
    if err != nil {
        fmt.Println("send msg failure!", err)
        return
    }

    // 接收數據
    data := make([]byte, 4096)
    for {
        readNum, rAddr, err := sock.ReadFromUDP(data)
        if err != nil {
            fmt.Println("read udp failure!", err)
            return
        }
        fmt.Println("read byte number:", readNum, "from:", rAddr)
        fmt.Println("received: ", string(data[:readNum]))
    }
}

再次運行下
服務端server

read byte number: 13 remote addr: 10.200.2.50:54404
received:  hello server!
send msg mack ok. hello client! to: 10.200.2.50:54404

客戶端htm

received:  hello client!

看結果還真印證了沒有偵聽就不能收消息的猜想。爲何dial的時候沒有偵聽呢?記得c代碼會隱式的綁定(參考https://stackoverflow.com/questions/54443823/udp-socket-sendto-implicit-bindhttps://www.cnblogs.com/skyfsm/p/6287787.html),語言之間會有不一樣的封裝嗎?我們就一同來看看golang的源碼。
udp.jpg
看到dialUDP和listenUDP這兩個方法,也就紅框裏的參數不同,其餘能夠說都同樣。再繼續往下看internetSocket -> socket方法
image.png
注意到紅框裏都這段代碼,它是給udp偵聽/綁定用的,可是有個前提:要存在local addr(laddr)並且要不存在remote addr(raddr)。blog

那再改下客戶端DialUDP的代碼

sock, err := net.DialUDP("udp", &net.UDPAddr{
    IP:   net.IPv4(0,0,0,0),
    Port: 9001,
}, nil)

再次運行客戶端的代碼。

connect udp failure! dial udp 0.0.0.0:9001: missing address

獲得了一個缺乏地址的一個錯誤,那說明DialUDP是不支持隱式綁定的。

總結

在使用udp編程的時候呢,若是是單純的發送數據,可使用DialUDP來得到socket句柄,而後調用write方法發送數據。若是想要收消息無論是客戶端仍是服務端,都得用ListenUDP,偵聽/綁定後才能夠接收消息。

相關文章
相關標籤/搜索