Unity Mirror NetworkDiscovery发现不了主机的解决方法

867 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

NetworkDiscovery找不到主机?

在使用Mirror的NetworkDiscovery时,经常会发现找不到主机,这是因为Mirrro的默认实现有一些问题,需要修改。

局域网UDP广播问题

使用Mirror的NetworkDiscovery查找服务器时,会发现找不到主机的情况。后来发现是由于在局域网中使用UDP广播的问题。在Mirror中,UDP广播使用的地址是IPAddress.Broadcast,即"255.255.255.255",这是物理层的广播地址。如果两个设备在同一个交换机下是没问题的,但是如果他们被隔离了,这个广播包是不会被路由器转发的,即便他们属于同一个子网,也不能接收到对方发出的广播包。

解决方法

解决的方法是不要使用255.255.255.255这个广播地址,而是使用子网广播地址。比如你的网段是192.168.1.0/24,那么该子网的广播地址就是192.168.1.255。但是,你不能直接在代码里面写死192.168.1.255,因为你不知道玩家的局域网是什么样的,比如可能是192.168.0.0/16,虽然不常见,但是也是有可能的。所以我们需要分析客户端的网络地址,并计算出相应的子网广播地址。我们需要修改的地方是NetworkDiscoveryBase.BroadcastDiscoveryRequest方法:

IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort);

这儿使用的是IPAddress.Broadcast, 说实话如果对于小白来说,不知道这个地址可能发送不到也就算了,Mirror的作者都是长期开发网络游戏的,居然没发现这个问题?可能是因为他们不太关注局域网联机吧。好了,我们需要的修改就是将这儿的广播地址替换成当前网段的广播地址。这儿我们先复习一下网段和IP地址吧。比如192.168.1.2这个IP,所属的网段为192.168.1.0/24,子网掩码为255.255.255.0,对于这样的网段,192.168.1.255就是它的广播地址。所以这儿的事情就是首先找到本机的IP,然后分析这个IP,最后计算出网段的广播地址。这些操作还是比较麻烦的,我整理了一下,可以直接用:

using System;
using System.Net;
using System.Net.Sockets;

public static class IPAddressExtensions
{
    public static IPAddress GetLocalIPAddress()
    {
        var host = Dns.GetHostEntry(Dns.GetHostName());
        foreach (var ip in host.AddressList)
        {
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            {                    
                return ip;
            }
        }       
        return IPAddress.None;
    }

    public static IPAddress GetBroadcastAddress(this IPAddress address)
    {
        IPAddress subnetMask = address.GetSubnetMask();
        byte[] ipAdressBytes = address.GetAddressBytes();
        byte[] subnetMaskBytes = subnetMask.GetAddressBytes();        

        byte[] broadcastAddress = new byte[ipAdressBytes.Length];
        for (int i = 0; i < broadcastAddress.Length; i++)
        {
            broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
        }
        return new IPAddress(broadcastAddress);
    }

    public static IPAddress GetSubnetMask(this IPAddress address)
    {
        uint firstOctet =  GetFirtsOctet(address);
        string subnetMask = "0.0.0.0";
        if (firstOctet >= 0 && firstOctet <= 127)
        {
            subnetMask = "255.0.0.0";
        }
        else if (firstOctet >= 128 && firstOctet <= 191)
        {
            subnetMask = "255.255.0.0";
        }
        else if (firstOctet >= 192 && firstOctet <= 223)
        {
            subnetMask = "255.255.255.0";
        }
        return IPAddress.Parse(subnetMask);
    }
    
    private static uint GetFirtsOctet(IPAddress iPAddress)
    {        
        byte[] byteIP = iPAddress.GetAddressBytes();
        uint ipInUint = (uint)byteIP[0];     
        return ipInUint;
    }

    
}

有了这个类后,上面广播的地方这样改就可以:


var broadcastAddress = IPAddressExtensions.GetLocalIPAddress().GetBroadcastAddress();            
IPEndPoint endPoint = new IPEndPoint(broadcastAddress, serverBroadcastListenPort);

WIFI网络问题

笔者在测试UDP广播时发现,对于移动设备,或者PC使用了无线网卡,做主机时会有一些奇怪的现象。首先是广播包迟迟接收不到,可能得很长时间才收到。然后是接受到广播包并发送回包(log是正常发送的),但是这些回包客户端收不到的。另外如果移动设备正好待机了,就会积压一些包,等恢复的瞬间发出大量的包,客户端就有可能收到。这个问题困扰了我两三天,各种方法都试过了都不好使,后来我确定应该不是我这边的问题,可能是网络环境的问题,换了一个路由器后就全正常了!我猜测之前使用的路由器对于UDP广播包有限制吧,可能和固件有关系,那个固件也是一个修改版的固件。