如何使用 Unity 和多播 UDP 套接字改进数据同步

How to improve data synchronization using Unity an multicast UDP socket

我正在使用 Unity 和套接字自学一些简单的网络,但我 运行 遇到了在客户端和服务器之间同步数据的问题。我知道还有其他使用 Unity Networking 的选项,但在我继续之前,我想更好地了解如何使用系统库改进我的代码。

在这个例子中,我只是想通过多播 UDP 套接字传输我的鼠标位置。我正在将一个字符串编码成一个字节数组,并每帧发送一次该数组。我知道将这些值作为字符串发送不是最优的,但是,除非这可能是瓶颈,否则我假设可以稍后再回来优化它。

我的设置服务器正在以 60 fps 的速度发送值,并且客户端正在以相同的速率读取。我遇到的问题是,当客户端收到值时,它通常会同时收到很多值。如果我在每帧之间用 ----- 记录收到的值,我通常会得到这样的输出:

------
------
------
------
------
------
------
119,396
91,396
45,391
18,379
-8,362
-35,342
-59,314
------
------
------
------
------
------
------

我希望不同步的更新周期会导致每帧接收两个值,但我不确定是什么导致了较大的差异。

这是服务器代码:

using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class Server : MonoBehaviour
{
    Socket _socket;

    void OnEnable ()
    {
        var ip = IPAddress.Parse ("224.5.6.7");
        var ipEndPoint = new IPEndPoint(ip, 4567);

        _socket = new Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        _socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption (ip));
        _socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 2);
        _socket.Connect(ipEndPoint);
    }

    void OnDisable ()
    {
        if (_socket != null)
        {
            _socket.Close();
            _socket = null;
        }
    }

    public void Send (string message)
    {
        var byteArray = Encoding.ASCII.GetBytes (message);
        _socket.Send (byteArray);
    }
}

而客户:

using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class Client : MonoBehaviour
{
    Socket _socket;
    byte[] _byteBuffer = new byte[16];

    public delegate void MessageRecievedEvent (string message);
    public MessageRecievedEvent messageWasRecieved = delegate {};
    

    void OnEnable ()
    {
        var ipEndPoint = new IPEndPoint(IPAddress.Any, 4567);
        var ip = IPAddress.Parse("224.5.6.7");

        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        _socket.Bind (ipEndPoint);
        _socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ip,IPAddress.Any));
    }

    void Update ()
    {
        while (_socket.Available > 0)
        {
            for (int i = 0; i < _byteBuffer.Length; i++) _byteBuffer[i] = 0;
            _socket.Receive (_byteBuffer);
            messageWasRecieved (Encoding.ASCII.GetString (_byteBuffer));
        }
    }
}

如果有人能阐明我可以做些什么来改进同步,那将是一个很大的帮助。

网络I/O受大量外界影响,TCP/IP作为协议要求不高。当然 none 可以保证您似乎想要的行为。

不幸的是,如果没有好的 Minimal, Complete, and Verifiable code example,则无法验证您的服务器是否确实按照您声明的时间间隔发送数据。您完全有可能遇到导致此行为的错误。

但是如果我们假设代码本身是完美的,那么在使用 UDP 时仍然不能保证数据报不会在途中的某个时刻被批处理,以至于大量出现在网络缓冲区中立刻。我希望当数据报通过多个网络节点(例如交换机,尤其是通过 Internet)发送时,这种情况会以更高的频率发生,但当服务器和客户端都在同一台计算机上时,它也很容易发生。

具有讽刺意味的是,可能强制数据报进一步分散的一个选项是用额外的字节填充每个数据报。所需的确切字节数取决于确切的网络路由;要“完美”地做到这一点,可能需要编写一些校准逻辑来尝试不同的填充量,直到代码看到数据报以预期的间隔到达。

但这会显着增加您的网络 I/O 代码的复杂性,但仍然无法 保证 您想要的行为。它有一些明显的负面影响,包括网络上的额外开销(使用按流量计费的网络连接的人肯定不会喜欢的东西),以及增加 UDP 数据报被完全丢弃的可能性。

从您的问题中不清楚您的项目是否真的需要多播 UDP,或者这是否只是您代码中的某些内容,因为这正是您所关注的某些教程或其他示例所使用的内容。如果实际上不需要多播,那么您绝对应该尝试的另一件事是使用不带多播的直接 UDP。

FWIW:我不会按照您的方式实施。相反,我会使用异步接收操作,以便我的客户端在数据报可用时立即接收数据报,而不是仅定期检查渲染的每一帧。我还会在数据报中包含一个序列号,并丢弃(忽略)任何乱序到达的数据报(即,序列号并不严格大于已收到的最新序列号)。这种方法将提高(可能只是轻微的)响应能力,但也将处理数据报无序到达或重复的情况(UDP 将遇到的三个主要交付问题中的两个......第三个当然是失败交货)。