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

时间:2014-05-11 02:55:34

标签: c# multithreading sockets unity3d udp

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

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

我正在使用UdpClient.BeginReceiveUdpClient.EndReceiveAsyncCallback流程。因此接收AsyncCallback函数(下面示例脚本中的AsyncRequestReceiveData)在另一个不允许我使用任何Unity函数的线程中运行。

我使用this answer作为我的剧本的基础。

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

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


虽然脚本继承自MonoBehaviour(Unity 3D中的基础对象),但它应该运行得很好。

脚本的输出应如下所示:

  

发送请求:requestedServers

     

请求收到:requesServers

以下是准系统脚本:

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),但我似乎无法弄清楚如何运作以及如何融入。

2 个答案:

答案 0 :(得分:2)

解决方案的工作原理是跟踪需要在主线程中执行的任务列表(队列),然后通过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有一些很好的编码模式。

答案 1 :(得分:-1)

此代码示例是否适合您?可以从任何线程调用X.

//place in class where main thread object is
void X(int sampleVariable)
{
    if (this.InvokeRequired)
    {
        this.Invoke((MethodInvoker) (() => X(sampleVariable)));
        return;
    }

    // write main thread code here
}