UDP广播被发送,但未被接收。

4
我正在为一款游戏开发非常简单的大厅系统。每个客户端会定期通过UDP广播两个数据包,以便最初发现其他客户端并传输用户信息、准备状态等。该游戏正在同时开发适用于Windows和Linux(32位和64位)。
在Windows端,我已经完美地实现了大厅系统。当我在一个Windows机器上进入大厅时,其他机器上也会弹出此人的信息。同样地,准备状态检查和断开连接也可以立即检测到。换句话说,它正常工作。
现在问题来了:Linux。网络代码基本相同,只是需要进行一些必要的平台特定更改。我首先尝试了Windows<->Linux,使用Wireshark,我发现Linux方确实正在广播数据包并从Windows接收数据包,但游戏却没有捕获数据包。我发现了我的select语句中的一个错误(套接字而不是套接字+1),但是修复它没有帮助。Windows方正在广播数据包,但根本无法从Linux方接收数据包!
然后我尝试了Linux<->Linux,但是发现即使两台机器都在广播和接收数据包(再次通过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?此外,为什么它能在Windows<->Windows的情况下工作,但不能在Linux<->Linux或Linux<->Windows的情况下工作? 编辑:这里是socket创建/设置代码,如请求所示。计算出的示例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;
    }

3
相关内容:你应该使用组播而不是广播,这对于网络更好(以后您还可以通过多个网络路由事物,或者可以更轻松地开始使用不支持广播的ipv6)。另外,您考虑过使用一些处理大部分工作的库吗?这将减少可能出错的点数量。 - PlasmaHH
@Gemini14:我非常确定能够处理广播的路由器也能够处理多播。 - PlasmaHH
@Warren Young 愿望得到满足,我已经添加了代码显示套接字创建阶段、选项设置等。 - Gemini14
@PlasmaHH 哦,那么在这种情况下,我之前得到的信息是错误的。感谢您的澄清 :) - Gemini14
@Gemini14路由器通过丢弃广播来处理它们。广播数据包只能通过交换机传递。 - blaze
显示剩余2条评论
2个回答

4
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,即通配地址,这将允许套接字接收发送到该端口的任何地址的数据包。

1
太好了...这几乎就是解决方案!将Linux代码更改为使用INADDR_ANY使得两个Linux客户端能够相互看到。奇怪的是,我发现将Windows更改为使用INADDR_ANY并没有起作用。然而,在所有配置(Linux<->Win,Win<->Win)中,将其绑定到机器的实际地址确实起作用。谢谢! - Gemini14

0

猜测您忘记设置SO_BROADCAST套接字选项,导致广播数据包被过滤。


设置该选项实际上是我做的第一件事之一。 - Gemini14

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接