Гарантии протокола TCP в рамках .NET

Есть один вопрос, который мне не дает покоя. Есть GPS-tracker, передающий данные по TCP/IP, используя свой протокол. Пишу background service (.NET Core 3.1), который должен слушать tcp-порт и сохранять пакеты сообщений в базу в сыром виде (byte[] или закодированные в строку). Сейчас мой код прослушки tcp-порта выглядит примерно так:

public class TcpListenerBackgroundService : BackgroundService
{
    private readonly ITcpListenerWrapper _tcpListener;
    private readonly ILogger<TcpListenerBackgroundService> _logger;

    public TcpListenerBackgroundService(ITcpListenerWrapper tcpListener, ILogger<TcpListenerBackgroundService> logger)
    {
        _tcpListener = tcpListener;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _tcpListener.StartListeningAsync(stoppingToken);
    }
}

TcpListenerWrapper.cs

public sealed class TcpListenerWrapper : ITcpListenerWrapper, IDisposable
{
    private const int BufferSize = 1024;

    private readonly TcpListener _listener;
    private readonly IMessageManager _messageManager;

    public TcpListenerWrapper(ITcpListenerWrapperConfiguration config, IMessageManager messageManager)
    {
        _listener = new TcpListener(IPAddress.Any, config.PortNumber);
        _messageManager = messageManager;
    }

    public async Task StartListeningAsync(CancellationToken stoppingToken)
    {
        _listener.Start();

        while (!stoppingToken.IsCancellationRequested)
        {
            var client = await _listener.AcceptTcpClientAsync();
            var stream = client.GetStream();

            while (!stoppingToken.IsCancellationRequested)
            {
                var buffer = new byte[BufferSize];

                using (var ms = new MemoryStream())
                {
                    int numBytesRead;
                    while ((numBytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        ms.Write(buffer, 0, numBytesRead);
                    }

                    if (ms.Length > 0)
                    {
                        var messageAsText = Encoding.ASCII.GetString(buffer);
                        // Console.WriteLine($"New message received: {messageAsText}");

                        var message = _messageManager.CreateMessage(buffer);
                        message.Save(); // Сохранение в бд.
                    }
                }
            }
        }
    }

    public void Dispose()
    {
        _listener.Stop();
    }
}

Пусть GPS-tracker послал 10 пакетов с периодом в 1 минуту.

Вопрос: Может ли быть так, что в NetworkStream окажется сразу несколько пакетов (скажем, 3 пакета)? Или есть гарантия, что они будут приходить по одному?

Доп. вопрос: Есть ли в коде прослушки сообщений какие-то ошибки? Может, здесь чего-то не хватает, либо наоборот, лишнее?


Ответы (1 шт):

Автор решения: KoVadim

Вопрос: Может ли быть так, что в NetworkStream окажется сразу несколько пакетов (скажем, 3 пакета)? Или есть гарантия, что они будут приходить по одному?

.NET ничего не знает (и не должен) о том, что там какой то GPS треккер передает данные. Как придут байты, так и будут сложены. То есть, в теории может прийти вначале два с половиной пакета, потом ещё полтора. А может и по другому. TCP оперирует байтами. И протокол гарантирует только что байты (БАЙТЫ!) придут в том же порядке, что не будет "случайно пропущенного байта/бита". Также протокол будет пытаться доставить пакеты или сделает "разрыв соединения", если доставить невозможно (провод перерезали).

Посмотрел в код. Вы каждый пришедший пакет пытаетесь декодировать как полностью пришедший пакет. Но только это гарантируется. Да, при локальном тесте оно скорее всего обычно так и будет работать. Но как только винда решит приуныть (антивирусник запустился) или сеть за роутером/gsm модулем. может быть все что угодно.

У меня был случай, когда из за одного известного фаервола всегда вначале первым чтением приходил 1 байт. А потом все остальное как обычно. Но делал он это не постоянно. А программист в коде рассчитывал, что ну хоть 4 байта придется сразу. А нет...

Что делать в коде. Скорее всего в этом протоколе разделителем сообщений есть перевод строки. Поэтому, пришли данные, добавили к конец буфера, потом ищите перевод строки. Есть - извлекаем пакет, повторяем поиск. нет - до следующего пакета.

→ Ссылка