Краткий обзор ситуации:
У меня есть служба, которая получает информацию и отправляет ответы через сокеты. Соединения не защищены. Я хочу настроить другую службу, которая может предоставлять TLS для этих соединений — эта новая служба будет предоставлять один порт и распределять соединения на основе предоставленного сертификата клиента. Я не хочу использовать stunnel по нескольким причинам, одна из которых заключается в том, что для каждого принимающего порта потребуется один порт пересылки.
Решение, которое я сейчас пытаюсь реализовать:
По сути, я пытаюсь соединить SslStream (входящий) с NetworkStream (исходящий — может быть Socket, но я помещаю его в NetworkStream, чтобы он соответствовал incoming) и связать операции чтения/записи для них двух.Эта ссылка обеспечит поток между клиентом (через 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;
}
}
}