10

尽管有文档,但 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 等待发送数据,或者等待服务器响应。

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

4

9 回答 9

11

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

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

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

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

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

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

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

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

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

于 2008-09-17T03:32:07.133 回答
5

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

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

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

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

于 2008-09-15T23:30:29.280 回答
1

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

于 2008-09-15T23:07:18.260 回答
1

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

于 2008-09-15T23:04:26.147 回答
1

我想不出 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() 可能会帮助您解决超时问题。它可以传递一个超时参数。这是一个懒惰的等待——在结果完成或超时到期之前,不会再次调度线程。

于 2008-09-16T00:15:27.003 回答
1

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

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

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

于 2008-09-16T05:55:17.657 回答
0

如何使用 Flush() 方法。

ns.Flush()

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

于 2008-09-15T22:57:12.217 回答
0

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

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

于 2009-04-01T22:25:00.723 回答
-1

也许尝试设置 tcp.NoDelay = true

于 2008-09-15T23:11:38.717 回答