理解ASP.NET Core 中的WebSocket

在本文中,我们将详细介绍RFC 6455 WebSocket规范,并配置一个通用的.NET 5应用程序通过WebSocket连接与SignalR通信。

我们将深入底层的概念,以理解底层发生了什么。

关于WebSocket

引入WebSocket是为了实现客户端和服务器之间的双向通信。HTTP 1.0的一个痛点是每次向服务器发送请求时创建和关闭连接。但是,在HTTP 1.1中,通过使用保持连接机制引入了持久连接(RFC 2616)。这样,连接可以被多个请求重用——这将减少延迟,因为服务器知道客户端,它们不需要在每个请求的握手过程中启动。

WebSocket建立在HTTP 1.1规范之上,因为它允许持久连接。因此,当你第一次创建WebSocket连接时,它本质上是一个HTTP 1.1请求(稍后详细介绍)。这使得客户端和服务器之间能够进行实时通信。简单地说,下图描述了在发起(握手)、数据传输和关闭WS连接期间发生的事情。我们将在后面更深入地研究这些概念。

协议中包含了两部分:握手和数据传输。

握手

让我们先从握手开始。

简单地说,WebSocket连接基于单个端口上的HTTP(和作为传输的TCP)。下面是这些步骤的总结。

  1. 服务器必须监听传入的TCP套接字连接。这可以是你分配的任何端口—通常是80443

  2. 客户端通过一个HTTP GET请求发起开始握手(否则服务器将不知道与谁对话)——这是“WebSockets”中的“Web”部分。在消息报头中,客户端将请求服务器将连接升级到WebSocket

  3. 服务器发送一个握手响应,告诉客户端它将把协议从HTTP更改为WebSocket

  4. 客户端和服务器双方协商连接细节。任何一方都可以退出。

下面是一个典型的打开(客户端)握手请求的样子。​​​​​​

GET /ws-endpoint HTTP/1.1
Host: example.com:80
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: L4kHN+1Bx7zKbxsDbqgzHw==
Sec-WebSocket-Version: 13

注意客户端是如何在请求中发送Connection: UpgradeUpgrade: websocket报头的。

并且,服务器握手响应。​​​​​​

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: CTPN8jCb3BUjBjBtdjwSQCytuBo=

数据传输

我们需要理解的下一个关键概念是数据传输。任何一方都可以在任何给定的时间发送消息——因为它是一个全双工通信协议。

消息由一个或多个帧组成。帧的类型可以是文本(UTF-8)、二进制和控制帧(例如0x8 (Close)、0x9 (Ping)和0xA (Pong))。

安装

让我们付诸行动,看看它是如何工作的。

首先创建一个 ASP.NET 5 WebAPI 项目。

dotnet new webapi -n WebSocketsTutorial
dotnet new sln
dotnet sln add WebSocketsTutorial

现在添加SignalR到项目中。

dotnet add WebSocketsTutorial/ package Microsoft.AspNet.SignalR

示例代码

我们首先将WebSockets中间件添加到我们的WebAPI应用程序中。打开Startup.cs,向Configure方法添加下面的代码。

在本教程中,我喜欢保持简单。因此,我不打算讨论SignalR。它将完全基于WebSocket通信。你也可以用原始的WebSockets实现同样的功能,如果你想让事情变得更简单,你不需要使用SignalR

app.UseWebSockets();

接下来,我们将删除默认的WeatherForecastController,并添加一个名为WebSocketsController的新控制器。注意,我们将只是使用一个控制器action,而不是拦截请求管道。

这个控制器的完整代码如下所示。​​​​​​​

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace WebSocketsTutorial.Controllers{
    [ApiController]
    [Route("[controller]")]
    public class WebSocketsController : ControllerBase
    {
        private readonly ILogger<WebSocketsController> _logger;

        public WebSocketsController(ILogger<WebSocketsController> logger)
        {
            _logger = logger;
        }

        [HttpGet("/ws")]
        public async Task Get()
        {
          if (HttpContext.WebSockets.IsWebSocketRequest)
          {
              using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
              _logger.Log(LogLevel.Information, "WebSocket connection established");
              await Echo(webSocket);
          }
          else
          {
              HttpContext.Response.StatusCode = 400;
          }
        }
        
        private async Task Echo(WebSocket webSocket)
        {
            var buffer = new byte[1024 * 4];
            var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            _logger.Log(LogLevel.Information, "Message received from Client");

            while (!result.CloseStatus.HasValue)
            {
                var serverMsg = Encoding.UTF8.GetBytes($"Server: Hello. You said: {Encoding.UTF8.GetString(buffer)}");
                await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), result.MessageType, result.EndOfMessage, CancellationToken.None);
                _logger.Log(LogLevel.Information, "Message sent to Client");

                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                _logger.Log(LogLevel.Information, "Message received from Client");
                
            }
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
            _logger.Log(LogLevel.Information, "WebSocket connection closed");
        }
    }
}

这是我们所做的。

1、添加一个名为ws/的新路由。

2、检查当前请求是否通过WebSockets,否则抛出400

3、等待,直到客户端发起请求。

4、进入一个循环,直到客户端关闭连接。

5、在循环中,我们将发送“Server: Hello. You said: <client’s message>”信息,并把它发回给客户端。

6、等待,直到客户端发送另一个请求。

注意,在初始握手之后,服务器不需要等待客户端发送请求来将消息推送到客户端。让我们运行应用程序,看看它是否工作。

dotnet run --project WebSocketsTutorial

运行应用程序后,请访问https://localhost:5001/swagger/index.html,应该看到Swagger UI
在这里插入图片描述
现在我们将看到如何让客户端和服务器彼此通信。在这个演示中,我将使用Chrome的DevTools(打开新标签→检查或按F12→控制台标签)。但是,你可以选择任何客户端。

首先,我们将创建一个到服务器终结点的WebSocket连接。

let webSocket = new WebSocket('wss://localhost:5001/ws');

它所做的是,在客户端和服务器之间发起一个连接。wss://WebSockets安全协议,因为我们的WebAPI应用程序是通过TLS服务的。

然后,可以通过调用webSocket.send()方法发送消息。你的控制台应该类似于下面的控制台。
在这里插入图片描述

让我们仔细看看WebSocket连接

如果转到Network选项卡,则通过WS选项卡过滤掉请求,并单击最后一个称为WS的请求。

单击Messages选项卡并检查来回传递的消息。在此期间,如果调用以下命令,将能够看到“This was sent from the Client!”。试试吧!

webSocket.send("Client: Hello");

在这里插入图片描述

如你所见,服务器确实需要等待客户端发送响应(即在初始握手之后),并且客户端可以发送消息而不会被阻塞。这是全双工通信。我们已经讨论了WebSocket通信的数据传输方面。作为练习,你可以运行一个循环将消息推送到客户机,以查看它的运行情况。

除此之外,服务器和客户端还可以通过ping-pong来查看客户端是否还活着。这是WebSockets中的一个实际特性!如果你真的想看看这些数据包,你可以使用像WireShark这样的工具来了解。

它是如何握手的?好吧,如果你跳转到Headers选项卡,你将能够看到我们在这篇文章的第一部分谈到的请求-响应标题。
在这里插入图片描述

也可以尝试一下webSocket.close(),这样我们就可以完全覆盖open-data-close循环了。

结论

如果你对WebSocketRFC感兴趣,请访问RFC 6455并阅读。这篇文章只是触及了WebSocket的表面,还有很多其他的东西我们可以讨论,比如安全,负载平衡,代理等等。


.net core 中使用 websocket

感觉放了好长时间的假期。贴个 websocket 的简单示例。

整体文件结构

- 项目(WebSockets.Test)
|-- Extensions
|      |-- SocketsExtension.cs
|-- Handlers
|      |-- WebSocketMessageHandler.cs
|-- SocketsManager
|      |-- SocketsHandler.cs
|      |-- SocketsManager.cs
|      |-- SocketsMiddleware.cs
|-- Program.cs
|-- Startup.cs

大体需要的文件是这些,这是最基本的示例,可以按需自行修改。

1、创建保存 WebSocket 的类

该类用于保存所有 WebSocket

// 文件:SocketsManager/SocketsManager.cs

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

namespace WebSockets.Test.SocketsManager
{
    public class SocketsManager
    {
        private readonly ConcurrentDictionary<string, WebSocket> _connections =
            new ConcurrentDictionary<string, WebSocket>();

        /// <summary>
        /// 获取所有 sockets 的字典集合
        /// </summary>
        /// <returns></returns>
        public ConcurrentDictionary<string, WebSocket> GetAllConnections()
        {
            return _connections;
        }

        /// <summary>
        ///     获取指定 id 的 socket
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public WebSocket GetSocketById(string id)
        {
            return _connections.FirstOrDefault(x => x.Key == id).Value;
        }

        /// <summary>
        /// 根据 socket 获取其 id
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public string GetId(WebSocket socket)
        {
            return _connections.FirstOrDefault(x => x.Value == socket).Key;
        }

        /// <summary>
        /// 删除指定 id 的 socket,并关闭该链接
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task RemoveSocketAsync(string id)
        {
            _connections.TryRemove(id, out var socket);
            await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "socket connection closed",
                CancellationToken.None);
        }

        /// <summary>
        /// 添加一个 socket
        /// </summary>
        /// <param name="socket"></param>
        public void AddSocket(WebSocket socket)
        {
            _connections.TryAdd(CreateId(), socket);
        }

        /// <summary>
        /// 创建 id
        /// </summary>
        /// <returns></returns>
        private string CreateId()
        {
            return Guid.NewGuid().ToString("N");
        }
    }
}

2、创建管理和操作 WebSocket 的基类

该类旨在处理 socket 的连接和断连,以及接收和发送消息,属于基类。

// 文件: SocketsManager/SocketsHandle.cs

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

namespace WebSockets.Test.SocketsManager
{
    public abstract class SocketsHandler
    {
        protected SocketsHandler(SocketsManager sockets)
        {
            Sockets = sockets;
        }

        public SocketsManager Sockets { get; set; }

        /// <summary>
        /// 连接一个 socket
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public virtual async Task OnConnected(WebSocket socket)
        {
            await Task.Run(() => { Sockets.AddSocket(socket); });
        }

        /// <summary>
        /// 断开指定 socket
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public virtual async Task OnDisconnected(WebSocket socket)
        {
            await Sockets.RemoveSocketAsync(Sockets.GetId(socket));
        }

        /// <summary>
        /// 发送消息给指定 socket
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendMessage(WebSocket socket, string message)
        {
            if (socket.State != WebSocketState.Open) return;

            await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(message)),
                WebSocketMessageType.Text, true, CancellationToken.None);
        }

        /// <summary>
        /// 发送消息给指定 id 的 socket
        /// </summary>
        /// <param name="id"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendMessage(string id, string message)
        {
            await SendMessage(Sockets.GetSocketById(id), message);
        }

        /// <summary>
        /// 给所有 sockets 发送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendMessageToAll(string message)
        {
            foreach (var connection in Sockets.GetAllConnections()) await SendMessage(connection.Value, message);
        }

        /// <summary>
        /// 接收到消息
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="result"></param>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public abstract Task Receive(WebSocket socket, WebSocketReceiveResult result,
            byte[] buffer);
    }
}

3、创建 WebSocket 的中间件

// 文件:SocketsManager/SocketsMiddleware.cs

using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace WebSockets.Test.SocketsManager
{
    public class SocketsMiddleware
    {
        private readonly RequestDelegate _next;

        public SocketsMiddleware(RequestDelegate next, SocketsHandler handler)
        {
            _next = next;
            Handler = handler;
        }

        private SocketsHandler Handler { get; }

        public async Task InvokeAsync(HttpContext context)
        {
            if (context.WebSockets.IsWebSocketRequest)
            {
                // 转换当前连接为一个 ws 连接
                var socket = await context.WebSockets.AcceptWebSocketAsync();
                await Handler.OnConnected(socket);

                // 接收消息的 buffer
                var buffer = new byte[1024 * 4];
                // 判断连接类型,并执行相应操作
                while (socket.State == WebSocketState.Open)
                {
                    // 这句执行之后,buffer 就是接收到的消息体,可以根据需要进行转换。
                    var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    switch (result.MessageType)
                    {
                        case WebSocketMessageType.Text:
                            await Handler.Receive(socket, result, buffer);
                            break;
                        case WebSocketMessageType.Close:
                            await Handler.OnDisconnected(socket);
                            break;
                        case WebSocketMessageType.Binary:
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
            }
            else
            {
                await _next(context);
            }
        }
    }
}

4、创建 WebSocket 管理子类

可以创建多个,用于个性化设置,主要是上面设置了接收的抽象方法,所以必须要重写 Receive 方法。如果不需要的话,其实把基类的抽象去掉,直接在基类中写也可以。

为了展示效果,添加了加入和离开时的消息提示。同时接收到的消息直接转发给所有人。

// 文件: Handlers/WebSocketMessageHandler.cs

using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using WebSockets.Test.SocketsManager;

namespace WebSockets.Test.Handlers
{
    public class WebSocketMessageHandler : SocketsHandler
    {
        public WebSocketMessageHandler(SocketsManager.SocketsManager sockets) : base(sockets)
        {
        }

        public override async Task OnConnected(WebSocket socket)
        {
            await base.OnConnected(socket);
            var socketId = Sockets.GetId(socket);
            await SendMessageToAll($"{socketId}已加入");
        }

        public override async Task OnDisconnected(WebSocket socket)
        {
            await base.OnDisconnected(socket);
            var socketId = Sockets.GetId(socket);
            await SendMessageToAll($"{socketId}离开了");
        }

        public override async Task Receive(WebSocket socket, WebSocketReceiveResult result, byte[] buffer)
        {
            var socketId = Sockets.GetId(socket);
            var message = $"{socketId} 发送了消息:{Encoding.UTF8.GetString(buffer, 0, result.Count)}";
            await SendMessageToAll(message);
        }
    }
}

5、创建注入扩展

直接在 Startup.cs 中写也无不可,但这是好习惯,将每个注入内容单独写到文件。

// 文件:Extensions/SocketsExtension.cs

using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using WebSockets.Test.SocketsManager;

namespace WebSockets.Test.Extensions
{
    public static class SocketsExtension
    {
        public static IServiceCollection AddWebSocketManager(this IServiceCollection services)
        {
            services.AddTransient<SocketsManager.SocketsManager>();
            var exportedTypes = Assembly.GetEntryAssembly()?.ExportedTypes;
            if (exportedTypes == null) return services;

            foreach (var type in exportedTypes)
                if (type.GetTypeInfo().BaseType == typeof(SocketsHandler))
                    services.AddSingleton(type);

            return services;
        }

        public static IApplicationBuilder MapSockets(this IApplicationBuilder app, PathString path,
            SocketsHandler socket)
        {
            return app.Map(path, x => x.UseMiddleware<SocketsMiddleware>(socket));
        }
    }
}

6、配置 Startup.cs

将上面的内容注入到启动项中即可。

ConfigureServices 中添加:

services.AddWebSocketManager();

然后在 Configure 中添加:

app.UseWebSockets();
app.MapSockets("/ws", serviceProvider.GetService<WebSocketMessageHandler>());

即可。

如果提示 serviceProvider 找不到,在 Configure 的参数中添加:

IServiceProvider serviceProvider

即可。

完整的内容如下:

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddWebSocketManager();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseWebSockets();
    // 配置路径
    app.MapSockets("/ws", serviceProvider.GetService<WebSocketMessageHandler>());

    app.UseStaticFiles();
}

7、测试

以上内容已经完成,现在可以跑起来。为了测试,编写一个最简单的页面。

<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket web client</title>
</head>

<body>
    <h1>WebSocket Web Client</h1>
    <br />

    <input type="text" placeholder="enter your message" id="message">
    <button id="sendBtn">Send</button>
    <ul id="messageList"></ul>

    <script>
        // 根据实际地址和端口进行修改,其他内容无需修改
        const uri = "ws://localhost:5000/ws";
        socket = new WebSocket(uri);
        socket.onopen = function (e) {
            console.log("websocket estabished!");
        }

        socket.onclose = function (e) {
            console.log('websocket closed!');
        }

        socket.onmessage = function (e) {
            appendItem(list, e.data);
            console.log(e.data);
        }

        const list = document.getElementById("messageList");
        const btn = document.getElementById("sendBtn");
        btn.addEventListener("click", function () {
            console.log("sending message~~~");

            var messgae = document.getElementById("message");
            socket.send(message.value)
        })

        function appendItem(list, message) {
            const li = document.createElement("li");
            li.appendChild(document.createTextNode(message));
            list.appendChild(li);
        }
    </script>
</body>

</html>

测试效果:
在这里插入图片描述
有了上面的示例,一个最简单的聊天室模型已经可以实现了。


.NET Core 学习笔记之 WebSocketsSample

1. 服务端

代码如下:

Program:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace WebSocketsServer
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}

Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebSocketsServer
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // configure keep alive interval, receive buffer size
            app.UseWebSockets();

            app.Map("/samplesockets", app2 =>
            {
                // middleware to handle websocket request
                app2.Use(async (context, next) =>
                {
                    if (context.WebSockets.IsWebSocketRequest)
                    {
                        var webSocket = await context.WebSockets.AcceptWebSocketAsync();
                        await SendMessagesAsync(context, webSocket, loggerFactory.CreateLogger("SendMessages"));
                    }
                    else
                    {
                        await next();
                    }
                }); 
            });

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Web Sockets sample");
            });
        }

        private async Task SendMessagesAsync(HttpContext context, WebSocket webSocket, ILogger logger)
        {
            var buffer = new byte[4096];
            WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            while (!result.CloseStatus.HasValue)
            {
                if (result.MessageType == WebSocketMessageType.Text)
                {
                    string content = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    if (content.StartsWith("REQUESTMESSAGES:"))
                    {
                        string message = content.Substring("REQUESTMESSAGES:".Length);
                        for (int i = 0; i < 10; i++)
                        {
                            string messageToSend = $"{message} - {i}";
                            if (i == 9)
                            {
                                messageToSend += ";EOS"; // send end of sequence to not let the client wait for another message
                            }
                            byte[] sendBuffer = Encoding.UTF8.GetBytes(messageToSend);
                            await webSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, endOfMessage: true, CancellationToken.None);
                            logger.LogDebug("sent message {0}", messageToSend);
                            await Task.Delay(1000);
                        }
                    }

                    if (content.Equals("SERVERCLOSE"))
                    {
                        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Bye for now", CancellationToken.None);
                        logger.LogDebug("client sent close request, socket closing");
                        return;
                    }
                    else if (content.Equals("SERVERABORT"))
                    {
                        context.Abort();
                    }
                }

                result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
            }
        }
    }
}

launchSettings.json

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:58167/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebSocketsServer": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:58168/"
    }
  }
}

2. 客户端

Program.cs

代码如下:

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

namespace WebSocketClient
{
    class Program
    {
        static async Task Main()
        {
            Console.WriteLine("Client - wait for server");
            Console.ReadLine();
            await InitiateWebSocketCommunication("ws://localhost:58167/samplesockets");
            //"ws://localhost:6295/samplesockets"
            //http://localhost:58167/
            Console.WriteLine("Program end");
            Console.ReadLine();          
        }

        static async Task InitiateWebSocketCommunication(string address)
        {
            try
            {
                var webSocket = new ClientWebSocket();
                await webSocket.ConnectAsync(new Uri(address), CancellationToken.None);

                await SendAndReceiveAsync(webSocket, "A");
                await SendAndReceiveAsync(webSocket, "B");
                await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("SERVERCLOSE")), 
                    WebSocketMessageType.Text, 
                    endOfMessage: true, 
                    CancellationToken.None);
                var buffer = new byte[4096];
                var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), 
                    CancellationToken.None);
                
                Console.WriteLine($"received for close: " +
                    $"{result.CloseStatus} " +
                    $"{result.CloseStatusDescription} " +
                    $"{Encoding.UTF8.GetString(buffer, 0, result.Count)}");
                await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, 
                    "Bye", 
                    CancellationToken.None);

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        static async Task SendAndReceiveAsync(WebSocket webSocket, string term)
        {
            byte[] data = Encoding.UTF8.GetBytes($"REQUESTMESSAGES:{term}");
            var buffer = new byte[4096];

            await webSocket.SendAsync(new ArraySegment<byte>(data), 
                WebSocketMessageType.Text, 
                endOfMessage: true, 
                CancellationToken.None);
            WebSocketReceiveResult result;
            bool sequenceEnd = false;
            do
            {
                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), 
                    CancellationToken.None);
                string dataReceived = Encoding.UTF8.GetString(buffer, 0, result.Count);
                Console.WriteLine($"received {dataReceived}");
                if (dataReceived.Contains("EOS"))
                {
                    sequenceEnd = true;
                }

            } while (!(result?.CloseStatus.HasValue ?? false) && !sequenceEnd);
        }
    }
}

运行截图

在这里插入图片描述

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我来给你提供一个详细的ASP.NET Core使用WebSocket的案例。 **1. 创建 ASP.NET Core 项目** 首先需要创建一个ASP.NET Core 项目,可以选择空项目或Web应用程序模板。 **2. 添加 WebSocket 支持** 在ASP.NET Core,使用WebSocket需要添加Microsoft.AspNetCore.WebSockets包。可以通过NuGet包管理器或手动添加对项目的引用来添加此包。 **3. 添加 WebSocket 控制器** 在 ASP.NET Core WebSocket 控制器是处理 WebSocket 请求的类。可以创建一个控制器类并继承自 Microsoft.AspNetCore.Mvc.Controller 类,然后添加 WebSocket 相关的方法。 ```csharp using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using System.Net.WebSockets; using System.Threading.Tasks; namespace WebSocketExample.Controllers { [Route("api/[controller]")] public class WebSocketController : Controller { [HttpGet("{id}")] public async Task Get(string id) { if (HttpContext.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); await Echo(webSocket); } else { HttpContext.Response.StatusCode = 400; } } private async Task Echo(WebSocket webSocket) { var buffer = new byte[1024 * 4]; WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), System.Threading.CancellationToken.None); while (!result.CloseStatus.HasValue) { await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, System.Threading.CancellationToken.None); result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), System.Threading.CancellationToken.None); } await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, System.Threading.CancellationToken.None); } } } ``` 在上面的代码,我们创建了一个名为 WebSocketController 的控制器,并添加了一个 Get 方法来处理 WebSocket 请求。 在 Get 方法,我们首先检查当前请求是否是 WebSocket 请求,如果是,则接受 WebSocket 连接,并调用 Echo 方法来处理 WebSocket 消息。如果不是,则返回状态码 400。 在 Echo 方法,我们接收 WebSocket 传来的消息,然后立即将其回发给客户端。该方法会一直循环,直到收到一个 CloseStatus 告知连接需要关闭,然后关闭连接。 **4. 配置应用程序启动** 现在我们需要配置应用程序以启用 WebSocket 支持。在 Startup.cs 文件,添加以下代码来启用 WebSocket 支持: ```csharp public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseWebSockets(); app.UseMvc(); } ``` **5. 测试 WebSocket** 现在,我们可以使用任何 WebSocket 客户端来测试上面的代码。例如,我们可以使用 JavaScript 来创建一个 WebSocket 客户端。 ```javascript var socket = new WebSocket("ws://localhost:5000/api/websocket/1"); socket.onmessage = function (evt) { console.log(evt.data); }; socket.onopen = function (evt) { console.log("WebSocket opened."); socket.send("Hello from client."); }; ``` 在上面的 JavaScript 代码,我们创建了一个 WebSocket 连接,并在连接打开时发送一条消息。当收到消息时,我们将其输出到控制台。 **6. 运行应用程序** 现在我们可以运行应用程序,并使用 WebSocket 客户端连接到我们的 WebSocket 服务。运行应用程序后,控制台应该显示类似以下内容: ``` WebSocket opened. Hello from client. ``` 接着,WebSocket 服务将回发相同的消息,并在控制台输出以下内容: ``` Hello from client. ``` 这就是使用ASP.NET Core创建WebSocket服务的详细步骤和示例代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值