Как улучшить синхронизацию данных с помощью Unity многоадресного сокета UDP

Я изучаю простые сети с использованием Unity и Sockets, и у меня возникают проблемы с синхронизацией данных между клиентом и сервером. Я знаю, что есть и другие варианты использования Unity Networking, но прежде чем двигаться дальше, я хочу лучше понять, как улучшить свой код с помощью системных библиотек.

В этом примере я просто пытаюсь передать свою позицию мыши через многоадресный сокет UDP. Я кодирую строку в массив байтов и отправляю этот массив один раз за кадр. Я знаю, что отправка этих значений в виде строки неоптимальна, но, если только это не является узким местом, я предполагаю, что можно вернуться, чтобы оптимизировать это позже.

Im, мой сервер настройки отправляет значения со скоростью 60 кадров в секунду, и клиент читает с той же скоростью. Проблема, с которой я сталкиваюсь, заключается в том, что когда клиент получает значения, он обычно получает сразу несколько значений. Если я регистрирую значения, которые я получил с ----- между каждым кадром, я обычно получаю вывод следующим образом:

------
------
------
------
------
------
------
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));
        }
    }
}

Если бы кто-нибудь мог пролить свет на то, что я могу сделать, чтобы улучшить синхронизацию, это было бы большим подспорьем.


person jonathan topf    schedule 10.07.2020    source источник
comment
Вы знаете, что _socket.Receive (_byteBuffer); — это блокирующий вызов, который вам не нужен в основном потоке. Скорее выполняйте весь процесс получения и преобразования строк в фоновом потоке и отправляйте только полученные сообщения обратно в основной поток. Также отправитель, вероятно, должен просто преобразовать и отправить свой материал в потоке или задаче. По-прежнему не будет реальной гарантии, что вы поймаете ровно один пакет за кадр. В общем, вы все равно можете захотеть обработать только самый последний полученный пакет за кадр и удалить предыдущие.   -  person derHugo    schedule 10.07.2020
comment
@derHugo Абсолютно - я обязательно перенесу это в отдельную ветку. Тем не менее, прямо сейчас каждый кадр завершается достаточно быстро, поэтому не похоже, что блокировка способствует этой конкретной проблеме.   -  person jonathan topf    schedule 10.07.2020
comment
Абсолютно не эксперт по сетям подумал, но, может быть, кто-нибудь в сети ждет, пока пакет не станет достаточно большим для отправки, а затем снова разбивает их на фактические сообщения? Вы пытались использовать UdpClient и позволить ему обрабатывать базовые сокеты?   -  person derHugo    schedule 10.07.2020
comment
@derHugo Отличное наблюдение. Я попробовал простой хак, дополнив отправляемую строку пробелами, и действительно, пакеты распределялись более равномерно. Я изучу UDPClient, но также посмотрю, смогу ли я найти возможность отправлять пакеты меньшего размера, чтобы эта версия работала.   -  person jonathan topf    schedule 10.07.2020


Ответы (1)


Сетевой ввод-вывод подвержен большому количеству внешних воздействий, а протокол TCP/IP предъявляет мало требований. Конечно, ни один из них не гарантировал бы желаемого поведения.

К сожалению, без хорошего минимального, полного и проверяемого примера кода невозможно проверить, находится ли ваш сервер в факт отправки данных с заявленным вами интервалом. Вполне возможно, что у вас есть ошибка, которая вызывает такое поведение.

Но если предположить, что сам код идеален, то при использовании UDP все равно нет никаких гарантий, что дейтаграммы не будут группироваться в какой-то момент по пути, так что в сетевом буфере одновременно появляется большое количество дейтаграмм. Я ожидаю, что это произойдет с большей частотой, когда дейтаграммы отправляются через несколько сетевых узлов (например, коммутатор и особенно через Интернет), но это может так же легко произойти, когда сервер и клиент находятся на одном компьютере.

По иронии судьбы, один из вариантов, который может заставить дейтаграммы рассредоточиться еще больше, заключается в дополнении каждой дейтаграммы дополнительными байтами. Точное количество требуемых байтов будет зависеть от точного сетевого маршрута; чтобы сделать это идеально, может потребоваться написать некоторую логику калибровки, которая пробует разные величины заполнения, пока код не увидит, что дейтаграммы прибывают с ожидаемыми интервалами.

Но это значительно увеличило бы сложность вашего сетевого кода ввода-вывода, но все равно не гарантировало бы желаемое поведение. И это имеет некоторые очевидные негативные побочные эффекты, в том числе дополнительные накладные расходы в сети (что, конечно, не оценят люди, использующие лимитные сетевые подключения), а также увеличение вероятности полного отбрасывания дейтаграммы UDP.

Из вашего вопроса неясно, действительно ли вашему проекту требуется многоадресный UDP, или это просто что-то в вашем коде, потому что это то, что использовалось в каком-то учебнике или другом примере, которому вы следуете. Если многоадресная рассылка на самом деле не является обязательным требованием, вам определенно следует попробовать использовать прямой UDP без многоадресной рассылки.

FWIW: Я бы не реализовал это так, как вы. Вместо этого я бы использовал асинхронные операции получения, чтобы мой клиент получал дейтаграммы в момент их доступности, а не только периодически проверял каждый кадр рендеринга. Я бы также включил порядковый номер в дейтаграмму и отбросил (проигнорировал) любые дейтаграммы, которые приходят не по порядку (т. е. где порядковый номер строго не больше, чем самый последний уже полученный порядковый номер). Такой подход улучшит (возможно, лишь немного) скорость отклика, но также будет обрабатывать ситуации, когда дейтаграммы поступают не по порядку или дублируются (две из трех основных проблем доставки, с которыми приходится сталкиваться при использовании UDP… Доставка).

person Peter Duniho    schedule 10.07.2020
comment
Спасибо за исчерпывающий ответ. Хотя это, скорее всего, не самое эффективное решение, добавление данных действительно решило проблему, с которой я столкнулся в этой ситуации. - person jonathan topf; 15.07.2020