noir_neo’s blog

日記と偶にスマホゲームの感想と、重箱の隅をつつくようなUI/UX批評

Unity で System.Net.WebSockets を使ったゲームサーバー書こうとしたらうまくいかない

今週ずっと悩んでる。

動機

なんとなくリアルタイムサーバーっていうか、 websocket でクライアントと繋がってるサーバーがどうなってるのかよくわかってなかったので、雑に書いてみたかった。 Unity をリアルタイムサーバーにしたければ、 NetworkManager とか使えばすぐできるのはわかってて、 適当に体験する分には楽かなと思って .Net 4.5 で入ったらしいそれで雑に書いてみたかった。

うまくいかないこと

HttpListener でリクエストを受け取って、ハンドシェイクするところが、どうにも websocket として判定されない。

var httpListener = new HttpListener();
httpListener.Prefixes.Add(uriPrefix);
httpListener.Start();

while (true)
{
    var listenerContext = await httpListener.GetContextAsync();
    if (listenerContext.Request.IsWebSocketRequest) // これが true にならない
    {

全コードは最後に。

windows でも mac でもだめ。 Unity 2017.3 と 2018.1b10 は試した。 参考にした .Net のアプリケーションを実行しても同じだったので、 元のコードが悪そうだけど、どうすればいいのか検討もつかない。

Windows 7 で動かないという情報しか見つからない。久しぶりに"詰んだ"感覚を味わっている。

実装

一応載せておくけど、ほぼ下記のコピペ

WebSocket-Samples/Server.cs at master · paulbatum/WebSocket-Samples · GitHub

サーバー

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public class Server : MonoBehaviour
{
    [SerializeField] private string uriPrefix;

    void Awake()
    {
        StartServer(uriPrefix);
        Log("start.");
    }

    void OnDestroy()
    {
        Parallel.ForEach(_client,p=>
        {
            if (p.State == WebSocketState.Open) p.CloseAsync(WebSocketCloseStatus.NormalClosure, "", System.Threading.CancellationToken.None);
        });
    }

    static readonly List<WebSocket> _client = new List<WebSocket>();

    static async void StartServer(string uriPrefix)
    {
        var httpListener = new HttpListener();
        httpListener.Prefixes.Add(uriPrefix);
        httpListener.Start();

        while (true)
        {
            var listenerContext = await httpListener.GetContextAsync();
            if (listenerContext.Request.IsWebSocketRequest)
            {
                ProcessRequest(listenerContext);
            }
            else
            {
                listenerContext.Response.StatusCode = 400;
                listenerContext.Response.Close();
                Log("Response 400");
            }
        }
    }

    static async void ProcessRequest(HttpListenerContext listenerContext)
    {
        Log($"New Session:{listenerContext.Request.RemoteEndPoint.Address}");

        var ws = (await listenerContext.AcceptWebSocketAsync(null)).WebSocket;
        _client.Add(ws);

        while (ws.State == WebSocketState.Open)
        {
            try
            {
                var buff = new ArraySegment<byte>(new byte[1024]);

                var ret = await ws.ReceiveAsync(buff, System.Threading.CancellationToken.None);

                if (ret.MessageType == WebSocketMessageType.Text)
                {
                    Log($"String Received:{listenerContext.Request.RemoteEndPoint.Address}");
                    Log($"Message={Encoding.UTF8.GetString(buff.Take(ret.Count).ToArray())}");

                    Parallel.ForEach(_client,
                        p => p.SendAsync(new ArraySegment<byte>(buff.Take(ret.Count).ToArray()),
                        WebSocketMessageType.Text,
                        true,
                        System.Threading.CancellationToken.None));
                }
                else if(ret.MessageType == WebSocketMessageType.Close)
                {
                    Log($"Session Close:{listenerContext.Request.RemoteEndPoint.Address}");
                    break;
                }
            }
            catch
            {
                Log($"Session Abort:{listenerContext.Request.RemoteEndPoint.Address}");
                break;
            }
        }

        _client.Remove(ws);
        ws.Dispose();

    }

    private static void Log(object o)
    {
        Debug.Log($"Server: {o}");
    }
}