开始在 .NET 中使用中继混合连接 Websocket

本教程简单介绍了 Azure 中继混合连接。 了解如何使用 Microsoft .NET 创建客户端应用程序,以便将消息发送到相应的侦听器应用程序。

要完成的任务

混合连接要求使用客户端组件和服务器组件。 在本教程中,请完成以下步骤,以便创建两个控制台应用程序:

  1. 使用 Azure 门户创建中继命名空间。
  2. 使用 Azure 门户在该命名空间中创建混合连接。
  3. 编写服务器(侦听器)控制台应用程序,用于接收消息。
  4. 编写客户端(发送方)控制台应用程序,用于发送消息。

先决条件

若要完成本教程,需要满足以下先决条件:

Note

若要完成本教程,你需要一个 Azure 帐户。 你可以注册试用版

1.使用 Azure 门户创建命名空间

如果已创建中继命名空间,请转到使用 Azure 门户创建混合连接

  1. 登录到 Azure 门户
  2. 在门户的左侧导航窗格中,依次单击“+”新建,搜索“relay"。
  3. 在“创建命名空间”下输入命名空间名称。 系统会立即检查该名称是否可用。
  4. 在“订阅”框中,选择要在其中创建命名空间的 Azure 订阅。
  5. “资源组”框中,选择要在其中放置命名空间的现有资源组,或者创建新资源组。
  6. 在“位置”中,选择应托管命名空间的国家或地区。

    创建命名空间

  7. 选择“创建” 。 系统会创建并启用命名空间。 几分钟后,系统为用户的帐户预配资源。

获取管理凭据

  1. 选择“所有资源”,然后选择新创建的命名空间名称。
  2. 在“中继命名空间”下,选择“共享访问策略”。
  3. 在“共享访问策略”下,选择“RootManageSharedAccessKey”。

    connection-info

  4. 在“策略: RootManageSharedAccessKey”下选择“连接字符串 - 主密钥”旁边的“复制”按钮。 这样会将连接字符串复制到剪贴板,供以后使用。 将此值粘贴到记事本或其他某个临时位置。

    连接字符串

  5. 重复上述步骤,将主密钥的值复制和粘贴到临时位置,供以后使用。

2.使用 Azure 门户创建混合连接

如果已创建混合连接,请转到创建服务器应用程序

确保已创建中继命名空间

  1. 登录到 Azure 门户
  2. 在左侧菜单中,选择“所有资源”。
  3. 选择要在其中创建混合连接的命名空间。 在本示例中,该命名空间为 mynewns
  4. 在“中继命名空间”下选择“混合连接”。

    创建混合连接

  5. 在命名空间概览窗口中,选择“+ 混合连接”

    选择混合连接

  6. 在“创建混合连接”下输入一个值,作为混合连接名称。 保留其他默认值。

    选择“新建”

  7. 选择“创建” 。

3.创建服务器应用程序(侦听程序)

在 Visual Studio 中编写可侦听和接收来自中继的消息的 C# 控制台应用程序。

创建控制台应用程序

在 Visual Studio 中创建新的控制台应用 (.NET Framework) 项目。

添加中继 NuGet 包

  1. 右键单击新创建的项目,然后选择“管理 NuGet 包”。
  2. 选择“包括预发行版”选项。
  3. 选择“浏览”,然后搜索 Microsoft.Azure.Relay。 在搜索结果中,选择“Microsoft Azure 中继”。
  4. 对于版本,选择 2.0.0-preview1-20180523
  5. 选择“安装”即可完成安装。 关闭对话框。

编写接收消息的代码

  1. 将 Program.cs 文件顶部的现有 using 语句替换为以下 using 语句:

    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Net;
    using Microsoft.Azure.Relay;
    
  2. 将常量添加到 Program 类,用于保存混合连接的连接详细信息。 将括号中的占位符替换为在创建混合连接时获得的值。 请务必使用完全限定的命名空间名称。

    private const string RelayNamespace = "{RelayNamespace}.servicebus.chinacloudapi.cn";
    private const string ConnectionName = "{HybridConnectionName}";
    private const string KeyName = "{SASKeyName}";
    private const string Key = "{SASKey}";
    
  3. ProcessMessagesOnConnection 方法添加到 Program 类:

    // The method initiates the connection.
    private static async void ProcessMessagesOnConnection(HybridConnectionStream relayConnection, CancellationTokenSource cts)
    {
        Console.WriteLine("New session");
    
        // The connection is a fully bidrectional stream. 
        // Put a stream reader and a stream writer over it.  
        // This allows you to read UTF-8 text that comes from 
        // the sender, and to write text replies back.
        var reader = new StreamReader(relayConnection);
        var writer = new StreamWriter(relayConnection) { AutoFlush = true };
        while (!cts.IsCancellationRequested)
        {
            try
            {
                // Read a line of input until a newline is encountered.
                var line = await reader.ReadLineAsync();
    
                if (string.IsNullOrEmpty(line))
                {
                    // If there's no input data, signal that 
                    // you will no longer send data on this connection,
                    // and then break out of the processing loop.
                    await relayConnection.ShutdownAsync(cts.Token);
                    break;
                }
    
                // Write the line on the console.
                Console.WriteLine(line);
    
                // Write the line back to the client, prepended with "Echo:"
                await writer.WriteLineAsync($"Echo: {line}");
            }
            catch (IOException)
            {
                // Catch an I/O exception. This likely occurred when
                // the client disconnected.
                Console.WriteLine("Client closed connection");
                break;
            }
        }
    
        Console.WriteLine("End session");
    
        // Close the connection.
        await relayConnection.CloseAsync(cts.Token);
    }
    
  4. RunAsync 方法添加到 Program 类:

    private static async Task RunAsync()
    {
        var cts = new CancellationTokenSource();
    
        var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key);
        var listener = new HybridConnectionListener(new Uri(string.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider);
    
        // Subscribe to the status events.
        listener.Connecting += (o, e) => { Console.WriteLine("Connecting"); };
        listener.Offline += (o, e) => { Console.WriteLine("Offline"); };
        listener.Online += (o, e) => { Console.WriteLine("Online"); };
    
        // Opening the listener establishes the control channel to
        // the Azure Relay service. The control channel is continuously 
        // maintained, and is reestablished when connectivity is disrupted.
        await listener.OpenAsync(cts.Token);
        Console.WriteLine("Server listening");
    
        // Provide callback for the cancellation token that will close the listener.
        cts.Token.Register(() => listener.CloseAsync(CancellationToken.None));
    
        // Start a new thread that will continuously read the console.
        new Task(() => Console.In.ReadLineAsync().ContinueWith((s) => { cts.Cancel(); })).Start();
    
        // Accept the next available, pending connection request. 
        // Shutting down the listener allows a clean exit. 
        // This method returns null.
        while (true)
        {
            var relayConnection = await listener.AcceptConnectionAsync();
            if (relayConnection == null)
            {
                break;
            }
    
            ProcessMessagesOnConnection(relayConnection, cts);
        }
    
        // Close the listener after you exit the processing loop.
        await listener.CloseAsync(cts.Token);
    }
    
  5. Program 类的 Main 方法中添加以下代码行:

    RunAsync().GetAwaiter().GetResult();
    

    完成的 Program.cs 文件应如下所示:

    namespace Server
    {
        using System;
        using System.IO;
        using System.Threading;
        using System.Threading.Tasks;
        using Microsoft.Azure.Relay;
    
        public class Program
        {
            private const string RelayNamespace = "{RelayNamespace}.servicebus.chinacloudapi.cn";
            private const string ConnectionName = "{HybridConnectionName}";
            private const string KeyName = "{SASKeyName}";
            private const string Key = "{SASKey}";
    
            public static void Main(string[] args)
            {
                RunAsync().GetAwaiter().GetResult();
            }
    
            private static async Task RunAsync()
            {
                var cts = new CancellationTokenSource();
    
                var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key);
                var listener = new HybridConnectionListener(new Uri(string.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider);
    
                // Subscribe to the status events
                listener.Connecting += (o, e) => { Console.WriteLine("Connecting"); };
                listener.Offline += (o, e) => { Console.WriteLine("Offline"); };
                listener.Online += (o, e) => { Console.WriteLine("Online"); };
    
                // Opening the listener establishes the control channel to
                // the Azure Relay service. The control channel is continuously 
                // maintained, and is reestablished when connectivity is disrupted.
                await listener.OpenAsync(cts.Token);
                Console.WriteLine("Server listening");
    
                // Provide callback for a cancellation token that will close the listener.
                cts.Token.Register(() => listener.CloseAsync(CancellationToken.None));
    
                // Start a new thread that will continuously read the console.
                new Task(() => Console.In.ReadLineAsync().ContinueWith((s) => { cts.Cancel(); })).Start();
    
                // Accept the next available, pending connection request. 
                // Shutting down the listener allows a clean exit. 
                // This method returns null.
                while (true)
                {
                    var relayConnection = await listener.AcceptConnectionAsync();
                    if (relayConnection == null)
                    {
                        break;
                    }
    
                    ProcessMessagesOnConnection(relayConnection, cts);
                }
    
                // Close the listener after you exit the processing loop.
                await listener.CloseAsync(cts.Token);
            }
    
            private static async void ProcessMessagesOnConnection(HybridConnectionStream relayConnection, CancellationTokenSource cts)
            {
                Console.WriteLine("New session");
    
                // The connection is a fully bidrectional stream. 
                // Put a stream reader and a stream writer over it.  
                // This allows you to read UTF-8 text that comes from 
                // the sender, and to write text replies back.
                var reader = new StreamReader(relayConnection);
                var writer = new StreamWriter(relayConnection) { AutoFlush = true };
                while (!cts.IsCancellationRequested)
                {
                    try
                    {
                        // Read a line of input until a newline is encountered.
                        var line = await reader.ReadLineAsync();
    
                        if (string.IsNullOrEmpty(line))
                        {
                            // If there's no input data, signal that 
                            // you will no longer send data on this connection.
                            // Then, break out of the processing loop.
                            await relayConnection.ShutdownAsync(cts.Token);
                            break;
                        }
    
                        // Write the line on the console.
                        Console.WriteLine(line);
    
                        // Write the line back to the client, prepended with "Echo:"
                        await writer.WriteLineAsync($"Echo: {line}");
                    }
                    catch (IOException)
                    {
                        // Catch an I/O exception. This likely occurred when
                        // the client disconnected.
                        Console.WriteLine("Client closed connection");
                        break;
                    }
                }
    
                Console.WriteLine("End session");
    
                // Close the connection.
                await relayConnection.CloseAsync(cts.Token);
            }
        }
    }
    

4.创建客户端应用程序(发送程序)

在 Visual Studio 中编写可将消息发送到中继的 C# 控制台应用程序。

创建控制台应用程序

在 Visual Studio 中创建新的控制台应用 (.NET Framework) 项目。

添加中继 NuGet 包

  1. 右键单击新创建的项目,然后选择“管理 NuGet 包”。
  2. 选择“浏览”,然后搜索 Microsoft.Azure.Relay。 在搜索结果中,选择“Azure 中继”。
  3. 选择“安装”即可完成安装。 关闭对话框。

编写发送消息的代码

  1. 将 Program.cs 文件顶部的现有 using 语句替换为以下 using 语句:

    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Azure.Relay;
    
  2. 将常量添加到 Program 类,用于保存混合连接的连接详细信息。 将括号中的占位符替换为在创建混合连接时获得的值。 请务必使用完全限定的命名空间名称:

    private const string RelayNamespace = "{RelayNamespace}.servicebus.chinacloudapi.cn";
    private const string ConnectionName = "{HybridConnectionName}";
    private const string KeyName = "{SASKeyName}";
    private const string Key = "{SASKey}";
    
  3. 将以下方法添加到 Program 类:

    private static async Task RunAsync()
    {
        Console.WriteLine("Enter lines of text to send to the server with ENTER");
    
        // Create a new hybrid connection client
        var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key);
        var client = new HybridConnectionClient(new Uri(String.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider);
    
        // Initiate the connection
        var relayConnection = await client.CreateConnectionAsync();
    
        // Run two concurrent loops on the connection. One 
        // reads input from the console and writes it to the connection 
        // with a stream writer. The other reads lines of input from the 
        // connection with a stream reader and writes them to the console. 
        // Entering a blank line shuts down the write task after 
        // sending it to the server. The server then cleanly shuts down
        // the connection, which terminates the read task.
    
        var reads = Task.Run(async () => {
            // Initialize the stream reader over the connection
            var reader = new StreamReader(relayConnection);
            var writer = Console.Out;
            do
            {
                // Read a full line of UTF-8 text up to newline
                string line = await reader.ReadLineAsync();
                // If the string is empty or null, you are done.
                if (String.IsNullOrEmpty(line))
                    break;
                // Write to the console
                await writer.WriteLineAsync(line);
            }
            while (true);
        });
    
        // Read from the console and write to the hybrid connection
        var writes = Task.Run(async () => {
            var reader = Console.In;
            var writer = new StreamWriter(relayConnection) { AutoFlush = true };
            do
            {
                // Read a line from the console.
                string line = await reader.ReadLineAsync();
                // Write the line out, also when it's empty.
                await writer.WriteLineAsync(line);
                // Quit when the line is empty,
                if (String.IsNullOrEmpty(line))
                    break;
            }
            while (true);
        });
    
        // Wait for both tasks to finish.
        await Task.WhenAll(reads, writes);
        await relayConnection.CloseAsync(CancellationToken.None);
    }
    
  4. Program 类的 Main 方法中添加以下代码行。

    RunAsync().GetAwaiter().GetResult();
    

    Program.cs 应如下所示:

    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Azure.Relay;
    
    namespace Client
    {
        class Program
        {
            private const string RelayNamespace = "{RelayNamespace}.servicebus.chinacloudapi.cn";
            private const string ConnectionName = "{HybridConnectionName}";
            private const string KeyName = "{SASKeyName}";
            private const string Key = "{SASKey}";
    
            static void Main(string[] args)
            {
                RunAsync().GetAwaiter().GetResult();
            }
    
            private static async Task RunAsync()
            {
                Console.WriteLine("Enter lines of text to send to the server with ENTER");
    
                // Create a new hybrid connection client
                var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key);
                var client = new HybridConnectionClient(new Uri(String.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider);
    
                // Initiate the connection
                var relayConnection = await client.CreateConnectionAsync();
    
                // Run two conucrrent loops on the connection. One 
                // reads input from the console and then writes it to the connection 
                // with a stream writer. The other reads lines of input from the 
                // connection with a stream reader and then writes them to the console. 
                // Entering a blank line shuts down the write task after 
                // sending it to the server. The server then cleanly shuts down
                // the connection, which terminates the read task.
    
                var reads = Task.Run(async () => {
                    // Initialize the stream reader over the connection.
                    var reader = new StreamReader(relayConnection);
                    var writer = Console.Out;
                    do
                    {
                        // Read a full line of UTF-8 text up to newline.
                        string line = await reader.ReadLineAsync();
                        // If the string is empty or null, you are done.
                        if (String.IsNullOrEmpty(line))
                            break;
                        // Write to the console.
                        await writer.WriteLineAsync(line);
                    }
                    while (true);
                });
    
                // Read from the console and write to the hybrid connection.
                var writes = Task.Run(async () => {
                    var reader = Console.In;
                    var writer = new StreamWriter(relayConnection) { AutoFlush = true };
                    do
                    {
                        // Read a line from the console.
                        string line = await reader.ReadLineAsync();
                        // Write the line out, also when it's empty.
                        await writer.WriteLineAsync(line);
                        // Quit when the line is empty.
                        if (String.IsNullOrEmpty(line))
                            break;
                    }
                    while (true);
                });
    
                // Wait for both tasks to finish.
                await Task.WhenAll(reads, writes);
                await relayConnection.CloseAsync(CancellationToken.None);
            }
        }
    }
    

5.运行应用程序

  1. 运行服务器应用程序。
  2. 运行客户端应用程序并输入一些文本。
  3. 确保服务器应用程序控制台显示了客户端应用程序中输入的文本。

running-applications

祝贺你,现已创建端到端混合连接应用程序!

后续步骤