Golang中UDP广播的实现与问题求助

Golang中UDP广播的实现与问题求助 你好,

我正在尝试通过UDP广播与另一台通过以太网电缆直接连接的计算机进行通信。

以下是服务器端代码:

package main

import (
	"fmt"
	"net"
)

func main() {
	//本地地址
	la, err := net.ResolveUDPAddr("udp4", "0.0.0.0:8000")
	if err != nil {
		fmt.Println(err)
		return
	}

	//监听
	c, err := net.ListenUDP("udp4", la)
	if err != nil {
		fmt.Println(err)
		return
	}

	//读取
	bs := make([]byte, 4096)
	n, ra, err := c.ReadFromUDP(bs)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(n, ra)
}

以下是客户端代码:

package main

import (
	"fmt"
	"net"
)

func main() {
	//本地地址
	la, err := net.ResolveUDPAddr("udp4", "1.2.3.4:8000")
	if err != nil {
		fmt.Println(err)
		return
	}

	ra, err := net.ResolveUDPAddr("udp4", "255.255.255.255:8000")
	if err != nil {
		fmt.Println(err)
		return
	}

	//拨号连接
	c, err := net.DialUDP("udp4", la, ra)
	if err != nil {
		fmt.Println(err)
		return
	}

	//写入
	n, err := c.Write([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%d bytes written\n", n)
}

情况1:如果这些电脑有相同的网关地址,则没有问题。 情况2:如果这些电脑在同一网络子组中,例如 1.2.3.4 和 1.2.3.5,也没有问题。 情况3:如果这些电脑具有随机的IP地址,例如 1.2.3.4 和 4.3.2.1,我就无法进行通信。

我还检查了所有网络接口都具备 net.FlagBroadcast 标志。

我计划使用这种通信方式来查找另一台电脑的IP地址,该电脑将拥有一个随机的静态IP地址,并且将通过以太网电缆直接连接到我的电脑。许多设备都有这种“查找设备”的功能。

UDP广播方法是正确的吗?

需要帮助 🙂

此致


更多关于Golang中UDP广播的实现与问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

听起来很不错,你介意分享一下使用原始以太网帧的最小测试用例吗?

更多关于Golang中UDP广播的实现与问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢,

我通过使用原始以太网帧解决了我的问题。

此致

如果路由配置正确,UDP广播将正常工作。 从连接到不路由该地址的网络的随机地址发出的UDP广播将无法工作。 你可能需要使用类似ARP的协议来代替。

当然,我可以参考 Matt Lahyer 的以下文章。我使用了他的库来处理原始以太网帧。

https://mdlayher.com/blog/network-protocol-breakdown-ethernet-and-go/

GitHub

mdlayher/ethernet

avatar

ethernet 包实现了 IEEE 802.3 以太网 II 帧和 IEEE 802.1Q VLAN 标签的封送(marshaling)与解封送(unmarshaling)。采用 MIT 许可证。

GitHub

mdlayher/raw

avatar

raw 包支持在网络接口的设备驱动层进行数据读写。采用 MIT 许可证。

在Go中实现UDP广播时,你遇到的情况3(不同子网)无法通信是正常的网络行为。UDP广播默认只在本地子网内有效,无法跨子网传播。对于直接以太网连接的情况,需要确保两台计算机在同一IP子网中。

以下是修改后的服务器端代码,可以监听所有接口的广播:

package main

import (
    "fmt"
    "net"
)

func main() {
    // 监听所有接口的8000端口
    addr, err := net.ResolveUDPAddr("udp4", ":8000")
    if err != nil {
        fmt.Println("ResolveUDPAddr error:", err)
        return
    }

    conn, err := net.ListenUDP("udp4", addr)
    if err != nil {
        fmt.Println("ListenUDP error:", err)
        return
    }
    defer conn.Close()

    fmt.Println("Server listening on :8000")

    // 设置广播权限
    conn.SetReadBuffer(1024 * 1024)

    buffer := make([]byte, 4096)
    for {
        n, clientAddr, err := conn.ReadFromUDP(buffer)
        if err != nil {
            fmt.Println("ReadFromUDP error:", err)
            continue
        }
        
        fmt.Printf("Received %d bytes from %s: %v\n", 
            n, clientAddr.String(), buffer[:n])
        
        // 可以回复消息
        _, err = conn.WriteToUDP([]byte("ACK"), clientAddr)
        if err != nil {
            fmt.Println("WriteToUDP error:", err)
        }
    }
}

以下是修改后的客户端代码,使用更灵活的广播方式:

package main

import (
    "fmt"
    "net"
    "time"
)

func main() {
    // 获取广播地址
    broadcastAddr, err := net.ResolveUDPAddr("udp4", "255.255.255.255:8000")
    if err != nil {
        fmt.Println("ResolveUDPAddr error:", err)
        return
    }

    // 创建连接,不绑定特定本地地址
    conn, err := net.DialUDP("udp4", nil, broadcastAddr)
    if err != nil {
        fmt.Println("DialUDP error:", err)
        return
    }
    defer conn.Close()

    // 设置广播权限
    conn.SetWriteBuffer(1024 * 1024)

    // 发送广播消息
    message := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    for i := 0; i < 5; i++ {
        n, err := conn.Write(message)
        if err != nil {
            fmt.Println("Write error:", err)
            return
        }
        
        fmt.Printf("Sent %d bytes via broadcast\n", n)
        time.Sleep(1 * time.Second)
    }
}

对于直接电缆连接的情况,建议使用以下方法:

  1. 确保在同一子网:为两台计算机手动配置同一子网的IP地址,例如:

    • 计算机A: 192.168.1.10/24
    • 计算机B: 192.168.1.20/24
  2. 使用更可靠的设备发现方法

package main

import (
    "fmt"
    "net"
    "time"
)

func discoverDevices() {
    // 尝试多个可能的广播地址
    broadcastAddresses := []string{
        "255.255.255.255:8000",
        "192.168.1.255:8000",
        "192.168.0.255:8000",
        "10.255.255.255:8000",
    }

    for _, addr := range broadcastAddresses {
        go tryBroadcast(addr)
    }
    
    time.Sleep(10 * time.Second)
}

func tryBroadcast(addr string) {
    broadcastAddr, _ := net.ResolveUDPAddr("udp4", addr)
    conn, err := net.DialUDP("udp4", nil, broadcastAddr)
    if err != nil {
        return
    }
    defer conn.Close()
    
    conn.Write([]byte("DISCOVER"))
}
  1. 使用多播(组播)作为替代方案
package main

import (
    "fmt"
    "net"
)

func multicastExample() {
    // 加入多播组
    multicastAddr, _ := net.ResolveUDPAddr("udp4", "224.0.0.1:9999")
    localAddr, _ := net.ResolveUDPAddr("udp4", ":9999")
    
    conn, err := net.ListenMulticastUDP("udp4", nil, multicastAddr)
    if err != nil {
        fmt.Println("ListenMulticastUDP error:", err)
        return
    }
    defer conn.Close()
    
    // 监听多播消息
    buffer := make([]byte, 1024)
    for {
        n, src, err := conn.ReadFromUDP(buffer)
        if err != nil {
            continue
        }
        fmt.Printf("Multicast from %s: %s\n", src, buffer[:n])
    }
}

UDP广播确实是实现设备发现的常用方法,但需要确保网络配置正确。对于直接电缆连接,最可靠的方法是手动配置IP地址在同一子网,或者使用链路本地地址(169.254.0.0/16)。

回到顶部