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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/67761/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 11:04:21  来源:igfitidea点击:

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

提问by dan gibson

Despite the documentation, NetworkStream.Write does not appear to wait until the data has been sent. Instead, it waits until the data has been copied to a buffer and then returns. That buffer is transmitted in the background.

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

This is the code I have at the moment. Whether I use ns.Write or ns.BeginWrite doesn't matter - both return immediately. The EndWrite also returns immediately (which makes sense since it is writing to the send buffer, not writing to the network).

这是我目前拥有的代码。我是否使用 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;
    }

How can I tell when the data has actually been sent to the client?

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

I want to wait for 10 seconds(for example) for a response from the server after sending my data otherwise I'll assume something was wrong. If it takes 15 seconds to send my data, then it will always timeout since I can only start counting from when NetworkStream.Write returns - which is before the data has been sent. I want to start counting 10 seconds from when the data has left my network card.

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

The amount of data and the time to send it could vary - it could take 1 second to send it, it could take 10 seconds to send it, it could take a minute to send it. The server does send an response when it has received the data (it's a smtp server), but I don't want to wait forever if my data was malformed and the response will never come, which is why I need to know if I'm waiting for the data to be sent, or if I'm waiting for the server to respond.

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

I might want to show the status to the user - I'd like to show "sending data to server", and "waiting for response from server" - how could I do that?

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

回答by palehorse

How about using the Flush() method.

如何使用 Flush() 方法。

ns.Flush()

That should ensure the data is written before continuing.

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

回答by GEOCHET

In general, I would recommend sending an acknowledgment from the client anyway. That way you can be 100% sure the data was received, and received correctly.

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

回答by Craig Eddy

If I had to guess, the NetworkStream considers the data to have been sent once it hands the buffer off to the Windows Socket. So, I'm not sure there's a way to accomplish what you want via TcpClient.

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

回答by sbeskur

Perhaps try setting tcp.NoDelay = true

也许尝试设置 tcp.NoDelay = true

回答by Greg Cottman

TCP is a "reliable" protocol, which means the data will be received at the other end if there are no socket errors. I have seen numerous efforts at second-guessing TCP with a higher level application confirmation, but IMHO this is usually a waste of time and bandwidth.

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

Typically the problem you describe is handled through normal client/server design, which in its simplest form goes like this...

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

The client sends a request to the server and does a blocking read on the socket waiting for some kind of response. If there is a problem with the TCP connection then that read will abort. The client should also use a timeout to detect any non-network related issue with the server. If the request fails or times out then the client can retry, report an error, etc.

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

Once the server has processed the request and sent the response it usually no longer cares what happens - even if the socket goes away during the transaction - because it is up to the client to initiate any further interaction. Personally, I find it very comforting to be the server. :-)

一旦服务器处理了请求并发送了响应,它通常不再关心会发生什么——即使套接字在事务期间消失——因为它是由客户端发起任何进一步的交互。就我个人而言,我觉得成为服务器非常令人欣慰。:-)

回答by Alex Lyman

I can not think of a scenario where NetworkStream.Write wouldn't send the data to the server as soon as possible. Barring massive network congestion or disconnection, it should end up on the other end within a reasonable time. Is it possible that you have a protocol issue? For instance, with HTTP the request headers must end with a blank line, and the server will not send any response until one occurs -- does the protocol in use have a similar end-of-message characteristic?

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

Here's some cleaner code than your original version, removing the delegate, field, and Thread.Sleep. It preforms the exact same way functionally.

这是一些比原始版本更清晰的代码,删除了委托、字段和 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);
}

Looks like the question was modified while I wrote the above. The .WaitOne() may help your timeout issue. It can be passed a timeout parameter. This is a lazy wait -- the thread will not be scheduled again until the result is finished, or the timeout expires.

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

回答by Lex Li

I try to understand the intent of .NET NetworkStream designers, and they must design it this way. After Write, the data to send are no longer handled by .NET. Therefore, it is reasonable that Write returns immediately (and the data will be sent out from NIC some time soon).

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

So in your application design, you should follow this pattern other than trying to make it working your way. For example, use a longer time out before received any data from the NetworkStream can compensate the time consumed before your command leaving the NIC.

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

In all, it is bad practice to hard code a timeout value inside source files. If the timeout value is configurable at runtime, everything should work fine.

总之,在源文件中硬编码超时值是不好的做法。如果超时值可在运行时配置,则一切正常。

回答by Glyph

I'm not a C# programmer, but the way you've asked this question is slightly misleading. The only way to know when your data has been "received", for any useful definition of "received", is to have a specific acknowledgment message in your protocol which indicates the data has been fully processed.

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

The data does not "leave" your network card, exactly. The best way to think of your program's relationship to the network is:

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

your program -> lots of confusing stuff -> the peer program

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

A list of things that might be in the "lots of confusing stuff":

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

  • the CLR
  • the operating system kernel
  • a virtualized network interface
  • a switch
  • a software firewall
  • a hardware firewall
  • a router performing network address translation
  • a router on the peer's end performing network address translation
  • CLR
  • 操作系统内核
  • 虚拟化网络接口
  • 一个开关
  • 软件防火墙
  • 硬件防火墙
  • 执行网络地址转换的路由器
  • 对端执行网络地址转换的路由器

So, if you are on a virtual machine, which is hosted under a different operating system, that has a software firewall which is controlling the virtual machine's network behavior - when has the data "really" left your network card? Even in the best case scenario, many of these components may drop a packet, which your network card will need to re-transmit. Has it "left" your network card when the first (unsuccessful) attempt has been made? Most networking APIs would say no, it hasn't been "sent" until the other end has sent a TCP acknowledgement.

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

That said, the documentation for NetworkStream.Writeseems to indicate that it will not return until it has at least initiated the 'send' operation:

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

The Write method blocks until the requested number of bytes is sent or a SocketException is thrown.

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

Of course, "is sent" is somewhat vague for the reasons I gave above. There's also the possibility that the data will be "really" sent by your program and received by the peer program, but the peer will crash or otherwise not actually process the data. So you should do a Writefollowed by a Readof a message that will only be emitted by your peer when it has actually processed the message.

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

回答by Glyph

Bellow .net is windows sockets which use TCP. TCP uses ACK packets to notify the sender the data has been transferred successfully. So the sender machine knows when data has been transferred but there is no way (that I am aware of) to get that information in .net.

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

edit: Just an idea, never tried: Write() blocks only if sockets buffer is full. So if we lower that buffers size (SendBufferSize) to a very low value (8? 1? 0?) we may get what we want :)

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