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

时间:2020-03-05 18:54:45  来源:igfitidea点击:

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

我可能想向想要显示"正在向服务器发送数据"和"正在等待服务器响应"的用户显示状态,我该怎么做?

解决方案

回答

如何使用Flush()方法。

ns.Flush()

那应该确保在继续操作之前已写入数据。

回答

通常,我建议无论如何都要从客户端发送确认。这样,我们可以100%确定已正确接收数据。

回答

如果我不得不猜测的话,一旦将缓冲区交给Windows Socket,NetworkStream就会认为数据已经发送。因此,我不确定是否可以通过TcpClient完成所需的方法。

回答

也许尝试设置
tcp.NoDelay = true

回答

TCP是"可靠"协议,这意味着如果没有套接字错误,则将在另一端接收数据。我已经看到了对TCP进行更高级别的应用程序确认的大量尝试,但是恕我直言,这通常是浪费时间和带宽。

通常,我们描述的问题是通过正常的客户端/服务器设计来解决的,其最简单的形式就是这样...

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

服务器处理完请求并发送响应后,即使套接字在事务处理过程中消失了,服务器通常也不再关心会发生什么,因为这取决于客户端是否可以发起任何进一步的交互。就个人而言,我觉得成为服务器感到非常安慰。 :-)

回答

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

回答

我试图了解.NET NetworkStream设计器的意图,他们必须以这种方式进行设计。写入后,.NET不再处理要发送的数据。因此,合理的做法是立即返回Write(并且不久后将从NIC发送数据)。

因此,在应用程序设计中,除了要按照自己的方式工作之外,还应该遵循这种模式。例如,在从NetworkStream接收任何数据之前可以使用更长的超时时间,以补偿命令离开NIC之前所花费的时间。

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

回答

我不是程序员,但是我们提出这个问题的方式有点误导。对于"接收"的任何有用定义,知道何时"接收"数据的唯一方法是在协议中包含一条特定的确认消息,该消息指示数据已被完全处理。

数据不会完全"离开"网卡。考虑程序与网络关系的最佳方法是:

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

在"很多令人困惑的东西"中可能出现的事情的列表:

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

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

就是说,NetworkStream.Write的文档似乎表明,直到至少启动了"发送"操作,它才会返回:

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

当然,由于我上面给出的原因,"发送"有点模糊。也有可能数据将由程序"真正"发送并由对等程序接收,但是对等方将崩溃或者不进行实际处理。因此,我们应该先对消息进行"写入",然后再进行"读取",该消息仅在对等方实际处理了该消息后才会发出。

回答

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

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