Как устранить возможные проблемы потерянных пакетов с сокетами .NET и TCP? - PullRequest
4 голосов
/ 20 января 2012

Мне нужна помощь в выяснении того, как устранить проблему, свидетелем которой я являюсь при передаче большого объема данных через TCP с использованием сокетов .NET.

В двух словах, при запуске клиентского приложения оно подключается кконкретный порт на сервере.После подключения сервер начинает отправку данных в реальном времени клиенту, который отображает информацию в виде тикера.Сервер поддерживает несколько клиентских рабочих станций, поэтому данные будут отправляться через несколько портов (несколько сокетов).

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

В настоящее время каждому транслируемому сообщению предшествует 4-байтовое значение, определяющее длину этого сообщения.Когда мы получаем данные в клиенте, мы добавляем данные в буфер (поток), пока не получим это количество байтов.Любые дополнительные байты считаются началом следующего сообщения.Опять же, это прекрасно работает, пока я не увеличу частоту.

В моем тесте я отправляю пакет размером около 225 байтов, за которым следует один из примерно 310 КБ, а другой - около 40 КБ.Отправка сообщения каждую 1 секунду работает без сбоев с 12 запущенными клиентами.Увеличив частоту до 1/2 секунды, я в итоге увидел, что один из дисплеев клиента завис.Переходя к 1/4 секунды, я могу воспроизвести проблему всего с 4 клиентами в течение нескольких секунд.

Глядя на мой код (который я могу предоставить, если необходимо), я вижу, что все клиентыполучают данные, но каким-то образом информация «вышла из синхронизации», и ожидаемое значение длины огромно (в диапазоне 100 миллионов).В результате мы просто продолжаем читать данные и никогда не воспринимаем конец сообщения.

Мне нужен либо лучший подход, либо способ убедиться, что я получаю ожидаемые данные, а не теряю пакеты.Можете ли вы помочь?

ОБНОВЛЕНИЕ

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

Итак, более точно описать то, что я ищу, это:

  1. Чтобы понять, что происходит.Это поможет мне определить возможное решение или, как минимум, установить пороги для надежного поведения.

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

Вот код, который я запускаю в клиентских (принимающих) приложениях:

public void StartListening(SocketAsyncEventArgs e)
{
    e.Completed += SocketReceive;
    socket.ReceiveAsync(e);
}

private void SocketReceive(Object sender, SocketAsyncEventArgs e)
{
    lock (_receiveLock)
    {
        ProcessData(e.Buffer, e.BytesTransferred);

        socket.ReceiveAsync(e);
    }
}

private void ProcessData(Byte[] bytes, Int32 count)
{
    if (_currentBuffer == null)
        _currentBuffer = new ReceiveBuffer();

    var numberOfBytesRead = _currentBuffer.Write(bytes, count);

    if (_currentBuffer.IsComplete)
    {
        // Notify the client that a message has been received (ignore zero-length, "keep alive", messages)
        if (_currentBuffer.DataLength > 0)
            NotifyMessageReceived(_currentBuffer);

        _currentBuffer = null;

        // If there are bytes remaining from the original message, recursively process
        var numberOfBytesRemaining = count - numberOfBytesRead;

        if (numberOfBytesRemaining > 0)
        {
            var remainingBytes = new Byte[numberOfBytesRemaining];
            var offset = bytes.Length - numberOfBytesRemaining;

            Array.Copy(bytes, offset, remainingBytes, 0, numberOfBytesRemaining);

            ProcessData(remainingBytes, numberOfBytesRemaining);
        }
    }
}


internal sealed class ReceiveBuffer
{
    public const Int32 LengthBufferSize = sizeof(Int32);

    private MemoryStream _dataBuffer = new MemoryStream();
    private MemoryStream _lengthBuffer = new MemoryStream();

    public Int32 DataLength { get; private set; }

    public Boolean IsComplete
    {
        get { return (RemainingDataBytesToWrite == 0); }
    }

    private Int32 RemainingDataBytesToWrite
    {
        get
        {
            if (DataLength > 0)
                return (DataLength - (Int32)_dataBuffer.Length);

            return 0;
        }
    }

    private Int32 RemainingLengthBytesToWrite
    {
        get { return (LengthBufferSize - (Int32)_lengthBuffer.Length); }
    }

    public Int32 Write(Byte[] bytes, Int32 count)
    {
        var numberOfLengthBytesToWrite = Math.Min(RemainingLengthBytesToWrite, count);

        if (numberOfLengthBytesToWrite > 0)
            WriteToLengthBuffer(bytes, numberOfLengthBytesToWrite);

        var remainingCount = count - numberOfLengthBytesToWrite;

        // If this value is > 0, then we have still have more bytes after setting the length so write them to the data buffer
        var numberOfDataBytesToWrite = Math.Min(RemainingDataBytesToWrite, remainingCount);

        if (numberOfDataBytesToWrite > 0)
            _dataBuffer.Write(bytes, numberOfLengthBytesToWrite, numberOfDataBytesToWrite);

        return numberOfLengthBytesToWrite + numberOfDataBytesToWrite;
    }

    private void WriteToLengthBuffer(Byte[] bytes, Int32 count)
    {
        _lengthBuffer.Write(bytes, 0, count);

        if (RemainingLengthBytesToWrite == 0)
        {
            var length = BitConverter.ToInt32(_lengthBuffer.ToArray(), 0);

            DataLength = length;
        }
    }
}

Ответы [ 2 ]

6 голосов
/ 20 января 2012

Не увидев ваш код, мы можем только догадываться. Я думаю: вы рассматриваете случай, когда вы читаете меньше полного 4-байтового заголовка? Вы можете прочитать только один, два или три байта. При больших объемах данных это будет происходить чаще.

Поскольку TCP является надежным протоколом, это , а не из-за потери пакета. Любые потерянные пакеты приводят к одной из двух вещей:

  1. Отсутствующие данные передаются повторно, и получатель испытывает короткую паузу, но никогда не видит отсутствующие данные или данные не в порядке.
  2. Розетка закрыта.

UPDATE

Ваш метод IsComplete возвращает true после частичной записи в буфер. Это приводит к тому, что код вашего получателя в ProcessData() отбрасывает уже принятые байты буфера длины, а затем выходит из синхронизации.

0 голосов
/ 17 августа 2013

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

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

Еще одна вещь, которая прыгнула прямо на меня в вашем коде, это замок. С какой стати ты что-то блокируешь, когда работаешь асинхронно? Вы должны понимать, что ваш объект SAEA является объектом состояния для потока, который обрабатывает ваш асинхронный вызов метода. Это предназначено для того, чтобы избавить себя от необходимости делать нити самостоятельно. Вы в основном вызываете метод Sockets ReceiveAsync и бросаете в него объект SAEA, а если обработка завершена, вы берете из него буфер и информацию и снова возвращаете его в метод ReceiveAsync. Обработка происходит в другом методе, который не имеет ничего общего с чтением из сокета. Таким образом, вы держите вашу розетку быстро.

И последнее: я не знаю, какова цель вашего приложения, но обычно не рекомендуется использовать TCP для больших объемов данных, поступающих на высоких скоростях. Именно поэтому более быстрые сетевые движки игр используют UDP. Даже самые медленные из этих движков обычно отправляют со скоростью 20 пакетов в секунду. Если ваш код прерывается через полсекунды, вы должны рассмотреть возможность перехода на UDP. Я обнаружил, что Gaffer в играх является хорошим источником информации о сетевом взаимодействии в реальном времени с UDP. Как он объясняет концепции, это может быть полезно и для вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...