Можно ли определить, был ли поток закрыт клиентом? - PullRequest
9 голосов
/ 14 марта 2012

Краткий обзор ситуации:

У меня есть служба, которая принимает информацию и отправляет ответы через сокеты. Соединения не защищены. Я хочу настроить другую службу, которая может предоставлять TLS для этих подключений - эта новая служба будет предоставлять один порт и распределять подключения на основе предоставленного сертификата клиента. Я не хочу использовать stunnel по нескольким причинам, одна из которых заключается в том, что для каждого порта приема потребуется один порт пересылки.

Решение, которое я сейчас пытаюсь реализовать:

По сути, я пытаюсь соединить SslStream (входящий) с NetworkStream (исходящий - может быть Socket, но я помещаю его в NetworkStream для соответствия входящему) и связать операции чтения / записи для двух , Эта ссылка обеспечит поток между клиентом (через SSL / TLS) и службой (через незащищенное соединение).

Вот класс, который я придумал, чтобы связать эти потоки:

public class StreamConnector
{
    public StreamConnector(Stream s1, Stream s2)
    {
        StreamConnectorState state1 = new StreamConnectorState(s1, s2);
        StreamConnectorState state2 = new StreamConnectorState(s2, s1);
        s1.BeginRead(state1.Buffer, 0, state1.Buffer.Length, new AsyncCallback(ReadCallback), state1);
        s2.BeginRead(state2.Buffer, 0, state2.Buffer.Length, new AsyncCallback(ReadCallback), state2);
    }

    private void ReadCallback(IAsyncResult result)
    {
        // Get state object.
        StreamConnectorState state = (StreamConnectorState)result.AsyncState;

        // Finish reading data.
        int length = state.InStream.EndRead(result);

        // Write data.
        state.OutStream.Write(state.Buffer, 0, length);

        // Wait for new data.
        state.InStream.BeginRead(state.Buffer, 0, state.Buffer.Length, new AsyncCallback(ReadCallback), state);
    }
}

public class StreamConnectorState
{
    private const int BYTE_ARRAY_SIZE = 4096;

    public byte[] Buffer { get; set; }
    public Stream InStream { get; set; }
    public Stream OutStream { get; set; }

    public StreamConnectorState(Stream inStream, Stream outStream)
    {
        Buffer = new byte[BYTE_ARRAY_SIZE];
        InStream = inStream;
        OutStream = outStream;
    }
}

Проблема:

Когда клиент завершает отправку информации и удаляет SslStream, сервер не имеет каких-либо указаний на то, произошло ли это или нет. Этот класс StreamConnector счастливо продолжает работать в вечности без каких-либо ошибок, и я не могу найти индикатор того, что он должен остановиться. (Есть, конечно, тот факт, что я каждый раз получаю 0 длины в ReadCallback, но мне нужно иметь возможность обеспечивать длительные соединения, так что это не очень хороший способ судить.)

Другая потенциальная проблема заключается в том, что ReadCallback вызывается, даже если данные недоступны. Не уверен, что будет иначе, если я использую Socket напрямую вместо потока, но кажется неэффективным продолжать повторять этот код снова и снова.

Мои вопросы:

1) Есть ли способ узнать, был ли поток закрыт со стороны клиента?

2) Есть ли лучший способ сделать то, что я пытаюсь сделать?

2a) Есть ли более эффективный способ запуска асинхронного цикла чтения / записи?

РЕДАКТИРОВАТЬ: Спасибо, Роберт. Оказывается, что цикл продолжал вызываться, потому что я не закрывал потоки (из-за того, что не знал, как определить, когда нужно закрыть потоки). Я включаю полное решение кода на случай, если кто-то столкнется с этой проблемой:

/// <summary>
/// Connects the read/write operations of two provided streams
/// so long as both of the streams remain open.
/// Disposes of both streams when either of them disconnect.
/// </summary>
public class StreamConnector
{
    public StreamConnector(Stream s1, Stream s2)
    {
        StreamConnectorState state1 = new StreamConnectorState(s1, s2);
        StreamConnectorState state2 = new StreamConnectorState(s2, s1);
        s1.BeginRead(state1.Buffer, 0, state1.Buffer.Length, new AsyncCallback(ReadCallback), state1);
        s2.BeginRead(state2.Buffer, 0, state2.Buffer.Length, new AsyncCallback(ReadCallback), state2);
    }

    private void ReadCallback(IAsyncResult result)
    {
        // Get state object.
        StreamConnectorState state = (StreamConnectorState)result.AsyncState;

        // Check to make sure Streams are still connected before processing.
        if (state.InStream.IsConnected() && state.OutStream.IsConnected())
        {
            // Finish reading data.
            int length = state.InStream.EndRead(result);

            // Write data.
            state.OutStream.Write(state.Buffer, 0, length);

            // Wait for new data.
            state.InStream.BeginRead(state.Buffer, 0, state.Buffer.Length, new AsyncCallback(ReadCallback), state);
        }
        else
        {
            // Dispose of both streams if either of them is no longer connected.
            state.InStream.Dispose();
            state.OutStream.Dispose();
        }
    }
}

public class StreamConnectorState
{
    private const int BYTE_ARRAY_SIZE = 4096;

    public byte[] Buffer { get; set; }
    public Stream InStream { get; set; }
    public Stream OutStream { get; set; }

    public StreamConnectorState(Stream inStream, Stream outStream)
    {
        Buffer = new byte[BYTE_ARRAY_SIZE];
        InStream = inStream;
        OutStream = outStream;
    }
}

public static class StreamExtensions
{
    private static readonly byte[] POLLING_BYTE_ARRAY = new byte[0];

    public static bool IsConnected(this Stream stream)
    {
        try
        {
            // Twice because the first time will return without issue but
            // cause the Stream to become closed (if the Stream is actually
            // closed.)
            stream.Write(POLLING_BYTE_ARRAY, 0, POLLING_BYTE_ARRAY.Length);
            stream.Write(POLLING_BYTE_ARRAY, 0, POLLING_BYTE_ARRAY.Length);
            return true;
        }
        catch (ObjectDisposedException)
        {
            // Since we're disposing of both Streams at the same time, one
            // of the streams will be checked after it is disposed.
            return false;
        }
        catch (IOException)
        {
            // This will be thrown on the second stream.Write when the Stream
            // is closed on the client side.
            return false;
        }
    }
}

Ответы [ 2 ]

3 голосов
/ 14 марта 2012

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

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

Стоит отметить, что если вы используете модель сервера на основе выбора, отключенный сокет обнаруживается - то есть возвращает select - как читаемый, когда он отключается, тогда вы пытаетесь прочитать с него и получить ошибку или null.

0 голосов
/ 15 марта 2012

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

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