C#標準ライブラリでWebSocketクライアントを作る

C#でWebSocketクライアントを作りたい場合、websocket-sharpというライブラリがよく使われているが、標準ライブラリだけで賄いたい。

標準でSystem.Net.WebSockets名前空間にClientWebSocketクラスが用意されており、MSDNに使い方が載っている。

Windows 8 のネットワーク接続 - Windows 8 と WebSocket プロトコル

一応通信はできるのだが、あまりにも原始的なのでラッパークラスを作る。

using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

public class WebSocketClient
{
    private const int SendBufferSize = 1024;
    private const int ReceiveBufferSize = 1024;

    private ClientWebSocket Socket;

    public event EventHandler OnOpenHandler;
    public event EventHandler OnCloseHandler;
    public event EventHandler<string> OnTextHandler;
    public event EventHandler<byte[]> OnBinaryHandler;

    public Uri Uri { get; set; }

    public async Task SendTextAsync(string text)
    {
        using var memoryStream = new MemoryStream();
        using var streamWriter = new StreamWriter(memoryStream);

        await streamWriter.WriteAsync(text);
        await streamWriter.FlushAsync();

        memoryStream.Seek(0, SeekOrigin.Begin);

        while (memoryStream.Position < memoryStream.Length)
        {
            var buffer = new byte[SendBufferSize];

            var remaining = memoryStream.Length - memoryStream.Position;

            var bufferSize = SendBufferSize < remaining ? SendBufferSize : (int)remaining;

            await memoryStream.ReadAsync(buffer, 0, buffer.Length);

            await Socket.SendAsync(new ArraySegment<byte>(buffer, 0, bufferSize), WebSocketMessageType.Text, !(memoryStream.Position < memoryStream.Length), CancellationToken.None);
        }
    }

    public async Task SendBinaryAsync(byte[] binary)
    {
        using var memoryStream = new MemoryStream();

        memoryStream.Write(binary, 0, binary.Length);

        memoryStream.Seek(0, SeekOrigin.Begin);

        while (memoryStream.Position < memoryStream.Length)
        {
            var buffer = new byte[SendBufferSize];

            var remaining = memoryStream.Length - memoryStream.Position;

            var bufferSize = SendBufferSize < remaining ? SendBufferSize : (int)remaining;

            await memoryStream.ReadAsync(buffer, 0, buffer.Length);

            await Socket.SendAsync(new ArraySegment<byte>(buffer, 0, bufferSize), WebSocketMessageType.Binary, !(memoryStream.Position < memoryStream.Length), CancellationToken.None);
        }
    }

    public async Task ConnectAsync()
    {
        if (Socket?.State == WebSocketState.Open) return;

        Socket = new ClientWebSocket();

        await Socket.ConnectAsync(Uri, CancellationToken.None);

        OnOpenHandler(this, EventArgs.Empty);
    }

    public async Task CloseAsync()
    {
        if (Socket == null || Socket.State == WebSocketState.Closed) return;

        await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
    }

    public WebSocketClient(Uri uri)
    {
        Uri = uri;

        OnOpenHandler += async (sender, e) => 
        {
            using var memoryStream = new MemoryStream();
            using var streamReader = new StreamReader(memoryStream);

            WebSocketMessageType messageType = WebSocketMessageType.Text;

            while (Socket.State == WebSocketState.Open)
            {
                var buffer = new byte[ReceiveBufferSize];

                var result = await Socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

                if (messageType != result.MessageType)
                {
                    messageType = result.MessageType;

                    await memoryStream.FlushAsync();

                    memoryStream.Seek(0, SeekOrigin.Begin);
                    memoryStream.SetLength(0);
                }

                if (result.MessageType == WebSocketMessageType.Text)
                {
                    await memoryStream.WriteAsync(buffer, 0, result.Count);

                    if (result.EndOfMessage)
                    {
                        await memoryStream.FlushAsync();

                        memoryStream.Seek(0, SeekOrigin.Begin);

                        OnTextHandler(this, streamReader.ReadToEnd());

                        memoryStream.Seek(0, SeekOrigin.Begin);
                        memoryStream.SetLength(0);
                    }
                }
                else if (result.MessageType == WebSocketMessageType.Binary)
                {
                    await memoryStream.WriteAsync(buffer, 0, result.Count);

                    if (result.EndOfMessage)
                    {
                        await memoryStream.FlushAsync();

                        memoryStream.Seek(0, SeekOrigin.Begin);

                        OnBinaryHandler(this, memoryStream.ToArray());

                        memoryStream.Seek(0, SeekOrigin.Begin);
                        memoryStream.SetLength(0);
                    }
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    OnCloseHandler(this, EventArgs.Empty);
                }
            }
        };
    }

    public WebSocketClient(string uri) : this(new Uri(uri))
    {

    }
}
var ws = new WebSocketClient("ws://localhost:3000");

ws.OnOpenHandler += async (event, e) =>
{
    ws.OnTextHandler += (event, e) =>
    {
        Console.WriteLine(e);
    };
    
    ws.SendText("aaaaaaaaaa");
    
    await ws.CloseAsync();
};

ws.ConnectAsync();

LINQで短くする

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

public class WebSocketClient
{
    private const int SendBufferSize = 1024;
    private const int ReceiveBufferSize = 1024;

    private ClientWebSocket Socket;

    public event EventHandler OnOpenHandler;
    public event EventHandler OnCloseHandler;
    public event EventHandler<string> OnTextHandler;
    public event EventHandler<byte[]> OnBinaryHandler;

    public Uri Uri { get; set; }

    public async Task SendTextAsync(string text)
    {
        var values = Encoding.UTF8.GetBytes(text).Select((value, index) => (value, index)).GroupBy(a => a.index / SendBufferSize).Select(a => a.Select(b => b.value).ToArray()).ToArray();

        foreach (var value in values)
        {
            await Socket.SendAsync(new ArraySegment<byte>(value), WebSocketMessageType.Text, value == values.Last(), CancellationToken.None);
        }
    }

    public async Task SendBinaryAsync(byte[] binary)
    {
        var values = binary.Select((value, index) => (value, index)).GroupBy(a => a.index / SendBufferSize).Select(a => a.Select(b => b.value).ToArray()).ToArray();

        foreach (var value in values)
        {
            await Socket.SendAsync(new ArraySegment<byte>(value), WebSocketMessageType.Binary, value == values.Last(), CancellationToken.None);
        }
    }

    public async Task ConnectAsync()
    {
        if (Socket?.State == WebSocketState.Open) return;

        Socket = new ClientWebSocket();

        await Socket.ConnectAsync(Uri, CancellationToken.None);

        OnOpenHandler(this, EventArgs.Empty);
    }

    public async Task CloseAsync()
    {
        if (Socket == null || Socket.State == WebSocketState.Closed) return;

        await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
    }

    public WebSocketClient(Uri uri)
    {
        Uri = uri;

        OnOpenHandler += async (sender, e) => 
        {
            WebSocketMessageType messageType = WebSocketMessageType.Text;

            var bytes = new List<byte>();

            while (Socket.State == WebSocketState.Open)
            {
                var buffer = new byte[ReceiveBufferSize];

                var result = await Socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

                if (messageType != result.MessageType)
                {
                    messageType = result.MessageType;

                    bytes.Clear();
                }

                if (result.MessageType == WebSocketMessageType.Text)
                {
                    bytes.AddRange(buffer.Take(result.Count));

                    if (result.EndOfMessage)
                    {
                        OnTextHandler(this, Encoding.UTF8.GetString(bytes.ToArray()));

                        bytes.Clear();
                    }
                }
                else if (result.MessageType == WebSocketMessageType.Binary)
                {
                    bytes.AddRange(buffer.Take(result.Count));

                    if (result.EndOfMessage)
                    {
                        OnBinaryHandler(this, bytes.ToArray());

                        bytes.Clear();
                    }
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    OnCloseHandler(this, EventArgs.Empty);
                }
            }
        };
    }

    public WebSocketClient(string uri) : this(new Uri(uri))
    {

    }
}

コメント

このブログの人気の投稿

fontconfigの設定

VLCでBlu-rayを再生

UEFIのブートオーダーを一時的に変更する