Unity Mirror 之NetworkDiscovery局域网服务器查找详解(2)

292 阅读3分钟

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

问题处理

主机断开后没有通知?

前一篇中的流程有一个主要的问题是,主机断开(关闭),客户端并不会收到任何通知,如果之前客户端已经搜索到该主机,那么该主机就一直停留在客户端的服务器列表上。由于UDP本来就是无状态的,你只知道之前有过,但是后来就一直没有了。所以我给出的方法就是记录每次服务器回包的时间,如果一段时间后离上次回包的时间大于某个阈值,则可以认为服务器已经关闭或者断开了。这样就在UI上把他去掉。

NetworkDiscovery的一些细节

关于 serverId

注意到NetworkDiscovery返回的服务器消息 ServerResponse 中包含了 serverId,并且这个serverId用作客户端服务器字典的key。我们先看一下这个serverId的注释:

Prevent duplicate server appearance when a connection can be made via LAN on multiple NICs

意思是对于UDP客户端,如果服务器上有多个网络接口(NIC),比如多个网卡,由于广播消息是不指定IP的,所以这多个网卡都会收到广播消息,因此通过UDP返回的包里面,是包含不同的源IP的。这样对于这种服务器,客户端那边就有可能收到来自于一个服务器的多个回包。为了避免客户端的服务器列表UI上出现重复的服务器条目,因此Mirror推荐对于每个服务器给予一个唯一ID。但是这唯一ID并不是唯一的,而是使用了一个随机数,当然从概率上说很难重复:

ServerId = RandomLong();

关于 ServerResponse.uri

ServerResponse中的uri其实在构建出ServerResponse时是不能用的,因为它的Host不一定是IP地址。当服务器返回握手包时,这uri是来自NetworkManager底层的transport的:

return new ServerResponse
{
    serverId = ServerId,
    uri = transport.ServerUri()
};

问题是,这个uri的Host并不是服务器的IP地址。我使用的是KcpTransport,是这么实现的:

public override Uri ServerUri()
{
    UriBuilder builder = new UriBuilder();
    builder.Scheme = Scheme;
    builder.Host = Dns.GetHostName();
    builder.Port = Port;
    return builder.Uri;
}

这儿Dns.GetHostName()返回的是A string that contains the DNS host name of the local computer.,即电脑的名字,所以这个uri不能用来连接服务器。因此在NetworkDiscovery内部,ProcessResponse函数中进行了处理。ProcessRequest函数是一个定义在NetworkDiscoveryBase中的虚函数,用于对服务器生成的握手包进行进一步的处理(在ReceiveGameBroadcastAsync中调用)。

protected override void ProcessResponse(ServerResponse response, IPEndPoint endpoint)
{
    // we received a message from the remote endpoint
    response.EndPoint = endpoint;

    // although we got a supposedly valid url, we may not be able to resolve
    // the provided host
    // However we know the real ip address of the server because we just
    // received a packet from it,  so use that as host.
    UriBuilder realUri = new UriBuilder(response.uri)
    {
        Host = response.EndPoint.Address.ToString()
    };
    response.uri = realUri.Uri;

    OnServerFound.Invoke(response);
}

这个函数里面,将uri的Host替换成了EndPont的Address,这是一个真实的IP。 关注这些细节(包括serverId)是为了我们自己实现自定义的NetworkDiscovery时避免出问题。