【问题标题】:Is it possible to detect if a Stream has been closed by the client?是否可以检测流是否已被客户端关闭?
【发布时间】:2012-03-14 17:52:14
【问题描述】:

情况简介:

我有一个服务,它接收信息并通过套接字发送回复。连接不安全。我想设置另一个可以为这些连接提供 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) 有没有办法判断 Stream 是否已从客户端关闭?

2) 有没有更好的方法来做我想做的事情?

2a) 有没有更高效的方式来运行异步读/写循环?

编辑:谢谢,罗伯特。原来循环一直被调用,因为我没有关闭 Streams(因为不知道如何判断 Streams 何时需要关闭)。我将包含完整的代码解决方案,以防其他人遇到此问题:

/// <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;
        }
    }
}

【问题讨论】:

  • 请不要在标题前加上'C#',这就是标签的用途:)
  • 对不起。 :) 我在研究时看到其他一些标题中提到了该语言;认为这会有所帮助。从这里开始会坚持使用标签。

标签: c# stream


【解决方案1】:

您必须尝试读取或写入套接字(或基于套接字的任何内容)才能检测断开连接。

尝试写入将引发异常/返回错误(取决于您的语言范例)或者可能只写入 0 个字节。尝试读取将引发异常/返回错误(同样取决于您的语言范例)或返回 null

值得注意的是,如果您使用的是基于选择的服务器模型,则断开连接的套接字会显示 - 即返回 select - 当它断开连接时可读,然后您尝试从中读取并得到错误或 @ 987654323@.

【讨论】:

  • 所以我需要尝试写入每个流(如果未关闭,可能会导致流中的数据错误)?
  • 猜猜,如果我写的是 0 字节,那应该不是问题。
  • 我可以补充一下,根据Nito,检测断开连接的唯一方法是写入流。
【解决方案2】:

我不禁认为您的客户端应该在完成某种消息时通知服务器。最好为电线被切断或电源故障或插头被拔出做好准备,但通常您希望使用某种消息结束标记来终止连接。将例外保存为实际问题,而不是正常对话。

【讨论】:

  • 在客户端(拥有 NetworkStream,拥有 Socket)上处理 SslStream 时,不会向服务器发送任何内容以告知其已断开连接。至少,不会关闭服务器上的套接字/流。
  • @zimdanen:我认为这是 SslStream 的(大)设计问题。如果 SslStream 超出您的权限,除了您所做的事情之外,没有什么可做的。我们必须让我们的软件以它本来的样子而不是它应该的样子与世界一起工作。 (不过,我无法抗拒抱怨。)
猜你喜欢
  • 2020-05-30
  • 2012-03-04
  • 2011-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-08
  • 2018-07-21
相关资源
最近更新 更多