Share via


教學課程:使用子通訊協定在 WebSocket 用戶端之間發佈和訂閱訊息

建置聊天應用程式教學課程中,您已了解如何使用 WebSocket API 搭配 Azure Web PubSub 傳送和接收資料。 您可以看到,用戶端與服務通訊時不需要通訊協定。 例如,您可以使用 WebSocket.send() 傳送任何類型的資料,然後伺服器會按照原狀接收資料。 WebSocket API 流程易於使用,但功能有限。 例如,您無法在將事件傳送至伺服器時指定事件名稱,或將訊息發佈至其他用戶端,而不是將它傳送至您的伺服器。 在本教學課程中,您會了解如何使用子通訊協定來擴充用戶端的功能。

在本教學課程中,您會了解如何:

  • 建立 Web PubSub 服務執行個體
  • 產生建立 WebSocket 連線的完整 URL
  • 使用子通訊協定在 WebSocket 用戶端之間發佈訊息

如果您沒有 Azure 訂閱,請在開始之前,先建立 Azure 免費帳戶

必要條件

  • 此設定需要 2.22.0 版或更新版本的 Azure CLI。 如果您是使用 Azure Cloud Shell,就已安裝最新版本。

建立 Azure Web PubSub 執行個體

建立資源群組

資源群組是在其中部署與管理 Azure 資源的邏輯容器。 使用 az group create 命令,在 eastus 位置中建立名為 myResourceGroup 的資源群組。

az group create --name myResourceGroup --location EastUS

建立 Web PubSub 執行個體

執行 az extension add 以安裝 webpubsub 延伸模組,或將該延伸模組升級至目前版本。

az extension add --upgrade --name webpubsub

使用 Azure CLI az webpubsub create 命令,在您已建立的資源群組中建立 Web PubSub。 下列命令會在 EastUS 的資源群組 myResourceGroup 下建立免費 Web PubSub 資源:

重要

每個 Web PubSub 資源都必須有唯一的名稱。 使用下列範例中的 Web PubSub 名稱取代 <your-unique-resource-name>。

az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1

此命令的輸出顯示新建資源的屬性。 請記下下列兩個屬性:

  • 資源名稱:您提供給上述 --name 參數的名稱。
  • hostName:在此範例中,主機名稱為 <your-unique-resource-name>.webpubsub.azure.com/

此時,您的 Azure 帳戶是唯一獲得授權在此新資源上執行任何作業的帳戶。

取得 ConnectionString 以供後續使用

重要

連接字串包含應用程式存取 Azure Web PubSub 服務所需的授權資訊。 連接字串內的存取金鑰類似於服務的根密碼。 在實際執行環境中,請務必小心保護您的存取金鑰。 使用 Azure Key Vault,以安全的方式管理及輪替金鑰。 避免將存取金鑰散發給其他使用者、寫入程式碼,或將其以純文字儲存在他人可以存取的位置。 如果您認為金鑰可能已遭盜用,請輪替金鑰。

使用 Azure CLI az webpubsub key 命令取得服務的 ConnectionString。 使用您的 Azure Web PubSub 執行個體名稱取代 <your-unique-resource-name> 預留位置。

az webpubsub key show --resource-group myResourceGroup --name <your-unique-resource-name> --query primaryConnectionString --output tsv

複製連接字串以供後續使用。

複製擷取的 ConnectionString,以稍後在本教學課程中用來做為 <connection_string> 的值。

設定專案

必要條件

使用子通訊協定

用戶端可以使用特定的子通訊協定來啟動 WebSocket 連線。 Azure Web PubSub 服務支援名為 json.webpubsub.azure.v1 的子通訊協定,可讓用戶端直接透過 Web PubSub 服務發佈/訂閱,而不是往返上游伺服器。 如需有關子通訊協定的詳細資訊,請參閱 Azure Web PubSub 支援的 JSON WebSocket 子通訊協定 (部分機器翻譯)。

如果您使用其他通訊協定名稱,服務會忽略它們並在連線事件處理常式中傳遞至伺服器,讓您可以建置自己的通訊協定。

現在,讓我們使用 json.webpubsub.azure.v1 子通訊協定建立 Web 應用程式。

  1. 安裝相依性

    mkdir logstream
    cd logstream
    dotnet new web
    dotnet add package Microsoft.Extensions.Azure
    dotnet add package Azure.Messaging.WebPubSub
    
  2. 建立伺服器端來裝載 /negotiate API 和網頁。

    使用下列程式碼更新 Program.cs

    • 使用 AddAzureClients 新增服務用戶端,並從設定中讀取連接字串。
    • app.Run(); 前面加上 app.UseStaticFiles(); 以支援靜態檔案。
    • 然後更新 app.MapGet,以產生具有 /negotiate 要求的用戶端存取權杖。
    using Azure.Messaging.WebPubSub;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddAzureClients(s =>
    {
        s.AddWebPubSubServiceClient(builder.Configuration["Azure:WebPubSub:ConnectionString"], "stream");
    });
    
    var app = builder.Build();
    app.UseStaticFiles();
    app.MapGet("/negotiate", async context =>
    {
        var service = context.RequestServices.GetRequiredService<WebPubSubServiceClient>();
        var response = new
        {
            url = service.GetClientAccessUri(roles: new string[] { "webpubsub.sendToGroup.stream", "webpubsub.joinLeaveGroup.stream" }).AbsoluteUri
        };
        await context.Response.WriteAsJsonAsync(response);
    });
    
    app.Run();
    
  3. 建立網頁

    使用下列內容建立 HTML 頁面,並將其儲存為 wwwroot/index.html

    <html>
      <body>
        <div id="output"></div>
        <script>
          (async function () {
            let res = await fetch('/negotiate')
            let data = await res.json();
            let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
            ws.onopen = () => {
              console.log('connected');
            };
    
            let output = document.querySelector('#output');
            ws.onmessage = event => {
              let d = document.createElement('p');
              d.innerText = event.data;
              output.appendChild(d);
            };
          })();
        </script>
      </body>
    </html>                                                                
    

    上述程式碼會連線至服務,並將收到的任何訊息列印至頁面。 主要的變更是我們在建立 WebSocket 連線時指定子通訊協定。

  4. 執行伺服器

    我們使用適用於 .NET Core 的 Secret Manager 工具來設定連接字串。 執行下列命令,使用在前一個步驟中擷取的命令取代 <connection_string>,並在瀏覽器中開啟 http://localhost:5000/index.html:

    dotnet user-secrets init
    dotnet user-secrets set Azure:WebPubSub:ConnectionString "<connection-string>"
    dotnet run
    

    如果您使用 Chrome,您可以按 F12 或以滑鼠右鍵按一下 -> [檢查] -> [開發人員工具],然後選取 [網路] 索引標籤。載入網頁,您可以看到 WebSocket 連線已建立。 選取以檢查 WebSocket 連線,您可以看到用戶端中已收到下列 connected 事件訊息。 您可以看到您可以取得針對此用戶端產生的 connectionId

    {"type":"system","event":"connected","userId":null,"connectionId":"<the_connection_id>"}
    

您可以看到,透過在子通訊協定的協助,您可以在連線為 connected 時取得連線的一些中繼資料。

用戶端現在會收到 JSON 訊息,而不是純文字。 JSON 訊息包含更多詳細資訊,例如訊息的類型和來源。 因此,您可以使用此資訊對訊息進行更多處理 (例如,如果訊息來自不同的來源,則以不同的樣式顯示訊息),您可以在後面的區段中找到。

從用戶端發佈訊息

建置聊天應用程式教學課程中,當用戶端透過 WebSocket 連線傳送訊息至 Web PubSub 服務時,服務會在伺服器端觸發使用者事件。 使用子通訊協定時,用戶端會藉由傳送 JSON 訊息擁有更多功能。 例如,您可以透過 Web PubSub 服務直接從用戶端將訊息發佈至其他用戶端。

如果您想要將大量資料即時串流至其他用戶端,這會很有用。 讓我們使用此功能建置記錄串流應用程式,將主控台記錄即時串流至瀏覽器。

  1. 建立串流程式

    建立 stream 程式:

    mkdir stream
    cd stream
    dotnet new console
    

    使用下列內容更新 Program.cs

    using System;
    using System.Net.Http;
    using System.Net.WebSockets;
    using System.Text;
    using System.Text.Json;
    using System.Threading.Tasks;
    
    namespace stream
    {
        class Program
        {
            private static readonly HttpClient http = new HttpClient();
            static async Task Main(string[] args)
            {
                // Get client url from remote
                var stream = await http.GetStreamAsync("http://localhost:5000/negotiate");
                var url = (await JsonSerializer.DeserializeAsync<ClientToken>(stream)).url;
                var client = new ClientWebSocket();
                client.Options.AddSubProtocol("json.webpubsub.azure.v1");
    
                await client.ConnectAsync(new Uri(url), default);
    
                Console.WriteLine("Connected.");
                var streaming = Console.ReadLine();
                while (streaming != null)
                {
                    if (!string.IsNullOrEmpty(streaming))
                    {
                        var message = JsonSerializer.Serialize(new
                        {
                            type = "sendToGroup",
                            group = "stream",
                            data = streaming + Environment.NewLine,
                        });
                        Console.WriteLine("Sending " + message);
                        await client.SendAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, default);
                    }
    
                    streaming = Console.ReadLine();
                }
    
                await client.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default);
            }
    
            private sealed class ClientToken
            {
                public string url { get; set; }
            }
        }
    }
    
    

    您可以看到,這裡有個新的概念「群組」。 群組是中樞中的邏輯概念,您可以在其中將訊息發佈至連線群組。 在中樞中,您可以有多個群組,而一個用戶端可以同時訂閱多個群組。 使用子通訊協定時,您只能發佈至群組,而不是廣播至整個中樞。 如需有關字詞的詳細資訊,請參閱基本概念

  2. 因為我們在這裡使用群組,所以在 ws.onopen 回呼內建立 WebSocket 連線時,我們也需要更新網頁 index.html 以加入群組。

    let ackId = 0;
    ws.onopen = () => {
      console.log('connected');
      ws.send(JSON.stringify({
        type: 'joinGroup',
        group: 'stream',
        ackId: ++ackId
      }));
    };
    

    您可以看到,用戶端透過傳送 joinGroup 類型的訊息來加入群組。

  3. 此外,請稍微更新 ws.onmessage 回呼邏輯,以剖析 JSON 回應,並僅列印來自 stream 群組的訊息,使其做為即時串流印表機。

    ws.onmessage = event => {
      let message = JSON.parse(event.data);
      if (message.type === 'message' && message.group === 'stream') {
        let d = document.createElement('span');
        d.innerText = message.data;
        output.appendChild(d);
        window.scrollTo(0, document.body.scrollHeight);
      }
    };
    
  4. 基於安全性考慮,用戶端預設無法自行發佈或訂閱群組。 因此,您會注意到,我們在產生權杖時為用戶端設定 roles

    GenerateClientAccessUriStartup.cs 中時,設定 roles,如下所示:

    service.GenerateClientAccessUri(roles: new string[] { "webpubsub.sendToGroup.stream", "webpubsub.joinLeaveGroup.stream" })
    
  5. 最後,也套用一些樣式到 index.html,使其妥善地顯示。

    <html>
    
      <head>
        <style>
          #output {
            white-space: pre;
            font-family: monospace;
          }
        </style>
      </head>
    

現在,執行下列程式碼並輸入任何文字,這些文字會即時顯示在瀏覽器中:

ls -R | dotnet run

# Or call `dir /s /b | dotnet run` when you are using CMD under Windows

或者,您可以放慢速度,以便看到資料即時串流至瀏覽器:

for i in $(ls -R); do echo $i; sleep 0.1; done | dotnet run

您可以在這裡找到此教學課程的完整程式碼範例。

下一步

此教學課程提供如何連線至 Web PubSub 服務,以及如何將訊息發佈至已連線用戶端的基本概念。

請查看其他教學課程,以進一步了解如何使用此服務。