2014-05-11 29 views
1

我想添加局域網服務器發現到我的Unity遊戲。我正在使用UDP來廣播請求。如果網絡上的任何人是服務器,他們將以一些服務器數據作出響應。我已經想出了這一部分,但我需要使用一些Unity函數(包括其他自定義monobehaviour腳本)來生成分組數據。獲取UdpClient接收數據返回主線程

Unity不允許從主函數以外的任何線程訪問其API。

我正在使用UdpClient.BeginReceive,UdpClient.EndReceive,AsyncCallback流。因此,接收AsyncCallback函數(下面示例腳本中的AsyncRequestReceiveData)在另一個不允許使用任何Unity函數的線程中運行。

我用flamy作爲我的腳本的基礎,使用this answer

當腳本聽到一個請求時,我試過讓一個委託和事件在AsyncRequestReceiveData中觸發,但似乎是在同一個單獨的線程中觸發。

單線程在主線程中觸發的事件會很好,但我不確定如何完成此操作。


雖然腳本MonoBehaviour(在Unity 3D基對象)繼承了它應該運行就好了獨立。

腳本的輸出應該是這樣的:

Sendering請求:requestingServers

收到申請: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),但我似乎無法弄清楚它如何工作,以及如何納入。

+0

這是WinForms? –

+0

@AgentShark [Unity 3D](https://unity3d.com)在我的情況。雖然這個問題並不完全是Unity。 – MLM

回答

2

解決方案的工作通過保持需要在主線程中執行的任務(隊列)的列表中的曲目,然後通過Update()通過HandleTasks()在隊列中運行;執行並從隊列中刪除每一個。您可以通過調用QueueOnMainThread()並傳遞一個匿名函數來添加到任務隊列中。

我通過Pragmateek

你可以有一個單獨的腳本來管理主線任務或者只是把它納入你現有的腳本,你需要運行在主線程的東西得到了this answer一些啓發。

這裏是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

這段代碼是否適用於您的示例工作? 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 
} 
+0

這不起作用,大多數這些方法,類都在'System.Windows.Forms'中,這些在Unity中不可用。如果我使用WinForms,還有一些更好的替代模式,我在[本文](http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired)中找到。 – MLM