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

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

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

尽管有文档,但NetworkStream.Write似乎不会等到发送数据后再进行。相反,它将等待直到将数据复制到缓冲区中然后返回。该缓冲区在后台传输。

这是我目前的代码。我使用ns.Write还是ns.BeginWrite都无关紧要-两者都立即返回。 EndWrite也立即返回(这是有意义的,因为它正在写入发送缓冲区,而不是写入网络)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    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#程序员,但是您提出这个问题的方式有点误导。对于"接收"的任何有用定义,知道何时"接收"数据的唯一方法是在协议中包含一条特定的确认消息,该消息指示数据已被完全处理。

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

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.

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


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

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

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

服务器处理完请求并发送响应后,它通常不再关心发生的事情-即使套接字在事务处理过程中消失了-因为由客户端来决定是否进行进一步的交互。就个人而言,我觉得成为服务器感到非常安慰。 :-)


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


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

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

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


我无法想到NetworkStream.Write不会尽快将数据发送到服务器的情况。除非出现严重的网络拥塞或断开连接,否则它应在合理的时间内最终到达另一端。您是否有协议问题?例如,使用HTTP时,请求标头必须以空行结尾,并且服务器将不会发送任何响应,直到响应发生为止-使用的协议是否具有类似的消息结束特性?

这是比原始版本更干净的代码,删除了委托,字段和Thread.Sleep。它在功能上完全相同。

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


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


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

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


如何使用Flush()方法。

1
ns.Flush()

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


也许尝试设置
tcp.NoDelay = true


推荐阅读