gpt4 book ai didi

c# - 我是否需要在异步BeginReceive回调中同步TCP/UDP客户端

转载 作者:行者123 更新时间:2023-11-30 21:54:26 25 4
gpt4 key购买 nike

我有一个使用UdpClientTcpClientTcpListener的多线程网络应用程序,并使用例如处理接收的连接和接收的数据。 BeginReceive() EndReceive()回调模式。

以UdpClient为例,在这种模式下,我正在使用的一般工作流程是:

  • 调用UdpClient.BeginReceive()
  • 当接收到数据报时执行接收回调。
  • 调用UdpClient.EndReceive()收集数据报。
  • 再次调用UdpClient.BeginReceive()以准备接收另一个数据报。
  • 处理在(3)处接收到的数据报。
  • 重复2-5,因为收到更多数据报

  • 问:由于仅存在一个 UdpClient对象,并且由于始终在下一个 EndReceive()之前调用 BeginReceive()的模式,是否有必要将这些调用的访问权限锁定/同步到 UdpClient对象?

    在我看来,另一个线程不可能干预此工作流程或使这些调用成为非原子调用。 TcpClient.BeginReceive()TcpListener.BeginAcceptTcpClient()的模式非常相似。

    奖励Q:是否需要将单个UdpClient对象声明为 static(如果需要,则声明 static锁定 object)?

    注意:我是 ,而不是,询问是否有必要在例如数据报处理。仅关于此模式和 UdpClient TcpClient TcpListener对象。

    编辑

    通过澄清,(忽略异常处理)以下代码:
    private void InitUDP()
    {
    udpclient = new UdpClient(new IPEndPoint(IPAddress.Any, Settings.Port));

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
    }

    private void receiveCallback(IAsyncResult ar)
    {
    UdpClient client = (UdpClient)ar.AsyncState;

    IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

    byte[] datagram = client.EndReceive(ar, ref ep);

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);

    processDatagram();
    }

    实际上与以下代码不同或保护性较低:
    private void InitUDP()
    {
    udpclient = new UdpClient(new IPEndPoint(IPAddress.Any, Settings.Port));

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
    }

    private void receiveCallback(IAsyncResult ar)
    {
    UdpClient client = (UdpClient)ar.AsyncState;

    IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);

    lock(_lock)
    {
    byte[] datagram = client.EndReceive(ar, ref ep);

    udpclient.BeginReceive(new AsyncCallback(receiveCallback), udpclient);
    }

    processDatagram();
    }

    最佳答案

    is it necessary to lock/synchronise acccess to the UdpClient object for those calls?



    不,不完全是,但可能不是出于您可能会想的原因。

    如果在处理完当前数据报之前调用 BeginReceiveFrom()(或者只是 BeginReceive()),则实际上有可能同时调用同一回调。是否真的会发生这种情况取决于很多事情,包括线程调度,线程池中当前有多少个IOCP线程可用,当然,甚至还有一个数据报要接收。

    因此,您绝对有风险,在完成当前数据报的处理之前,将发生新数据报的接收,并且将在处理第一个数据报之前开始对其进行处理。

    现在,如果数据报的处理涉及对某些其他共享数据的访问,那么您确实确实需要围绕该其他共享数据进行同步,以确保对其他数据的安全访问。

    但是就数据报本身而言,网络对象是线程安全的,从某种意义上讲,您不会同时使用该对象来破坏该对象……仍然需要确保您以一致的方式使用它们。但是特别是对于UDP协议(protocol),这比TCP更容易。

    UDP不可靠。它缺乏三个非常重要的保证:
  • 无法保证将发送数据报。
  • 无法保证数据报不会被多次传递。
  • 不能保证数据报的发送顺序与发送该数据报的其他数据报的顺序相同。

  • 最后一点在这里特别重要。您的代码已经需要能够无序地处理数据报。因此,不管这种数据报困惑是由于网络本身发生,还是由于您在处理完当前I/O操作之前启动了新的I/O操作,如果编写正确,您的代码都将成功地对其进行处理。

    使用TCP,情况有所不同。再次遇到同样的问题,即如果您已开始I/O操作,则很可能可以在完成当前I/O操作之前完成该操作。但是与UDP不同,您确实对TCP有一些保证,包括套接字上接收的数据将按照发送时的相同顺序进行接收。

    因此,只要您不完全处理完当前已完成的I/O操作就不会调用 BeginReceive(),一切都很好。您的代码以正确的顺序查看数据。但是,如果您较早调用 BeginReceive(),那么当前线程可能会在处理完当前I/O操作之前被抢占,而另一个线程可能会结束对新完成的I/O操作的处理。

    除非您对接收到的数据进行某种形式的同步或排序以解决乱序处理I/O完成的可能性,否则这将损坏您的数据。不好。

    有充分的理由同时发出多个接收操作。但是它们通常与对高度可扩展的服务器的需求有关。发出多个并发接收操作也会带来负面影响,包括确保以正确的顺序处理数据的额外复杂性,以及堆中具有多个固定/固定缓冲区的开销(尽管可以在多种方式,例如分配足够大的缓冲区以确保它们在大对象堆中)。

    我将避免以这种方式实现代码,除非您必须解决特定的性能问题。即使在处理UDP时,尤其是在处理TCP时。而且,如果您确实以这种方式实现代码,则应格外小心。

    Does the single UdpClient object need to be declared static (and static lock object if one is required)?



    您存储对 UdpClient对象的引用的位置并不重要。如果您的代码需要一次维护一个以上的 UdpClient,那么将引用存储在单个 UdpClient -type字段中甚至都不是很方便。

    制作 static所做的所有事情就是改变了该成员的访问方式。如果不是 static,则需要指定在其中找到该成员的实例引用;如果是 static,则只需指定类型。就这样。它与线程安全本身根本没有任何关系。

    最后,对于您的两个代码示例,它们在功能上是等效的。无需保护对 EndReceive()BeginReceive()的调用,并且您的 lock不包含这些方法的任何其他部分(例如,数据报的实际处理),因此它实际上并没有完成任何事情(除了可能会增加开销)上下文切换)。

    在并发场景中,有可能在离开 lock之前但在调用 BeginReceive()之后会抢占第一个线程。这可能会导致第二个线程被唤醒,以处理用于第二次I/O完成的回调。然后,第二个线程将命中 lock并停顿,从而允许第一个线程恢复执行并离开 lock。但是同步所做的只是使速度变慢。它不会阻止对数据报数据本身的任何并发访问,这是(可能)重要的部分。

    关于c# - 我是否需要在异步BeginReceive回调中同步TCP/UDP客户端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33114541/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com