選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
4 分鐘閱讀時間 (889 個字)

[.Net Core] WebSocket

unsplash-coding104

上一篇介紹了前端 JavaScript 的 WebSocket,
今天來介紹 .Net Core Web API 的 WebSocket。

接受連線

首先要到 Startup.cs > Configure 去設定一個 WebSocket 的路徑,
然後設定使用 WebSocket 及接受連線的方法。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	// ...

	// 設定 WebSocket 的路徑
	app.Map("/websocket", con =>
	{
		// 使用 WebSocket
	    con.UseWebSockets();
			
		// 接受連線的方法
	    con.Use(async (context, _) =>
		{
		    var socket = await context.WebSockets.AcceptWebSocketAsync();
		    while (socket.State == WebSocketState.Open)
		    {
				// 因為我只需要後端傳到前端
				// 所以就不處理前端傳來的訊息了 XD
		        await socket.ReceiveAsync(
					new ArraySegment<byte>(new byte[1]), CancellationToken.None);
		    }
		});
	});
	
	// ...
} 

傳送訊息

傳送訊息至前端就需要用到前面 AcceptWebSocketAsync 回傳的實例。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	// ...

	app.Map("/websocket", con =>
	{
	    con.UseWebSockets();
	    con.Use(async (context, _) =>
		{
		    var socket = await context.WebSockets.AcceptWebSocketAsync();

			// 傳送連線成功訊息
			var message = "連線成功囉~";
			
			// 傳送的訊息參數類型是 bytes 要轉一下
            var messageBuffer = Encoding.UTF8.GetBytes(message);
            await socket.SendAsync(
				buffer, WebSocketMessageType.Text, true, CancellationToken.None);

		    while (socket.State == WebSocketState.Open)
		    {
		        await socket.ReceiveAsync(
				    new ArraySegment<byte>(new byte[1]), CancellationToken.None);
		    }
		});
	});

	// ...
} 

WebsocketHandler

理論上,
我們不會只在接收連線時傳送訊息,
也不會只傳訊息給一個連線。

相反的,
我們應該在接受連線時,將其放入一個集合。

並在需要發送訊息時,
使用這個集合來以廣播的方式發送訊息給所有連線。

所以這裡我會傾向用一個 Service 來處理連線。

public class WebsocketHandler
{
	// 存放連線的集合
    private List<WebSocket> _sockets = new List<WebSocket>();

	// 接受連線的方式
    public async Task Connect(HttpContext context, Func<Task> _)
    {
        var socket = await context.WebSockets.AcceptWebSocketAsync();
        _sockets.Add(socket); // 將連線加入集合
        while (socket.State == WebSocketState.Open)
        {
            await socket.ReceiveAsync(
				new ArraySegment<byte>(new byte[1]), CancellationToken.None);
        }
    }

	// 發送訊息
    public async Task SendAsync(object data, CancellationToken ct = default)
    {
		// 過濾沒有開啟的連線
        _sockets =
			_sockets.Where(socket => socket.State == WebSocketState.Open).ToList();

		// 逐一發送訊息
        foreach (WebSocket socket in _sockets)
        {
            await SendAsync(socket, data, ct);
        }
    }

    private async Task SendAsync(WebSocket socket, object data, CancellationToken ct)
    {
		// 把資料轉成 JSON string 再轉成 bytes
        var dataStr = JObject.FromObject(data).ToString();
        var buffer = Encoding.UTF8.GetBytes(dataStr);

		// 發送訊息
        await socket.SendAsync(buffer, WebSocketMessageType.Text, true, ct);
    }
} 


然後調整 Startup.cs 的設定

public void ConfigureServices(IServiceCollection services)
{
	// ...

    // 記得要用 Singleton 連線集合才不會出錯
    services.AddSingleton<WebsocketHandler, WebsocketHandler>();

    // ...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	// ...

    app.Map("/websocket", con =>
    {
        con.UseWebSockets();
        con.Use(app.ApplicationServices.GetService<WebsocketHandler>().Connect);
    });

	// ...
} 

統一訊息格式

在發送訊息的部分,我會希望將訊息的格式統一成:

{
	"type": "訊息類型",
	"data": "訊息資料"
} 

方便前端區分資料類型。

所以這邊我會建立一個 Abstract class ,
讓所有需要發送的通知的部分來繼承。 

public abstract class AbstractWebsocket
{
    private readonly WebsocketHandler _handler;
    private readonly WebsocketType _type;
		
	// 除了注入剛剛的 handler 來處理發送訊息的部分
	// 還要傳入一個參數來定義資料的類型
    protected AbstractWebsocket(WebsocketHandler handler, WebsocketType type)
    {
        _handler = handler;
        _type = type;
    }

		
    protected async Task SendAsync(object data)
    {
		// 傳送的訊息為類型加上資料
        await _handler.SendAsync(new { type = _type, data });
    }
}

// 繼承範例
public class ExampleWebsocket : AbstractWebsocket, IExampleWebsocket
{
    public ExampleWebsocket(WebsocketHandler handler) // 注入 handler
		: base(handler, WebsocketType.Example) // 設定類型
    {
        
    }

		
    public async Task SendAsync(ExampleModel data) // 這裡可以使用想要的類別
    {
        await base.SendAsync(data); // 使用抽象類別的發送方法
    }
} 


在 Startup.cs > ConfigureServices 加入 IExampleWebsocket :

public void ConfigureServices(IServiceCollection services)
{
	// ...

    // 這邊就可以用 Scoped 或是 Transient
		services.AddTransient<IExampleWebsocket, ExampleWebsocket>();

    // ...
} 


要使用的時候就直接注入 IExampleWebsocket 來使用:

public class ExampleService {
	private readonly IExampleWebsocket _websocket;

	public ExampleService(IExampleWebsocket websocket) {
		_websocket = websocket;
	}
	
	public async Task SendNewExample() {
		await _websocket.SendAsync(new ExampleModel());
	}
} 


以上就是我在 .Net Core Web Api 中使用 Websocket 的方式。
如果有更好的方式,一樣歡迎留言推薦給我噢!

使用 acme.sh 幫個人網站取得免費的 SSL 憑證
[JavaScript] WebSocket (ft. React Context)

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2024/05/06, 週一

Captcha 圖像