TOP PAGE

Unity C# ブロードキャスト受信、UDP送信、受信

送受信のクラス ファイル名:MyNet2.cs

通信相手の宛先情報(IPアドレス,ポート番号)を、ブロードキャストの受信で受け取ります。
これで取得した相手の宛先情報で、自身への宛先情報(IPアドレス,ポート番号)をUDPユニキャストで送信する繰り返しを行います。
このUDP送信の繰り返しは、相手からのUDPユニキャストで受信で終わります。
その後はこの受信だけの繰り返し行います。(このリンク先のUDP送信に対する受信処理です。)

送受信のクラス ファイル名:MyNet1.cs 案その1

部分的にAIに問い合わせて(sensor2_client.pyをUnityのC#のコードにしてください)得たコードを変更しています
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

// 下記メソッドが存在するユユーティリティクラス
public class MyNet2 
{
    private const int BROADCAST_PORT = 59001;
    private CancellationTokenSource cts;// 非同期処理を強制するためのオブジェクト

    private UdpClient udpClient; // センサー端末に自身をアドレスを知らせる時に使う送信用UDP
    private IPEndPoint remoteIpEndPoint;// 上記で使う端末送信時の宛先用
    private string receivedUdpIp;// センサー端末のIPアドレス
    private int receivedUdpPortNo;// センサー端末からデータ受信で使うポート番号

    public delegate void MyReceive(byte[] receivedData);//delegate型を定義
    public MyReceive myReceive;
    public MyNet2()
    {
        this.myReceive = dummy;
    }

    void dummy(byte[] receivedData)// 初期のデリゲート関数myReceiveに設定するダミー関数
    {
        //Debug.Log($"受信データ:{Encoding.UTF8.GetString(receivedData)}");
        StringBuilder stringBuilder = new StringBuilder();
        foreach( byte c in receivedData)
        {
            stringBuilder.Append($"{c:X} ");
        }
        Debug.Log($"受信データ:{stringBuilder}");
    }

    // 非同期で、portNoのブロードキャストで受信後、StartUnicastCommunicationメソッドを起動
    public async void ReceiveBroadcastAsync()
    {
        try
        {
            using (UdpClient broadcastReceiver = new UdpClient(BROADCAST_PORT))
            {
                Debug.Log("Waiting for broadcast...");
                // ブロードキャストの受信
                UdpReceiveResult result = await broadcastReceiver.ReceiveAsync();
                byte[] receivedBytes = result.Buffer;
                IPEndPoint sender = result.RemoteEndPoint;

                Debug.Log($"Received: {Encoding.UTF8.GetString(receivedBytes)} from {sender}");

                string recData = Encoding.UTF8.GetString(receivedBytes);
                string[] ipPortArray = recData.Split(',');
                if (ipPortArray.Length == 2)
                {
                    receivedUdpIp = ipPortArray[0];
                    if (int.TryParse(ipPortArray[1], out receivedUdpPortNo))
                    {
                        Debug.Log($"受信データ (IP): {receivedUdpIp}, (Port): {receivedUdpPortNo}");
                        
                        cts = new CancellationTokenSource();// 非同期を終了するためのオブジェクト生成
                        StartUnicastCommunication(cts.Token);// 非同期のユニキャスト処理を開始
                    }
                    else
                    {
                        Debug.LogError("Invalid port number received in broadcast.");
                    }
                }
                else
                {
                    Debug.LogError("Invalid data format received in broadcast.");
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Broadcast reception error: {e.Message}");
        }
    }

    // UDP送受信関数
    private async void StartUnicastCommunication(CancellationToken cancellationToken)
    {
        try
        {
            udpClient = new UdpClient();
            udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, receivedUdpPortNo));
            remoteIpEndPoint = new IPEndPoint(IPAddress.Parse(receivedUdpIp), receivedUdpPortNo);

            Debug.Log($"UDP 受信待機中 (ポート {receivedUdpPortNo})...");

            string localIp = GetLocalIPAddress(); // 実際のIPアドレスを取得する場合
            if(localIp=="")localIp = "192.168.0.110"; // インターネットが使えない場合に使う固定値

            string sendMessageStr = $"{localIp},{receivedUdpPortNo}";
            byte[] sendBytes = Encoding.UTF8.GetBytes(sendMessageStr);

            // 自身の情報を送信し、返信を受信するまで繰り返す
            while (!cancellationToken.IsCancellationRequested)
            {
                Debug.Log($"sendto( {sendMessageStr}, {remoteIpEndPoint} )");
                await udpClient.SendAsync(sendBytes, sendBytes.Length, remoteIpEndPoint);
                await Task.Delay(1000); // 1秒待機

                try
                {
                    // 返信の受信を試みる(ノンブロッキングではないが、タスクで待機)
                    // CancellationTokenを渡すことで、アプリケーション終了時にキャンセル可能
                    UdpReceiveResult result = await udpClient.ReceiveAsync();
                    byte[] receivedData = result.Buffer;
                    myReceive(receivedData); // 受信で実行するdelegate
                    break; // 受信できたらループを抜ける
                }
                catch (SocketException e)
                {
                    // 受信がなくてもエラーを吐かないようにするが、厳密にはTimeoutを設定すべき
                    // このケースではPythonのノンブロッキング+OSErrorを模倣しているため、
                    // receiveBroadcastAsyncの後の最初の受信まではエラーを気にしない。
                    // ただし、Unityでは非同期OperationCanceledExceptionを考慮する必要がある。
                    if (e.SocketErrorCode != SocketError.WouldBlock && e.SocketErrorCode != SocketError.TimedOut)
                    {
                        Debug.LogError($"Error receiving initial reply: {e.Message}");
                    }
                }
                catch (OperationCanceledException)
                {
                    Debug.Log("Unicast send/receive loop cancelled.");
                    return;
                }
            }

            // 受信ループを開始
            int count = 1;
            while (!cancellationToken.IsCancellationRequested)
            {
                UdpReceiveResult result = await udpClient.ReceiveAsync();
                byte[] receivedData = result.Buffer;
                Debug.Log($"{count}回目の受信データ");
                myReceive(receivedData); // 受信で実行するdelegate
                count++;
            }
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Unicast communication cancelled.");
        }
        catch (Exception e)
        {
            Debug.LogError($"Unicast communication error: {e.Message}");
        }
        finally
        {
            if (udpClient != null)
            {
                udpClient.Close();
                udpClient = null;
                Debug.Log("UDP Client closed.");
            }
        }
    }

    private string GetLocalIPAddress()// 自身のローカルIPアドレスの文字列を取得
    {
        string localIP = "";
        using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
        {
            socket.Connect("8.8.8.8", 65530); // Google Public DNSに接続してみる
            IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
            localIP = endPoint.Address.ToString();
        }
        return localIP;
    }
}

送受信のクラス ファイル名:MyNet2.cs 案その2

上記で AIからUdpReceiveResult result = await udpClient.ReceiveAsync();を使う方法が得られましたが この方法は、動きましたが、好ましくないと判断しています。
StartUnicastCommunicationメソッド内で、 この繰り返しに送信を含んでいますが、をれを相手に届けないと、相手は送信してこないためです。
そこで、StartUnicastCommunicationメソッド内の受信に対してタイムアウトを指定する方法に変更しました。
これで、実質的にノンブロッキングと同等の処理になりました。
自身へ宛先を送信し、それを受信した相手から自身の宛先でデータ送信が繰り返される情報を受け取る関数です。
    private async void StartUnicastCommunication(CancellationToken cancellationToken)
    {
        try
        {
            udpClient = new UdpClient();
            udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, receivedUdpPortNo));
            remoteIpEndPoint = new IPEndPoint(IPAddress.Parse(receivedUdpIp), receivedUdpPortNo);

            Debug.Log($"UDP 受信待機中 (ポート {receivedUdpPortNo})...");

            string localIp = GetLocalIPAddress(); // 実際のIPアドレスを取得する場合
            if(localIp=="")localIp = "192.168.0.110"; // インターネットが使えない場合に使う固定値

            string sendMessageStr = $"{localIp},{receivedUdpPortNo}";
            byte[] sendBytes = Encoding.UTF8.GetBytes(sendMessageStr);

            // 自身の情報を送信し、返信を受信するまで繰り返す(送信と受信のループ)
            while (!cancellationToken.IsCancellationRequested)
            {
                await udpClient.SendAsync(sendBytes, sendBytes.Length, remoteIpEndPoint);
                Debug.Log($"sendto( {sendMessageStr}, {remoteIpEndPoint} )");

                
                int timeoutMilliseconds = 1000; // 1.0秒 タイムアウトを設定
                Task<UdpReceiveResult> receiveTask = udpClient.ReceiveAsync();
                Task timeoutTask = Task.Delay(timeoutMilliseconds, cancellationToken); // キャンセル可能にする
                Task completedTask = await Task.WhenAny(receiveTask, timeoutTask);


                if (completedTask == receiveTask)
                {
                    // データが受信された場合
                    UdpReceiveResult result = await receiveTask; // ReceiveAsyncの結果を取得
                    byte[] receivedData = result.Buffer;
                    Debug.Log($"1回目の受信データ:{Encoding.UTF8.GetString(receivedData)}");
                    myReceive(receivedData); // 受信で実行するdelegate
                    break;
                }
                else if (completedTask == timeoutTask)
                {
                    // タイムアウトした場合
                    Debug.LogWarning($"UDP receive timed out after {timeoutMilliseconds / 1000} seconds.");
                }
                else if (cancellationToken.IsCancellationRequested)
                {
                    Debug.Log("Unicast receive loop cancelled during await.");
                    break;
                }
            }

            // 受信だけループを開始
            int count = 2;
            while (!cancellationToken.IsCancellationRequested)
            {
                UdpReceiveResult result = await udpClient.ReceiveAsync();
                byte[] receivedData = result.Buffer;
                Debug.Log($"{count}回目の受信データ");
                myReceive(receivedData); // 受信で実行するdelegate
                count++;
            }
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Unicast communication cancelled.");
        }
        catch (Exception e)
        {
            Debug.LogError($"Unicast communication error: {e.Message}");
        }
        finally
        {
            if (udpClient != null)
            {
                udpClient.Close();
                udpClient = null;
                Debug.Log("UDP Client closed.");
            }
        }
    }

上記の呼び出しコード例(MyNet2クラスの利用例)


public class SensorNet2 : MonoBehaviour 
{
    MyNet2 myNet2;

    void Start()
    {
        myNet2 = new MyNet2();
 
        myNet2.ReceiveBroadcastAsync();
        // 上記非同期処理内で、ブロードキャストで、センサー端末のIPとポート知る。
        // その後、自身の宛先をセンサー端末へUDPで知らせ、応答のデータ受信を繰り返す。
        // 上記で受信が出来たら、自身の宛先をセンサー端末へUDPで知らせる送信処理を止める。
        // データ受信の繰り返しだけ永続するが、この受信データは、MyReceive myReceiveのdelegateで処理する

    }