简体   繁体   English

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

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

Despite the documentation, NetworkStream.Write does not appear to wait until the data has been sent.尽管有文档,但 NetworkStream.Write 似乎并没有等到数据发送完毕。 Instead, it waits until the data has been copied to a buffer and then returns.相反,它会等到数据被复制到缓冲区然后返回。 That buffer is transmitted in the background.该缓冲区在后台传输。

This is the code I have at the moment.这是我目前拥有的代码。 Whether I use ns.Write or ns.BeginWrite doesn't matter - both return immediately.无论我使用 ns.Write 还是 ns.BeginWrite 都没有关系 - 两者都会立即返回。 The EndWrite also returns immediately (which makes sense since it is writing to the send buffer, not writing to the network). 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.我想在发送我的数据后等待 10 秒(例如)等待来自服务器的响应,否则我会假设有问题。 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.如果发送我的数据需要 15 秒,那么它将始终超时,因为我只能从 NetworkStream.Write 返回时开始计数 - 这是在发送数据之前。 I want to start counting 10 seconds from when the data has left my network card.我想从数据离开我的网卡开始计算 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.数据量和发送时间可能会有所不同 - 发送可能需要 1 秒,发送可能需要 10 秒,发送可能需要一分钟。 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.服务器在收到数据后确实会发送响应(它是 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?我可能想向用户显示状态 - 我想显示“正在向服务器发送数据”和“等待来自服务器的响应” - 我该怎么做?

I'm not a C# programmer, but the way you've asked this question is slightly misleading.我不是 C# 程序员,但你问这个问题的方式有点误导。 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.对于“接收”的任何有用定义,了解何时“接收”您的数据的唯一方法是在您的协议中包含一个特定的确认消息,表明数据已被完全处理。

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 CLR
  • the operating system kernel操作系统 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对等端的路由器执行网络地址转换

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.Write seems 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.在发送请求的字节数或抛出 SocketException 之前,Write 方法会一直阻塞。

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 Write followed by a Read of a message that will only be emitted by your peer when it has actually processed the message.因此,您应该先Write ,然后Read一条消息,该消息仅在您的对等方实际处理了该消息时才会发出。

TCP is a "reliable" protocol, which means the data will be received at the other end if there are no socket errors. TCP 是一个“可靠”的协议,这意味着如果没有套接字错误,数据将在另一端接收。 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,但恕我直言,这通常是浪费时间和带宽。

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.如果 TCP 连接出现问题,则读取将中止。 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.如果请求失败或超时,则客户端可以重试、报告错误等。

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.就个人而言,我觉得成为服务员很舒服。 :-) :-)

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% 确定数据已被接收并正确接收。

If I had to guess, the NetworkStream considers the data to have been sent once it hands the buffer off to the Windows Socket.如果我不得不猜测,一旦将缓冲区交给 Windows 套接字,NetworkStream 就会认为数据已经发送。 So, I'm not sure there's a way to accomplish what you want via TcpClient.所以,我不确定有没有办法通过 TcpClient 完成你想要的。

I can not think of a scenario where NetworkStream.Write wouldn't send the data to the server as soon as possible.我想不出 NetworkStream.Write 不会尽快将数据发送到服务器的情况。 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?例如,对于 HTTP,请求标头必须以空行结尾,并且服务器不会发送任何响应,直到发生响应 - 使用的协议是否具有类似的消息结束特性?

Here's some cleaner code than your original version, removing the delegate, field, and Thread.Sleep.这里有一些比原始版本更简洁的代码,删除了委托、字段和 Thread.Sleep。 It preforms the exact same way functionally.它在功能上执行完全相同的方式。

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

I try to understand the intent of .NET NetworkStream designers, and they must design it this way.我试图理解 .NET NetworkStream 设计师的意图,他们必须这样设计。 After Write, the data to send are no longer handled by .NET.写入后,要发送的数据不再由 .NET 处理。 Therefore, it is reasonable that Write returns immediately (and the data will be sent out from NIC some time soon).因此,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.如果超时值可以在运行时配置,那么一切都应该正常工作。

How about using the Flush() method.如何使用 Flush() 方法。

ns.Flush()

That should ensure the data is written before continuing.这应该确保在继续之前写入数据。

Bellow .net is windows sockets which use TCP.波纹管 .net 是 windows sockets 使用 ZB136EF5F6A01D816991FE3CF7A6AC76 TCP uses ACK packets to notify the sender the data has been transferred successfully. TCP 使用 ACK 包通知发送方数据已经传输成功。 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.因此,发送方机器知道何时传输了数据,但无法(据我所知)在 .net 中获取该信息。

edit: Just an idea, never tried: Write() blocks only if sockets buffer is full.编辑:只是一个想法,从未尝试过:Write() 仅在 sockets 缓冲区已满时阻塞。 So if we lower that buffers size (SendBufferSize) to a very low value (8? 1? 0?) we may get what we want:)因此,如果我们将缓冲区大小(SendBufferSize)降低到一个非常低的值(8?1?0?)我们可能会得到我们想要的:)

Perhaps try setting tcp.NoDelay = true也许尝试设置tcp.NoDelay = true

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

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