c# - 在主线程中获取 UdpClient 接收数据

标签 c# multithreading sockets unity3d udp

我正在尝试将 LAN 服务器发现添加到我的 Unity 游戏中。我正在使用 UDP 广播请求。如果网络上的任何人是服务器,他们将响应一些服务器数据。我已经弄清楚了这部分,但我需要使用一些 Unity 函数(包括其他自定义的 monobehaviour 脚本)来生成数据包数据。

Unity 不允许从除主线程之外的任何线程访问其 API。

我正在使用 UdpClient.BeginReceive , UdpClient.EndReceive , AsyncCallback流动。所以接收 AsyncCallback 函数(下面示例脚本中的 AsyncRequestReceiveData)在另一个线程中运行,它不允许我使用任何 Unity 函数。

我用了this answer由 flamy 作为我剧本的基础。

我已尝试在 AsyncRequestReceiveData 中触发委托(delegate)和事件当脚本听到请求时,它似乎在同一个单独的线程中触发。

由单独线程在主线程中触发的事件会很好用,但我不确定如何做到这一点。

虽然脚本继承自 MonoBehaviour (Unity 3D 中的基础对象)它应该可以独立运行。

脚本的输出应如下所示:

Sendering Request: requestingServers

Request Received: requestingServers



这是准系统脚本:
using UnityEngine;
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;

public class TestUDP : MonoBehaviour {


    public delegate void RequestReceivedEventHandler(string message);
    public event RequestReceivedEventHandler OnRequestReceived;

    // Use this to trigger the event
    protected virtual void ThisRequestReceived(string message)
    {
        RequestReceivedEventHandler handler = OnRequestReceived;
        if(handler != null)
        {
            handler(message);
        }
    }


    public int requestPort = 55795;

    UdpClient udpRequestSender;
    UdpClient udpRequestReceiver;


    // Use this for initialization
    void Start () {

        // Set up the sender for requests
        this.udpRequestSender = new UdpClient();
        IPEndPoint requestGroupEP = new IPEndPoint(IPAddress.Broadcast, this.requestPort);
        this.udpRequestSender.Connect(requestGroupEP);


        // Set up the receiver for the requests
        // Listen for anyone looking for us
        this.udpRequestReceiver = new UdpClient(this.requestPort);
        this.udpRequestReceiver.BeginReceive(new AsyncCallback(AsyncRequestReceiveData), null);

        // Listen for the request
        this.OnRequestReceived += (message) => {
            Debug.Log("Request Received: " + message);
            // Do some more stuff when we get a request
            // Use `Network.maxConnections` for example
        };

        // Send out the request
        this.SendRequest();
    }

    void Update () {

    }


    void SendRequest()
    {
        try
        {
            string message = "requestingServers";

            if (message != "") 
            {
                Debug.Log("Sendering Request: " + message);
                this.udpRequestSender.Send(System.Text.Encoding.ASCII.GetBytes(message), message.Length);
            }
        }
        catch (ObjectDisposedException e)
        {
            Debug.LogWarning("Trying to send data on already disposed UdpCleint: " + e);
            return;
        }
    }


    void AsyncRequestReceiveData(IAsyncResult result)
    {
        IPEndPoint receiveIPGroup = new IPEndPoint(IPAddress.Any, this.requestPort);
        byte[] received;
        if (this.udpRequestReceiver != null) {
            received = this.udpRequestReceiver.EndReceive(result, ref receiveIPGroup);
        } else {
            return;
        }
        this.udpRequestReceiver.BeginReceive (new AsyncCallback(AsyncRequestReceiveData), null);
        string receivedString = System.Text.Encoding.ASCII.GetString(received);

        // Fire the event
        this.ThisRequestReceived(receivedString);

    }
}

我看过this question (Cleanest Way to Invoke Cross-Thread Events)但我似乎无法弄清楚它是如何工作的以及如何融入其中。

最佳答案

该解决方案的工作原理是跟踪需要在主线程中执行的任务列表(队列),然后在 Update() 中运行该队列。通过 HandleTasks() ;执行并从队列中删除每个。您可以通过调用 QueueOnMainThread() 添加到任务队列并传递一个匿名函数。

我从 this answer 获得了一些灵感由 Pragmateek

您可以有一个单独的脚本来管理主线程任务,或者只是将其合并到您需要在主线程中运行某些东西的现有脚本中。

这里是 full source ,修复了我在问题中的原始片段的解决方案。

以下只是启动和运行队列的必要位:

// We use this to keep tasks needed to run in the main thread
private static readonly Queue<Action> tasks = new Queue<Action>();


void Update () {
    this.HandleTasks();
}

void HandleTasks() {
    while (tasks.Count > 0)
    {
        Action task = null;

        lock (tasks)
        {
            if (tasks.Count > 0)
            {
                task = tasks.Dequeue();
            }
        }

        task();
    }
}

public void QueueOnMainThread(Action task)
{
    lock (tasks)
    {
        tasks.Enqueue(task);
    }
}

要在主线程上排队,只需传递一个 lambda 表达式:
this.QueueOnMainThread(() => {
    // We are now in the main thread...
    Debug.Log("maxConnections: " + Network.maxConnections);
});

如果您使用的是 WinForms,this article: Avoiding InvokeRequired有一些很棒的编码模式。

关于c# - 在主线程中获取 UdpClient 接收数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23588443/

相关文章:

multithreading - mpi: MPI_INIT_THREAD 提供的支持级别

java - 为什么 serverSocket.accept() 只对一个请求执行多次

Android TCP 发送文件。 Android 客户端将图像发送到 Java 服务器。无法发送图像。该怎么办?

c# - 如何测试函数返回 IEnumerable<Integer>

使用 msbuild 构建时出现 C# 版本错误,使用 Visual Studio 可以

c# - 如何使用带有 MVVM 的 WPF 应用程序中的 FolderBrowserDialog

C# 和 bool 的线程安全

java - Tomcat中两个war之间的互通

c# - 如何使用 Timer (Thread) 类处理异常

java - 从 Esper + sockets 开始