【问题标题】:Blocking TCP socket write then immediatelly close - is it a problem?阻止 TCP 套接字写入然后立即关闭 - 有问题吗?
【发布时间】:2010-11-23 11:32:23
【问题描述】:

我有一个 TCP 连接、发送数据块并关闭套接字的发送方。 我想编写完成上述操作的最简单但可靠的程序。 首先想到的是(例如在 .NET 中,尽管这个问题通常与套接字相关):

// assuming LingerOption(false), NoDelay set to whatever
var client = new TcpClient().Connect(server, port);
var stream = client.GetStream();
stream.Write(data, 0, data.Length);
stream.Close();
client.Close();

现在根据阅读各种 MSDN 和其他材料提出一些问题:

  1. stream.Close() 调用 Socket.Close()。 Socket.Close() 据说关闭 立即丢弃网络 缓冲未发送的数据。这 不好。但是,Socket.Write 文件说,如果 套接字阻塞(它是,通过 默认),Socket.Write 将阻塞 直到发送完所有数据。所以 没问题吧?
  2. 一般来说,会不会有一种情况,上面的代码 将导致接收器不 接收发送的所有内容 在写? (假设 100% 可靠 网络)

【问题讨论】:

    标签: .net tcp sockets communication


    【解决方案1】:

    TcpClient.Close 表示“Close 方法将实例标记为已释放并请求关联的 Socket 关闭 TCP 连接。根据 LingerState 属性,在调用 Close 方法后 TCP 连接可能会保持打开一段时间当数据仍有待发送时。当底层连接完成关闭时,不会提供任何通知。"

    简而言之,除非您使用 LingerState,否则调用 Close 不会丢弃数据的 OS 缓冲区 - TCP 堆栈将尽最大努力传递您编写的数据。

    但请务必捕获异常,如果出现问题,您的任何 Write 或 2 个 Close 调用都可能引发异常 - 并确保在这些情况下处置套接字和流。

    另请注意,Write() 调用可能不会阻塞,直到数据被发送,它只会阻塞直到数据被复制到 TCP 堆栈(并且依赖于太多的事情,TCP 可能会部分发送一些数据在Write返回之前在网络上出去)

    【讨论】:

      【解决方案2】:

      在 Socket.Write 之后调用 Close 是完全可以的。这是 TCP RFC 793 says:

      “关闭连接是一种优雅的操作,在流量控制允许的情况下,未完成的 SEND 将被传输(和重新传输),直到所有连接都得到服务。因此,应该可以接受多个SEND 调用,后跟 CLOSE,并期望将所有数据发送到目的地。"

      混乱部分来自MSDN documentation

      "如果您使用的是面向连接的协议,Send 将阻塞,直到缓冲区中的所有字节都发送"

      实际上,阻塞式写入只会将数据复制到传出缓冲区并返回。根据 RFC 793,网络提供商有责任在调用 Socket.Close 之后完成数据的传递(当然,只要连接没有死)。

      【讨论】:

        【解决方案3】:

        这是在回避您最初的问题,但您没有说明您发送的数据类型。你为什么首先决定使用 TCP 连接?您多久编写一次这些数据块?如果经常出现,我会推荐以下两种方法之一:

        1) 保持 TCP.Stream 打开。否则,如果您在 4 分钟内发送超过 ~64000 个块,您可能会耗尽可用源端口 (1025-65535) 的 TCP 堆栈。如果源计算机上打开了其他应用程序,情况会变得更糟。一旦发生这种情况,您的应用程序将挂起或出错,直到超过 4 分钟的第一个端口可用。

        2) 使用 UDP 而不是 TCP。无法保证交付,但如果这是实时数据,那么您不想使用 TCP,因为它可能会显着延迟实时数据。

        如果您使用 TCP,Client.Close() 应该通过 TCP 堆栈发送一个 FIN,除非其他主机崩溃或它们之间的网络出现故障,否则应该保证交付。

        如果您关心到达目的地的每个数据块,无论您使用 TCP 还是 UDP,您都应该构建一些应用程序级别,检查所有数据块是否按顺序到达且未损坏...

        【讨论】:

          【解决方案4】:

          如果接收器无法读取,您可能会丢失部分数据,这取决于较低层中使用的缓冲区大小。

          但是如果接收器在读取时被阻塞,那么在读取时提供足够的缓冲区是没有问题的。

          假设网络可靠,在“写入”之前执行“读取”,并且接收端有足够的缓冲区,这段代码绝对可靠!

          但是解决办法!

          正如我所说,接收端缓冲区的大小真的很重要。如果缓冲区没有足够的空间,你会被这段代码弄丢的!

          所以你有三个选择:

          1. 只需分配一个足够大的缓冲区。
          2. 使用库或更高的协议传输数据(例如我们在 Win32 API 中有TransmitFile 函数用于传输文件)
          3. 实现您的协议,在发送数据之前, 发送所需缓冲区的大小,然后发送实际数据。

          如果您知道数据的最大大小,那么我为您提供 1 否则,如果您没有足够的时间并且对协议不感兴趣,那么我为您提供 2 否则我给你 3

          祝你好运;)

          【讨论】:

          • 所以,假设接收器在接受后立即开始读取并读取直到套接字关闭 - 我的代码绝对可靠,无论如何?谢谢。
          • @Borka:假设网络可靠,在“写入”之前执行“读取”,并且接收端有足够的缓冲区,这段代码绝对可靠。
          • @Isaac - 很好,但实际上你不能指望这些假设是正确的。
          • @hoohoo - 可靠的网络是传输的要求。虽然 TCP 协议修复了一些错误,但真正不可靠的网络不是网络!我讨论了缓冲区的大小,我希望它很清楚。但是关于在“写”之前执行“读”,如果你有一个专用的接收器,这个条件总是会满足的。但是当我们有一个共享接收器(称为“服务器”)时,我们必须在发送大数据之前与它“协商”
          • @Isaac:“不可靠的网络不是网络”——这是诡辩。
          【解决方案5】:

          接收方可以向发送方发送等待或重传消息。如果在接收到整个数据块之前接收器的 recv 缓冲区已满,或者网络上存在丢失的数据包,则可能会发生这种情况。因此,如果您在交换所有数据之前退出客户端,则会丢失数据。

          看看这本书以获得您的通用解决方案:W. Richard Stevens 的 UNIX 网络编程第一卷。

          是的,它是 UNIX,但它最终处理的是 TCP/IP 通信,这是上述问题的根源。抱歉,这不是很简单,但也不是很困难。这是一个状态机,其状态比您的提案包含的多。

          【讨论】:

          • Indded,你说的一本非常好的书。我会做一些深入的重新定义,并将在上面发布更新。谢谢。
          猜你喜欢
          • 2013-07-28
          • 2015-08-28
          • 2012-07-14
          • 1970-01-01
          • 1970-01-01
          • 2017-09-10
          • 2019-03-08
          • 2013-05-25
          • 1970-01-01
          相关资源
          最近更新 更多