【Unity】WebSocketSharpを用いたWebSocket通信環境の構築

WebSocketSharp(https://github.com/sta/websocket-sharp)を利用してWebSocketServerをUnity上に構築するメモ。主にクライアントからサーバに向けてデータを送信する用途向け。https://www.kemomimi.dev/unity/54/ の記事と違う点は同時に複数のサーバを立てる(複数のパス、ポートで同時に起動する)ことができるようになっている。

Env.

Unity2021.3.6f1

Source

WebSocketServerBaseクラス
このクラスを継承することでWebSocketのカスタムサーバが簡単に立てられる

using UnityEngine;
using System.Threading;
using WebSocketSharp.Server;

public abstract class WebSocketServerBase : MonoBehaviour
{
    [SerializeField] protected string path = "/";
    [SerializeField] protected int port = 8080;

    private WebSocketServer server;
    private SynchronizationContext context;

    const string DEBUGLOG_PREFIX = "[<color=#FF9654>WSServer</color>]";
    
    protected void StartServer(string path = "/", int port = 8080)
    {
        this.path = path;
        this.port = port;

        context = SynchronizationContext.Current;

        server = new WebSocketServer(port);
        server.AddWebSocketService<WebSocketServerBehavior>(path, serverBehavior =>
        {
            serverBehavior.SetContext(context, OnOpen, OnMessage, OnClose);
        });

        server.Start();
        
        Debug.Log($"{DEBUGLOG_PREFIX} Start path={path}, port={port}");
    }
    
    protected void StopServer()
    {
        server.Stop();
        server.RemoveWebSocketService(path);
        server = null;
        
        Debug.Log($"{DEBUGLOG_PREFIX} Stop path={path}, port={port}");
    }

    private void OnDestroy() => StopServer();

    protected virtual void OnOpen()
    {
        Debug.Log($"{DEBUGLOG_PREFIX} OnOpen");
    }

    protected virtual void OnMessage(byte[] data)
    {
        Debug.Log($"{DEBUGLOG_PREFIX} Received message: {data.Length}");
    }

    protected virtual void OnClose()
    {
        Debug.Log($"{DEBUGLOG_PREFIX} OnClose");
    }
}

WebSocketServerBehavior クラス
WebSocketServerBaseでWebSocketServer.AddWebSocketServiceで立てられる本体。
受信したデータをメインスレッドで扱うことが可能なようにSynchronizationContextを使って各イベントが呼び出されたときにメインスレッドで実行されるようになっている。

using System.Threading;
using WebSocketSharp;
using WebSocketSharp.Server;

public class WebSocketServerBehavior : WebSocketBehavior
{
    private SynchronizationContext context;

    private OnOpenDelegate onOpen;
    private OnMessageDelegate onMessage;
    private OnCloseDelegate onClose;
    
    public delegate void OnOpenDelegate();
    public delegate void OnMessageDelegate(byte[] data);
    public delegate void OnCloseDelegate();
    
    public void SetContext(SynchronizationContext context, OnOpenDelegate onOpenDelegate, OnMessageDelegate onMessageDelegate, OnCloseDelegate onCloseDelegate)
    {
        this.context = context;
        this.onOpen = onOpenDelegate;
        this.onMessage = onMessageDelegate;
        this.onClose = onCloseDelegate;
    }

    protected override void OnOpen()
    {
        context?.Post(_ =>
        {
            onOpen?.Invoke();
        }, null);
    }

    protected override void OnMessage (MessageEventArgs e)
    {
        context?.Post(_ =>
        {
            onMessage?.Invoke(e.RawData);
        }, null);
    }

    protected override void OnClose (CloseEventArgs e)
    {
        context?.Post(_ =>
        {
            onClose?.Invoke();
        }, null);
    }
}

WebSocketClientBase クラス
クライアント側基底クラス。isOpen で接続が確立されたことを見ているが接続が確立されていないときにSendDataが呼び出されるとエラーを吐くので調整が必要。

using System;
using System.Threading;
using UnityEngine;
using WebSocketSharp;

public abstract class WebSocketClientBase : MonoBehaviour
{
    [SerializeField] private string serverAddress = "localhost";
    [SerializeField] private int port = 8080;
    [SerializeField] private string path = "/";

    protected Action onOpenAction;
    protected Action onCloseAction;
    protected Action<byte[]> onMessageAction;

    private WebSocket client;
    private SynchronizationContext context;
    private bool isOpen = false;
    
    const string DEBUGLOG_PREFIX = "[<color=#FF9654>WSClient</color>]";

    public void StartCient()
    {
        context = SynchronizationContext.Current;
        
        var url = $"ws://{serverAddress}:{port}{path}";
        client = new WebSocket(url);

        client.OnOpen += OnOpen;
        client.OnClose += OnClose;
        client.OnMessage += OnMessage;
        client.OnError += OnError;

        client.ConnectAsync();
        Debug.Log($"{DEBUGLOG_PREFIX} Connecting to server url={url}");
    }
    
    public void StopClient()
    {
        if (client == null)
            return;
        
        client.CloseAsync();
        
        client.OnOpen -= OnOpen;
        client.OnClose -= OnClose;
        client.OnMessage -= OnMessage;
        client.OnError -= OnError;
    }

    private void OnDestroy() => StopClient();

    protected virtual void OnOpen(object sender, EventArgs e)
    {
        context?.Post(_ =>
        {
            Debug.Log($"{DEBUGLOG_PREFIX} Connected to server");
            isOpen = true;
            
            onOpenAction?.Invoke();
        }, null);
    }

    protected virtual void OnClose(object sender, CloseEventArgs e)
    {
        context?.Post(_ =>
        {
            Debug.Log($"{DEBUGLOG_PREFIX} Disconnected from server ({e.Code} {e.Reason}) url={client.Url}");
            isOpen = false;
            
            onOpenAction?.Invoke();
        }, null);
    }
    
    protected virtual void OnMessage(object sender, MessageEventArgs e)
    {
        context?.Post(_ =>
        {
            Debug.Log($"{DEBUGLOG_PREFIX} Received message: {e.RawData.Length}");
            
            onMessageAction?.Invoke(e.RawData);
        }, null);
    }
    
    protected virtual void OnError(object sender, ErrorEventArgs e)
    {
        context?.Post(_ =>
        {
            Debug.Log($"{DEBUGLOG_PREFIX} Error: {e.Message}");
        }, null);
    }

    public void SendData(byte[] data)
    {
        if (!isOpen)
            return;
        
        client?.SendAsync(data, null);
    }
}


この記事が気に入ったらサポートをしてみませんか?