简要概述:
我的服务通过套接字接收信息并发送回复,连接是不安全的。我想设置另一个服务来为这些连接提供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类愉快地一直运行,而不会抛出任何错误,并且我找不到应该停止它的任何指示。(当然,每次在ReadCallback中都会得到0长度,但我需要能够提供长时间运行的连接,因此这不是一个判断的好方法。)
另一个潜在问题是,即使没有可用数据,也会调用ReadCallback。 不确定如果我使用Socket而不是流,是否会有所不同,但似乎保持不断运行那段代码效率低下。
我的问题:
1)有没有办法从客户端端告诉流已经关闭?
2)有没有更好的方式来实现我正在尝试做的事情?
2a)有没有更有效的方法来运行异步读写循环?
编辑:感谢Robert。 结果循环一直被调用,因为我没有关闭流(由于不知道何时需要关闭流)。 我将包含完整的代码解决方案,以防其他人遇到此问题:
/// <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;
}
}
}