Self-healing SslStream

本文关键字:SslStream Self-healing | 更新日期: 2023-09-27 18:20:10

我正在编写一个服务,该服务需要维护与远程服务器的长期运行SSL连接。我需要这个服务器能够自我修复,也就是说,如果它因任何原因断开连接,那么下次写入时就会重新连接。我试过这个:

bool isConnected = client.Connected && client.Client.Poll(0, SelectMode.SelectWrite) && stream.CanWrite;
if (!isConnected )
{
    this.connected = false;
    GetConnection();
}
stream.Write(bytes, 0, bytes.Length);
stream.Flush();

但我发现它并没有像我预期的那样运行。如果我通过禁用wifi来模拟网络中断,我仍然可以用流写入流。Write()大约20秒。然后下次我试着给它写信时,没有客户。已连接,客户端。Client.Poll()或流。CanWrite()返回false,但当我写入流时,我会得到一个套接字异常。最后,如果我尝试重新创建连接,我会得到以下异常:远程主机强制关闭了现有连接。

如果能帮助创建一个能够承受网络故障的长期运行的SslStream,我将不胜感激。谢谢

Self-healing SslStream

从10.000英尺的角度来看:

关闭wifi后,你仍然可以写入流的原因是有一个网络缓冲区,用来保存传输数据,流。写入/流。刷新成功意味着网络接口(TCP/IP堆栈)已接受数据并已进行缓冲以进行传输,而不是数据已到达其目标。

TCP/IP堆栈需要一段时间才能注意到媒体完全断开连接(连接丢失/重置),因为即使没有物理链路,TCP/IP也会将其视为网络中的临时问题,并会持续重试一段时间(网络可能在某个时刻丢弃数据包,堆栈会继续重试)

如果你反过来想,如果出现网络故障(这种情况在互联网上经常发生),你不会希望所有程序都失败,所以TCP/IP会花时间通知应用层连接无效(重试几次并等待合理的时间后)

当SslStream失败时,您总是可以重新连接到服务器并继续发送数据,尽管您会发现这并没有这么容易,因为有几种情况下,您发送的数据没有被服务器接收到,而其他情况下,服务器接收到数据,而您根本没有收到来自服务器的任何ACK。。。因此,根据你的需要,光靠自我修复可能是不够的。

自愈很容易实现,数据的一致性和可靠性更难实现,通常需要服务器准备好支持某种可靠的消息传递机制,以确保所有数据都已发送和接收。

SSL的底层协议是TCP。TCP通常只在应用程序希望它传递数据,或者需要通过发送ACK来回复从另一端接收到的数据时才发送数据。这意味着,在您尝试发送任何数据之前,断开的连接(如丢失的链接)不会被注意到。但你不会立即注意到,因为:

  • 对套接字的写入只会将数据传递到操作系统内核,如果传递成功,则返回成功
  • 然后内核将尝试将数据传递给对等端,并等待来自客户端的ACK
  • 如果它没有得到任何ACK,它将再次重试以传递数据,并且只有在一些不成功的重试之后,内核才会声明连接已断开
  • 只有在连接被内核标记为断开之后,下一次写入或读取才会将错误从内核返回到用户空间,就像写入时返回EPIPE一样

这意味着,如果你想提前知道连接是否仍然有效,你必须确保在连接上进行定期的数据交换。在TCP级别,您可以设置TCP_KEEPALIVE,但这可能会在交换数据包之间使用几个小时的间隔。在SSL层,您可能会尝试使用臭名昭著的heartbeat扩展,但大多数同行不会理解它。最后的选择是在自己的应用程序中实现某种heartbeast。

至于自愈:重新连接时,您将获得一个新的TCP连接,并且还需要进行一次完整的SSL握手,因为上一次SSL连接没有完全关闭,因此无法恢复。服务器不知道这个新连接只是旧连接的延续,所以您必须在客户端和服务器上的应用程序层内实现某种跨越多个TCP连接的元连接。在这个元连接中,你需要有自己的数据跟踪来检测,哪些数据是真正从对等端接受的,哪些数据只是发送的,但由于连接断开而从未明确接受。听起来像是TCP之上的一种TCP。