【问题标题】:NetworkStream.Write returns immediately - how can I tell when it has finished sending data?NetworkStream.Write 立即返回 - 我如何知道它何时完成发送数据?
【发布时间】:2008-09-15 22:54:58
【问题描述】:

尽管有文档,但 NetworkStream.Write 似乎并没有等到数据发送完毕。相反,它会等到数据被复制到缓冲区然后返回。该缓冲区在后台传输。

这是我目前拥有的代码。无论我使用 ns.Write 还是 ns.BeginWrite 都没有关系 - 两者都会立即返回。 EndWrite 也立即返回(这是有道理的,因为它正在写入发送缓冲区,而不是写入网络)。

    bool done;
    void SendData(TcpClient tcp, byte[] data)
    {
        NetworkStream ns = tcp.GetStream();
        done = false;
        ns.BeginWrite(bytWriteBuffer, 0, data.Length, myWriteCallBack, ns);
        while (done == false) Thread.Sleep(10);
    }
   
    public void myWriteCallBack(IAsyncResult ar)
    {
        NetworkStream ns = (NetworkStream)ar.AsyncState;
        ns.EndWrite(ar);
        done = true;
    }

我如何知道数据何时实际发送到客户端?

我想在发送数据后等待 10 秒(例如)等待服务器的响应,否则我会假设有问题。如果发送我的数据需要 15 秒,那么它总是会超时,因为我只能从 NetworkStream.Write 返回时开始计数 - 这是在发送数据之前。我想从数据离开我的网卡开始计算 10 秒。

数据量和发送时间可能会有所不同 - 发送可能需要 1 秒,发送可能需要 10 秒,发送可能需要一分钟。服务器在收到数据后确实会发送响应(它是一个 smtp 服务器),但是如果我的数据格式错误并且响应永远不会到来,我不想永远等待,这就是为什么我需要知道我是否'我正在等待数据发送,或者我正在等待服务器响应。

我可能想向用户显示状态 - 我想显示“正在向服务器发送数据”和“等待来自服务器的响应” - 我该怎么做?

【问题讨论】:

  • 你尝试过 NetworkStream.Write 的 100MB 数据包吗?

标签: c# network-programming


【解决方案1】:

我不是 C# 程序员,但你问这个问题的方式有点误导。对于“已接收”的任何有用定义,了解您的数据何时“已接收”的唯一方法是在您的协议中包含一个特定的确认消息,表明数据已被完全处理。

确切地说,数据不会“离开”您的网卡。考虑您的程序与网络的关系的最佳方式是:

你的程序 -> 很多令人困惑的东西 -> 对等程序

可能在“很多令人困惑的东西”中的东西列表:

  • CLR
  • 操作系统内核
  • 虚拟化网络接口
  • 一个开关
  • 软件防火墙
  • 硬件防火墙
  • 执行网络地址转换的路由器
  • 对等端的路由器执行网络地址转换

因此,如果您使用的是托管在不同操作系统下的虚拟机,该虚拟机具有控制虚拟机网络行为的软件防火墙 - 数据何时“真正”离开您的网卡?即使在最好的情况下,这些组件中的许多也可能会丢弃一个数据包,您的网卡将需要重新传输该数据包。第一次(不成功)尝试时,它是否“离开”了您的网卡?大多数网络 API 会拒绝,直到另一端发送 TCP 确认后才“发送”。

也就是说,the documentation for NetworkStream.Write 似乎表明它至少在启动“发送”操作之前不会返回:

Write 方法一直阻塞,直到发送了请求的字节数或抛出了 SocketException。

当然,由于我上面给出的原因,“被发送”有些模糊。也有可能数据将“真正”由您的程序发送并由对等程序接收,但对等方将崩溃或以其他方式不实际处理数据。因此,您应该执行 Write 后跟 Read 的消息,该消息只有在您的对等方实际处理了该消息时才会发出。

【讨论】:

  • 发送的 TCP 数据包总是有来自其对等方的底层 TCP ACK,这就是我们使用 TCP 与 UDP 的原因:我们总是知道连接的状态。回到问题,如果Write(bytes)被调用,稍后如果从其他对等方收到TCP ACK,那么应该弹出一个通知,这就是所有者想要的问题。
  • TCP ACK 仅表示远程操作系统(不是远程应用程序)已收到您的一些数据。或者可能是一些正在执行 TCP 缓冲以帮助您的远程系统的网络中间盒。您不能依赖它来可靠地交付应用程序数据。
【解决方案2】:

TCP 是一个“可靠”的协议,这意味着如果没有套接字错误,数据将在另一端接收。我已经看到无数次尝试通过更高级别的应用程序确认来猜测 TCP,但恕我直言,这通常是浪费时间和带宽。

您描述的问题通常是通过正常的客户端/服务器设计来处理的,最简单的形式是这样的......

客户端向服务器发送请求并在套接字上进行阻塞读取以等待某种响应。如果 TCP 连接出现问题,则读取将中止。客户端还应该使用超时来检测服务器的任何非网络相关问题。如果请求失败或超时,则客户端可以重试、报告错误等。

一旦服务器处理了请求并发送了响应,它通常就不再关心发生了什么——即使套接字在事务期间消失了——因为它取决于客户端来启动任何进一步的交互。就我个人而言,我觉得当服务员很舒服。 :-)

【讨论】:

    【解决方案3】:

    一般来说,我还是建议发送来自客户端的确认。这样您就可以 100% 确定数据已被接收并正确接收。

    【讨论】:

    • 如果您有问题,请提出问题。不要在评论中留下问题。
    【解决方案4】:

    如果我不得不猜测,一旦将缓冲区交给 Windows Socket,NetworkStream 就会认为数据已经发送。所以,我不确定有没有办法通过 TcpClient 完成你想要的。

    【讨论】:

    • 确实,CMD/ACK 是实现他想要的唯一方法。
    • 那么,如果我只想处理一个请求,我应该如何使用 TcpListener?
    【解决方案5】:

    我想不出 NetworkStream.Write 不会尽快将数据发送到服务器的情况。除非出现大规模的网络拥塞或断开连接,否则它应该会在合理的时间内到达另一端。您是否有可能遇到协议问题?例如,对于 HTTP,请求标头必须以空行结束,并且服务器不会发送任何响应,直到发生响应 - 使用的协议是否具有类似的消息结束特性?

    这里有一些比原始版本更简洁的代码,删除了委托、字段和 Thread.Sleep。它在功能上执行完全相同的方式。

    void SendData(TcpClient tcp, byte[] data) {
        NetworkStream ns = tcp.GetStream();
        // BUG?: should bytWriteBuffer == data?
        IAsyncResult r = ns.BeginWrite(bytWriteBuffer, 0, data.Length, null, null);
        r.AsyncWaitHandle.WaitOne();
        ns.EndWrite(r);
    }
    

    看起来问题在我编写上述内容时已被修改。 .WaitOne() 可能会帮助您解决超时问题。它可以传递一个超时参数。这是一个惰性等待——在结果完成或超时到期之前,不会再次调度线程。

    【讨论】:

      【解决方案6】:

      我试图理解 .NET NetworkStream 设计者的意图,他们必须以这种方式进行设计。写入后,要发送的数据不再由 .NET 处理。因此,Write 立即返回是合理的(并且数据很快就会从 NIC 发送出去)。

      所以在你的应用程序设计中,你应该遵循这个模式,而不是试图让它按照你的方式工作。例如,在从 NetworkStream 接收任何数据之前使用较长的超时时间可以补偿您的命令离开 NIC 之前所消耗的时间。

      总而言之,在源文件中硬编码超时值是不好的做法。如果超时值可以在运行时配置,那么一切都应该正常。

      【讨论】:

        【解决方案7】:

        使用 Flush() 方法怎么样。

        ns.Flush()
        

        这应该确保在继续之前写入数据。

        【讨论】:

        • Flush 方法实现了 Stream.Flush 方法;但是,由于 NetworkStream 没有缓冲,因此对网络流没有影响。调用 Flush 方法不会引发异常。 msdn.microsoft.com/en-us/library/…
        【解决方案8】:

        Bellow .net 是使用 TCP 的 windows 套接字。 TCP 使用 ACK 数据包通知发送方数据已成功传输。 因此,发送者机器知道数据何时传输,但没有办法(据我所知)在 .net 中获取该信息。

        编辑: 只是一个想法,从未尝试过: 只有当套接字缓冲区已满时,Write() 才会阻塞。因此,如果我们将缓冲区大小(SendBufferSize)降低到一个非常低的值(8?1?0?)我们可能会得到我们想要的:)

        【讨论】:

          【解决方案9】:

          或许可以试试设置 tcp.NoDelay = true

          【讨论】:

          • 这告诉它不要使用 Nagal。 Nagal 在发送任何数据之前等待约 200 毫秒。这个想法是,稍等片刻可以确保发送尽可能多的数据,从而减少网络使用量。您仍然不知道数据何时发送。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-27
          • 1970-01-01
          • 2023-04-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多