Поддержание работоспособности TCP-соединения

#c# #.net #tcpclient #tcplistener

#c# #.net #tcpclient #tcplistener

Вопрос:

Возможно, это глупый вопрос, но выслушайте меня.

На моем удаленном сервере я запускаю программу со следующим кодом:

 private const int BufferSize = 1024;
private static readonly byte[] BytesBeingReceived = new byte[BufferSize];

static void Main(string[] args)
{
    TcpListener tcpListener = new TcpListener(localaddr: IPAddress.Any, port: 8080);
    tcpListener.Start();

    clientSocket = tcpListener.AcceptSocket();
    clientSocket.Receive(BytesBeingReceived); // Receives header first

    ReceiveAndUnzip(GetZippedFolderSizeInBytes()); 
    // GetZippedFolderSizeInBytes() reads the header, which contains information about the folder size

    Console.WriteLine("Press any key to exit the program.");
    Console.ReadLine();
}
  

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

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

Любые предложения о том, как я могу это сделать?

Обновить:

Вот реализация GetZippedFolderSizeInBytes() :

 private static int GetFileSizeInBytes()
{
    return Convert.ToInt32(Encoding.ASCII.GetString(BytesBeingReceived)
                                         .Replace("", Empty)
                                         .Split(new[] { Environment.NewLine, ":" }, StringSplitOptions.RemoveEmptyEntries)[1]);
}
  

Вот реализация ReceiveAndUnzip(int numBytesExpectedToReceive) метода:

 private static void ReceiveAndUnzip(int numBytesExpectedToReceive)
{
    int numBytesLeftToReceive = numBytesExpectedToReceive;

    using (MemoryStream zippedFolderStream = new MemoryStream(new byte[numBytesExpectedToReceive]))
    {
        while (numBytesLeftToReceive > 0)
        {
            Array.Clear(BytesBeingReceived, 0, BufferSize);
            int numBytesReceived = clientSocket.Receive(BytesBeingReceived, SocketFlags.Partial);
            zippedFolderStream.Write(
                BytesBeingReceived, 
                0, 
                numBytesLeftToReceive < BufferSize ? numBytesLeftToReceive : BufferSize);
            numBytesLeftToReceive -= numBytesReceived;
        }

        zippedFolderStream.Unzip(afterReadingEachDocument: DoMoreStuff);
    }
}
  

Комментарии:

1. Что ж, первая очевидная ошибка здесь на Receive линии. Он возвращает «Количество полученных байтов». но вы не сохраняете это или не используете его. Нет никакой гарантии, что оно совпадает с размером вашего буфера.

2. Спасибо за ваш комментарий. Я обновлю свой пост с помощью реализации метода ReceiveAndUnzip().

3. Да, вы делаете это прямо внутри этого метода, но вы не делаете это правильно Main . // Receives header first может быть полной неправдой, поскольку он мог предоставить всего один байт.

4. @Damien_The_Unbeliever Спасибо за ваш комментарий. Прямо сейчас единственным клиентом, подключающимся к моему серверу, всегда является мой локальный компьютер, и я позаботился о том, чтобы заголовок всегда отправлялся первым. На данный момент обработка ошибок отсутствует, пока я все еще на стадии прототипирования; Я понимаю вашу озабоченность, и обработка ошибок будет добавлена позже 🙂

5. Это не обработка ошибок, это корректность . Если ваш заголовок состоит более чем из одного байта, вам не гарантируется получение всего этого за один раз, даже если на другом конце он был отправлен за один раз. TCP — это поток байтов , а не сообщений. Если вы ожидаете получить, скажем, целое количество байтов в буфере ( GetFileSizeInBytes предполагается, что весь буфер действителен), вам нужно выполнить то же циклическое поведение ReceiveAndUnzip , что и для обеспечения получения всех этих байтов.

Ответ №1:

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

Сетевое программирование — сложная задача. Вот несколько советов.


1. Дефрагментация сообщений

У вас нет гарантии, что один вызов Send на клиенте приведет к одному получению на сервере. Ваше сообщение может быть разбито на фрагменты или, если вы быстро отправите пару сообщений, все они могут быть объединены и получены одним вызовом Receive.

Одним из решений этой проблемы является префикс сообщения с заголовком, например, с целым числом 4 байта. Это целое число является заголовком вашего сообщения и просто содержит информацию о том, какой длины ваше сообщение (в байтах).

Что вы хотите сделать, это получать, пока не получите этот 4-байтовый целочисленный заголовок, а затем получите остальную часть сообщения — теперь вы знаете, как долго сообщение, поэтому вы получаете, пока не получите целое сообщение. Повторяйте этот процесс для каждого нового сообщения.

Это называется дефрагментацией или дефреймингом. Это можно реализовать различными способами: разделители, сообщения с префиксом заголовков фиксированного размера, некоторые сочетания между этими двумя.

2. Механизм поддержания работоспособности / сердцебиения

В tcp есть проблемы с полуоткрытыми и полузакрытыми соединениями. Короче говоря, это просто означает, что вы не можете быть уверены, что соединение все еще работает — одноранговый узел / клиент подключен, если вы не обмениваетесь никакими сообщениями. Когда одноранговый узел / клиент отключается, ваш вызов Receive должен возвращать 0 — это означает, что соединение закрыто, но способ работы TCP не всегда таков, и вы должны учитывать это, чтобы избежать всевозможных связанных проблем.

Самый простой механизм поддержания работоспособности, который вы можете реализовать, — это просто некоторая логика тайм-аута. например, неполучение какого-либо сообщения в течение 2 минут означает, что одноранговый узел / клиент отключен. Или добавьте дополнительное сообщение о сердцебиении, отправляемое каждые X секунд, и отключите одноранговый узел / клиент, если он не отправил его в течение некоторого периода времени.

3. Используйте какую-нибудь хорошо протестированную библиотеку

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

Комментарии:

1. Спасибо за ваш вдумчивый ответ, Барт. Я действительно ценю время и усилия, которые вы вложили в написание этого. Есть ли у вас какие-либо рекомендации по библиотеке?

2. Также google protobuf: developers.google.com/protocol-buffers/docs/csharptutorial Вероятно, вы можете найти на github гораздо более простые в использовании библиотеки