Соединение разрывается при простое

319
29 июня 2017, 00:50

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

При необходимости могу предоставить код, а так я использую этот.

Код клиента.

public async Task Send(Packet message)
{
    var buffer = message?.ToBytes();
    if (buffer?.Length > 0)
    {
        try
        {
            var bytes = BitConverter.GetBytes(buffer.Length).ToList();
            bytes.AddRange(buffer);
            await _stream.WriteAsync(bytes.ToArray(), 0, bytes.Count);
        }
        catch (ObjectDisposedException)
        {
            Console.WriteLine(@"Connection close");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}
private async Task<byte[]> ReceiveTask(int byteCount)
{
    var buffer = new byte[byteCount];
    var pos = 0;
    while (pos < byteCount)
    {
        var count = await _stream.ReadAsync(buffer, pos, buffer.Length - pos);
        if (count == 0)
            throw new EndOfStreamException();
        pos += count;
    }
    return buffer;
}
public async void Receive()
{
    try
    {
        while (true)
        {
            var packetLenBytes = await ReceiveTask(4);
            var packetlen = BitConverter.ToInt32(packetLenBytes, 0);
            if (packetlen > MaxLenSize && packetlen < 0) break;
            var data = await ReceiveTask(packetlen);
            RaiseEventMessegaReceive(data);
        }
    }
    catch (OutOfMemoryException exception)
    {
        Console.WriteLine(exception);
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception);
    }
    finally
    {
        Dispose();
    }
}

Код на сервере:

public ClientTcp(TcpClient client, ServerTcp serverTcp)
{
    _client = client;
    _serverTcp = serverTcp;
    _serverTcp.ClientAdd(this);
    _stream = client.GetStream();
    _client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
    Address = ((IPEndPoint)_client.Client.RemoteEndPoint).Address;
    Port = ((IPEndPoint)_client.Client.RemoteEndPoint).Port;
    GUID = Guid.NewGuid().ToString();
    Program.ClientLogger.Info($"Client from {Address}:{Port} connect.");
    Task.Run(Receive);
}

public async Task Send(Packet message)
{
    if (message == null)
        throw new ArgumentNullException(nameof(message));
    Program.CommandLogger.Info($"Sent: {message.Command} to {Address}:{Port}");
    var buffer = message.ToBytes();
    if (buffer.Length > 0)
    {
        try
        {
            await _stream.WriteAsync(buffer.ToArray(), 0, buffer.Length);
        }
        catch (Exception)
        {
            Dispose();
        }
    }
}
private async Task<byte[]> ReceiveTask(int byteCount)
{
    var buffer = new byte[byteCount];
    var pos = 0;
    while (pos < byteCount)
    {
        var count = await _stream.ReadAsync(buffer, pos, buffer.Length - pos);
        if (count == 0)
            throw new EndOfStreamException();
        pos += count;
    }
    return buffer;
}
private async Task Receive()
{
    try
    {
        while (true)
        {
            var packetLenBytes = await ReceiveTask(4);
            var packetlen = BitConverter.ToInt32(packetLenBytes, 0);
            if (packetlen > MaxLenSize)
                break;
            var data = await ReceiveTask(packetlen);
            RaiseEventMessegaReceive(data);
        }
    }
    catch (Exception)
    {
        // ignore
    }
    finally
    {
        Dispose();
    }
}
Answer 1

Подобные проблемы могут возникать из-за межсетевых экранов, отслеживающих состояние соединения. Обычно они используются для реализации PNAT (трансляции сетевых адресов). Этой трансляцией может заниматься ваш роутер или провайдер. Проверьте IP-адрес компьютера, где запущен клиент: если он из подсетей 10.0.0.0/8, 172.16.0.0/12 или 192.168.0.0/16 (так называемые диапазоны серых адресов) - значит, где-то NAT точно есть.

Но даже если вы используете белый IP - это еще не означает, что между клиентом и сервером нет межсетевого экрана.

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

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

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

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

READ ALSO
Правильное написание плагина на чистом JavaScript

Правильное написание плагина на чистом JavaScript

Возник вопрос о правильном подходе создания плагина на js

385
как вынести данные из объекта?

как вынести данные из объекта?

Как вынести данные из объекта в отдельные переменные, чтобы при использовании каждый раз не писать objectname, object

269