【问题标题】:UDP broadcast being sent, but not receivedUDP 广播正在发送,但未收到
【发布时间】:2011-09-21 08:29:46
【问题描述】:

我正在开发一个非常简单的游戏大厅系统。每个客户端定期通过 UDP 广播两个数据包,以初步发现其他客户端并传输用户信息、准备情况等。该游戏正在为 Windows 和 Linux(32 位和 64 位)开发。

在 Windows 方面,我已经让大厅系统完美运行。当我在一台 Windows 机器上进入大厅时,该人会出现在其他机器上。同样,立即检测到就绪检查和断开连接。换句话说,它有效。

现在的问题是:Linux。网络代码实际上是相同的,只有一些必要的特定于平台的更改。我首先尝试了 WindowsLinux。使用Wireshark,我发现Linux端确实在广播数据包并从Windows机器接收它们,但游戏从未捕获到数据包。我在我的 select 语句中发现了一个错误(socket 而不是 socket + 1),但修复它并没有帮助。 Windows 机器正在广播数据包,但它根本没有从 Linux 机器接收数据包!

然后我尝试了 LinuxLinux,但发现即使两台机器都在广播和接收(再次通过 Wireshark 确认),两台机器上的游戏都无法“看到”数据包。

我很确定这不是防火墙问题(在任何一个平台上都关闭、测试、重新打开一切,没有任何变化)并且网络连接似乎正常(能够手动 ping 每个主机)。我还检查以确保端口确实可用(它们确实可用)。

下面是广播包的代码:

    void NetworkLinux::BroadcastMessage(const std::string &msg,
            const char prefix)
    {
        string data(prefix + msg);

        if (sendto(linuxSocket, data.c_str(), static_cast<int>(data.length()), 0,
        reinterpret_cast<sockaddr*>(&broadcastAddr), sizeof(broadcastAddr)) == -1)
        {
            Display_PError("sendto");
        }
    }

以及接收数据包的代码:

const Message NetworkLinux::ReceiveMessage()
    {
        char buffer[recvBufferLength];
        fill(buffer, buffer + recvBufferLength, 0);
        sockaddr_in sender;
        int senderLen = sizeof(sender);

        fd_set read_fds;
        FD_ZERO(&read_fds);
        FD_SET(linuxSocket, &read_fds);

        timeval time;
        time.tv_sec = 0;
        time.tv_usec = 16667; // microseconds, so this is ~1/60 sec

        int selectResult = select(linuxSocket + 1, &read_fds, 
                                      nullptr, nullptr, &time);
        if (selectResult == -1)
        {
            Display_PError("select");
        }
        else if (selectResult > 0) // 0 means it timed-out
        {
            int receivedBytes = recvfrom(linuxSocket, buffer, 
                        recvBufferLength, 0, reinterpret_cast<sockaddr*>(&sender),
                        reinterpret_cast<socklen_t*>(&senderLen));

            if (receivedBytes == -1)
            {
                Display_PError("recvfrom");
            }
            else if (receivedBytes > 0)
            {
                Message msg;
                msg.prefix = buffer[0];
                msg.msg = string(buffer + 1, buffer + receivedBytes);
                msg.address = sender.sin_addr;
                return msg;
            }
        }
        Message m;
        m.prefix = 'N';
        return m;
    }

当我看到数据包到达时,为什么select() 总是返回 0?而且,为什么它在WindowsWindows场景下可以工作,而在LinuxLinux或者LinuxWindows下不行?

编辑:这是套接字创建/设置代码,根据要求。计算的示例 IP/广播地址为:192.168.1.3/192.168.1.255、192.168.1.5/192.168.1.255,与 Windows 端生成和使用的相匹配。

bool NetworkLinux::StartUp()
    {
        // zero addr structures
        memset(&machineAddr, 0, sizeof machineAddr);
        memset(&broadcastAddr, 0, sizeof broadcastAddr);

        // get this machine's IP and store it
        machineAddr.sin_family = AF_INET;
        machineAddr.sin_port = htons(portNumber);
        inet_pton(AF_INET, GetIP().c_str(), &(machineAddr.sin_addr));

        // get the netmask and calculate/store the correct broadcast address
        broadcastAddr.sin_family = AF_INET;
        broadcastAddr.sin_port = htons(portNumber);
        GetNetMask();
        broadcastAddr.sin_addr.s_addr = machineAddr.sin_addr.s_addr | ~netmask;

        char bufIP[INET_ADDRSTRLEN], bufBroadcast[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &machineAddr.sin_addr, bufIP, INET_ADDRSTRLEN);
        inet_ntop(AF_INET, &broadcastAddr.sin_addr, bufBroadcast,
                INET_ADDRSTRLEN);
        Log("IP is: " + string(bufIP) + "\nBroadcast address is: "
                + string(bufBroadcast));

        // create socket
        linuxSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (linuxSocket == -1)
        {
            Display_PError("socket");
            return false;
        }
        Log("Socket created.");

        // switch to broadcast mode
        int broadcast = 1;
        if (setsockopt(linuxSocket, SOL_SOCKET, SO_BROADCAST, &broadcast,
                sizeof broadcast) == -1)
        {
            Display_PError("setsockopt");
            close(linuxSocket);
            return false;
        }
        Log("Socket switched to broadcast mode.");

        // bind it (this simplifies things by making sure everyone is using the same port)
        if (bind(linuxSocket, reinterpret_cast<sockaddr*>(&machineAddr),
                sizeof(machineAddr)) == -1)
        {
            Display_PError("bind");
            close(linuxSocket);
            return false;
        }
        Log("Socket bound.");

        return true;
    }

【问题讨论】:

  • 一些相关的:你应该使用多播而不是广播,这对网络更好(以后你也可以通过多个网络路由,或者可以更容易地开始使用不支持广播的 ipv6) .另外,您是否考虑过使用一些可以为您处理大部分内容的库?这应该会为您减少可能出现的错误点的数量。
  • 您还需要展示如何设置套接字。 sockaddr_insetsockopt() 等等。
  • @PlasmaHH 我以前研究过多播,但读到有些路由器不支持它;但是,我将来仍然可以研究它。无论如何,这让我感到困扰只是因为它应该工作,但没有。我查看的大多数库都需要某种服务器地址,这在这种情况下无济于事,因为客户端还不知道 LAN 上有谁。
  • @Gemini14:我很确定可以处理广播的路由器也可以处理多播。
  • @Warren Young 希望同意,我添加了显示套接字创建阶段、选项设置等的代码。

标签: c++ linux network-programming udp


【解决方案1】:
machineAddr.sin_port = htons(portNumber);
inet_pton(AF_INET, GetIP().c_str(), &(machineAddr.sin_addr));

bind(linuxSocket, reinterpret_cast<sockaddr*>(&machineAddr),

这将套接字绑定为仅接受发送到portNumber 的数据包,该数据包位于GetIP 返回的机器地址,这可能不是您想要的,因为您还希望接收发送到广播地址端口的数据包。您可能希望将sin_addr 设置为INADDR_ANY,即通配符地址,这将允许套接字接收发送到以某种方式到达机器的任何地址的端口的数据包。

【讨论】:

  • 优秀...这(几乎)完全是解决方案!将 Linux 代码更改为使用 INADDR_ANY 使得两个 Linux 客户端可以看到彼此。奇怪的是,我发现将 Windows 更改为使用 INADDR_ANY 没有 工作。但是,在所有配置(LinuxWin、WinWin)中,将其绑定到机器的实际地址确实 工作。谢谢!
【解决方案2】:

猜你忘记设置 SO_BROADCAST 套接字选项,广播包被过滤掉了。

【讨论】:

  • 设置该选项实际上是我做的第一件事。
猜你喜欢
  • 2021-07-30
  • 1970-01-01
  • 2010-11-28
  • 2015-03-14
  • 2020-02-21
  • 1970-01-01
  • 2021-12-18
  • 1970-01-01
  • 2019-10-27
相关资源
最近更新 更多