【问题标题】:Getting information about received UDP message is broadcast or unicast获取有关接收到的 UDP 消息的信息是广播还是单播
【发布时间】:2018-12-08 17:46:01
【问题描述】:

大家好!

我正在开发一个 Python 模块,该模块允许使用 UDP 套接字通过本地 IP 网络发现和配置客户端设备。 我正在基于现有协议构建我的解决方案,因此它可以与现有软件一起使用,但只能使用一个端口。 因此,我无法通过为不同的消息类型使用不同的端口来解决问题。 但协议的安全性很大程度上取决于应接受或拒绝哪个请求,具体取决于请求是针对广播还是直接针对设备。

我的主要问题是:我找不到一种方法(在 Python 中)将广播消息与单播消息(在同一端口/套接字上接收)区分开来。

此外,我还在寻找适用于 Linux 和 Windows 的解决方案。 此外,在一些必须运行此服务的(嵌入式、OpenWrt)设备上,安装用 cPython 编写的模块很困难,因此我强烈倾向于只使用 Linux 端的内置资源。 我也一直在使用 Python,但如果没有帮助,我也可以使用外部工具。

我很乐意提供任何帮助或指导。 (这是我在 stackoverflow 上的第一篇文章)

【问题讨论】:

    标签: python sockets networking udp broadcast


    【解决方案1】:

    所以我终于设法解决了我的问题。 汉密尔顿的回答非常合适,并使解决方案更接近,但正如他所提到的,它可能在 Windows 上不起作用(并且不会)。 Linux 解决方案几乎都适用于这两者,但与 Wondows 套接字有一些非常烦人的区别:

    • 不能将套接字绑定到广播地址。 (n.n.n.255255.255.255.255 都不起作用)
    • 您可以将套接字绑定到任播 (0.0.0.0) 或任何有效的本地地址,它将只接收单播消息。
    • 如果您在绑定之前设置了 socketopt SO_BROADCAST,它也会接收单播和广播消息(而且您不会区分)。
    • 如果要将多个套接字绑定到同一个端口,则必须在所有套接字上设置 socketopt SO_REUSEADDR
    • 如果您构造一个单播和一个“双播”套接字并绑定到同一个端口,那么您最终都会收到单播消息。
    • 如果您使用select(在Linux 示例中由这两个套接字提供)并且它们接收到相同的单播消息,select 只在第一轮返回一个。 (这是 Windows 10 的个人实验)

    我最终得到了这个解决方法:

    import platform
    from socket import gethostbyname, gethostname, socket, AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR
    from select import select
    
    PORT = 9999
    myPublicIP = gethostbyname(gethostname())
    platform = platform.system()
    
    bsock = socket(AF_INET, SOCK_DGRAM)
    usock = socket(AF_INET, SOCK_DGRAM)
    
    if platform == 'Windows':
        usock.setsockopt(SOL_SOCKET, SO_BROADCAST, 0)  # This isn't necessary
        usock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        bsock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
        bsock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        usock.bind(('', PORT))
        bsock.bind(('', PORT))
        # empty string means anycast
    elif platform == 'Linux':
        # on Linux SO_BROADCAST only affects the sending
        usock.bind((myPublicIP, PORT))
        bsock.bind(('255.255.255.255', PORT))
    
    while True:
        # This is the tricky part
        select([bsock, usock], [], [])
        # if a bcast msg received the first select gives back only the usock instead of both
        ready = select([bsock, usock], [], [])[0]
        # the second select will give back both at the same time
        if bsock in ready and usock in ready:
            bmsg = bsock.recvfrom(1024)
            umsg = usock.recvfrom(1024)
            in_msg, in_addr = bmsg
            print("Got broadcast msg", in_msg, "from", in_addr)
            if bmsg != umsg:
                in_msg, in_addr = umsg
                print("Got unicast msg", in_msg, "from", in_addr)
        else:
            if bsock in ready:
                in_msg, in_addr = bsock.recvfrom(1024)
                print("Got broadcast msg", in_msg, "from", in_addr)
            if usock in ready:
                in_msg, in_addr = usock.recvfrom(1024)
                print("Got unicast msg", in_msg, "from", in_addr)
    

    【讨论】:

      【解决方案2】:

      linux 有一个套接字选项(SO_PKTINFO),它实际上是理想的——它会准确报告数据报发送给谁。它的手册页说它是 linux-only。但是,windows WSARecvMsg 页面似乎表明那里也支持它。

      另请参阅此答案:How to differentiate between UDP Broadcasts and Unicasts?

      如果这还不够,最简单的方法是在接收端创建两个套接字。两者都可以使用相同的端口,但会绑定到不同的地址。例如,假设您的本地 IP 地址是 192.168.0.100。然后创建两个套接字:一个绑定到本地 IP,另一个绑定到广播地址。

      from socket import socket, AF_INET, SOCK_DGRAM
      from select import select
      
      usock = socket(AF_INET, SOCK_DGRAM)
      usock.bind(('192.168.0.100', 9999))
      
      bsock = socket(AF_INET, SOCK_DGRAM)
      bsock.bind(('255.255.255.255', 9999))
      
      while True:
          rrdy, wrdy, xrdy = select([usock, bsock], [], [])
          if usock in rrdy:
              in_msg, in_addr = usock.recvfrom(1024)
              print("Got unicast msg", in_msg, "from", in_addr)
          if usock in rrdy:
              in_msg, in_addr = bsock.recvfrom(1024)
              print("Got broadcast msg", in_msg, "from", in_addr)
      

      这绝对适用于 linux。我不确定它是否可以在 Windows 上运行,但可以预期它似乎是合理的。

      我在这里使用select 接收来自任一套接字的消息,但如果更易于管理,您可以轻松地为其中一个端口分离一个单独的线程。

      【讨论】:

      • 很酷的答案,我很惊讶你没有得到它的功劳。操作:accept?.
      • 感谢您快速而有用的回答。
      猜你喜欢
      • 2019-05-12
      • 2012-09-17
      • 2012-06-05
      • 1970-01-01
      • 2018-02-27
      • 1970-01-01
      • 2010-09-28
      • 2016-03-27
      • 2015-11-10
      相关资源
      最近更新 更多