繁体   English   中英

NetworkStream.Write 立即返回 - 我如何知道它何时完成发送数据?

[英]NetworkStream.Write returns immediately - how can I tell when it has finished sending data?

尽管有文档,但 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 服务器),但是如果我的数据格式错误并且响应永远不会到来,我不想永远等待,这就是为什么我需要知道我是否' m 等待发送数据,或者等待服务器响应。

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

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

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

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

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

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

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

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

在发送请求的字节数或抛出 SocketException 之前,Write 方法会一直阻塞。

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

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

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

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

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

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

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

我想不出 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);
}

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

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

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

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

如何使用 Flush() 方法。

ns.Flush()

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

波纹管 .net 是 windows sockets 使用 ZB136EF5F6A01D816991FE3CF7A6AC76 TCP 使用 ACK 包通知发送方数据已经传输成功。 因此,发送方机器知道何时传输了数据,但无法(据我所知)在 .net 中获取该信息。

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

也许尝试设置tcp.NoDelay = true

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM