【问题标题】:C# UDP Server loop maxing CPUC# UDP 服务器循环最大化 CPU
【发布时间】:2014-02-23 16:41:11
【问题描述】:

我几乎遍及 Google,甚至在 Stack Overflow 上都查看过,但我似乎找不到我正在寻找的解决方案。我正在测试我的编程技能,使用 MonoGame for C# 重新制作 Pong,我正在尝试使用 UDP 客户端和 UDP 服务器制作这个多人游戏。我将采用“完美的客户端-服务器模型”的想法,服务器处理所有计算,而游戏客户端只是从服务器接收数据并将其显示在屏幕上。不幸的是,我在处理 UDP 服务器编程时遇到过问题。我有一个循环,我在其中接收数据报,然后开始监听另一个。我使用异步调用,因为在我看来,这对客户端和服务器最有效。 Main 代码如下所示:(我将删除不会影响 CPU 的部分,仅显示网络。)

    static void Main()
    {
        //Initialize
        Console.Title = "Pong Server";
        Console.WriteLine("Pong Server");
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        //Bind socket
        EndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 25565);
        Console.WriteLine("Binding to port 25565");
        serverSocket.Bind(localEndPoint);

        //Listen
        Console.WriteLine("Listening on {0}.", localEndPoint);

        //Prepare EndPoints
        EndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);

        //Recive Data...
        rcvPacket = new byte[Data.Size];
        serverSocket.BeginReceiveFrom(rcvPacket, 0, Data.Size, SocketFlags.None, ref clientEndPoint, new AsyncCallback(Receive), null);
    }

然后在Receive(IAsyncResult ar)方法中:

    static void Receive(IAsyncResult ar)
    {
        //Prepare EndPoints
        EndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);

        //End
        int PacketSize = serverSocket.EndReceiveFrom(ar, ref clientEndPoint);

        //<Handle Packet Code Here>

        //Receive Loop
        rcvPacket = new byte[Data.Size];
        serverSocket.BeginReceiveFrom(rcvPacket, 0, Data.Size, SocketFlags.None, ref clientEndPoint, new AsyncCallback(Receive), null);
    }

这段代码的样子是它会等到收到一个数据包,然后再监听另一个数据包,这将停止这个“监听器”线程,是异步的。无论如何,感谢您阅读我的问题,希望我能深入了解这一点。

哦,顺便说一下,这是我的 4 个核心中的一个被最大化的图像。 (是的,我目前正在调试它。)

【问题讨论】:

  • 当在 Receive(IAsyncResult ar) 方法中接收到数据包时,它会侦听另一个数据包。在两个代码sn-ps的底部,可以看到监听器,第二个sn-p就是“循环”
  • 我编辑了原始帖子,以便更容易看到循环。 Receive(ar)Main() 方法(命令行应用程序)调用,并在 Receive(ar) 结束时调用Receive(ar) 方法,继续监听数据包。
  • 在调试器中暂停它并查看调用堆栈,看看它在忙什么。
  • Data.Size 的大小是多少?如果是0,您将得到您描述的行为。
  • 它可能不是空的,但如果没有 sleep 或 yield 它肯定会使用 100% 的 CPU 内核(如果不涉及 IO 请求)。将该代码添加到您的问题中,我们可以帮助您解决它。

标签: c# performance sockets udp cpu-usage


【解决方案1】:

在更新循环中添加 Thread.Sleep(1) 调用不仅修复了原始问题中的 CPU 使用率,而且还使更新循环中执行的代码表现得更好。在我跟踪经过时间和总时间的 while(alive)}{ } 中,我插入了 sleep 调用。

该值似乎需要至少为 1

    static void Main(string[] args)
    {
        //Initialize
        Console.Title = "Pong Server";
        Console.WriteLine("Pong Server");
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        //Bind socket
        EndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 25565);
        Console.WriteLine("Binding to port 25565");
        serverSocket.Bind(localEndPoint);

        //Listen
        Console.WriteLine("Listening on {0}.", localEndPoint);

        //Prepare EndPoints
        EndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);

        //Recive Data...
        serverSocket.BeginReceiveFrom(rcvPacket, 0, Data.Size, SocketFlags.None, ref clientEndPoint, new AsyncCallback(ReceiveFrom), null);

        //Initialize TimeSpans
        DateTime Now = DateTime.Now;
        DateTime Start = Now;
        DateTime LastUpdate = Now;
        TimeSpan EllapsedTime;
        TimeSpan TotalTime;

        //Create Physics Objects
        Paddle1 = new Physics2D();
        Paddle2 = new Physics2D();
        Ball = new Physics2D();

        //Loop
        while (alive)
        {
            Now = DateTime.Now;
            TotalTime = Now - Start;
            EllapsedTime = Now - LastUpdate;
            LastUpdate = Now;
            Update(EllapsedTime, TotalTime);

            //Add Sleep to reduce CPU usage;
            Thread.Sleep(1);
        }

        //Press Any Key
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

【讨论】:

  • Update 做了什么让你需要这么频繁地调用它?
  • 从技术上讲,它是在玩游戏,如果你能想象游戏循环,但游戏循环在服务器上,服务器告诉客户端一切在哪里。m
【解决方案2】:

来自 MSDN 文档:

您的回调方法应该调用 EndReceiveFrom 方法。当您的应用程序调用 BeginReceiveFrom 时,系统将使用单独的线程执行指定的回调方法,并在 EndReceiveFrom 上阻塞,直到 Socket 读取数据或抛出异常。如果您希望在调用 BeginReceiveFrom 方法后阻塞原始线程,请使用 WaitHandle.WaitOne。当您希望原始线程继续执行时,在回调方法中调用 T:System.Threading.ManualResetEvent 的 Set 方法。有关编写回调方法的更多信息,请参阅回调示例

据我了解,您在调用 BeginReceiveFrom 时直接创建回调线程。 因为你不使用 EndReceiveFrom,你的回调线程被执行,创建另一个线程,一次又一次......

使用 EndReceiveFrom 等待整个数据包被读取,然后您的回调将重复并再次开始读取。注意线程安全我不知道你在这里如何管理你的数据。

查看该帖子可能会有所帮助: Asynchronous TCP/UDP Server

【讨论】:

  • -1 他没有在任何地方创建线程,而是按照他应该的方式指定回调。
  • 但是系统在单独的线程上执行回调,所以他只是通过指定一个回调来创建一个。
  • 不,系统会处理该线程。它仅在收到某些内容时调用,或者缓冲区已满或由于错误而被调用。
  • 回调方法与调用 BeginReceive 的方法相同。文档说回调将阻塞 EndReceiveFrom,这里没有出现,因此我会说回调不会阻塞。
  • 他的开始/结束代码非常好。我已经使用了这些方法和回调数十次。你?他如何使用它们不是问题。问题更可能是我在问题评论中所说的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-10
  • 2016-02-26
  • 2017-01-06
  • 2015-04-11
  • 2011-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多