使用.NET Core搭建WebSocket聊天室

第一步:安装WebScokets库

首先需要安装Microsoft.AspNetCore.WebSockets这个库

第二步:.NET Core与中间件

需要在Startup类的Configure方法中添加WebSocket中间件:

app.UseWebSockets();

更一般地,我们可以配置以下两个配置,其中,KeepAliveInterval表示向客户端发送Ping帧的时间间隔;ReceiveBufferSize表示接收数据的缓冲区大小: 

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
    ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets(webSocketOptions);

那么怎么接收一个来自客户端的请求呢?

首先,我们需要判断下请求的地址,这是客户端和服务端约定好的地址,默认为/,这里我们以/ws为例;接下来,我们需要判断当前的请求上下文是否为WebSocket请求,通过context.WebSockets.IsWebSocketRequest来判断。当这两个条件同时满足时,我们就可以通过context.WebSockets.AcceptWebSocketAsync()方法来得到WebSocket对象,这样就表示“握手”完成,这样我们就可以开始接收或者发送消息啦。

if (context.Request.Path == "/ws")
{
    if (context.WebSockets.IsWebSocketRequest)
    {
        WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
        //TODO
    }
});

一旦建立了Socket连接,客户端和服务端之间就可以开始通信,这是我们从Socket中收获的经验,这个经验同样适用于WebSocket。这里分别给出WebSocket发送和接收消息的实现,并针对代码做简单的分析。

private async Task SendMessage<TEntity>(WebSocket webSocket, TEntity entity)
{
    var Json = JsonConvert.SerializeObject(entity);
    var bytes = Encoding.UTF8.GetBytes(Json);

    await webSocket.SendAsync(
        new ArraySegment<byte>(bytes),
        WebSocketMessageType.Text,
        true,
        CancellationToken.None
    );
}

这里我们提供一个泛型方法,它负责对消息进行序列化并转化为byte[],最终调用SendAsync()方法发送消息。与之相对应地,客户端会在onmessage()回调中就会接受到消息,这一点我们放在后面再说。WebSocket接收消息的方式,和传统的Socket非常相似,我们需要将字节流循环读取到一个缓存区里,直至所有数据都被接收完。下面给出基本的代码示例:

var buffer =new ArraySegment<byte>(new byte[bufferSize);
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
while (!result.EndOfMessage)
{
    result = await webSocket.ReceiveAsync(buffer, default(CancellationToken));
}
var json = Encoding.UTF8.GetString(buffer.Array);
json = json.Replace("\0", "").Trim();
return JsonConvert.DeserializeObject<TEntity>(json, new JsonSerializerSettings()
{
    DateTimeZoneHandling = DateTimeZoneHandling.Local
});

 虽然不大清楚,为什么这里反序列化后的内容中会有大量的\0,以及这个全新的类型ArraySegment到底是个什么鬼,不过程序员的一生无非都在纠结这样两个问题,“it works” 和 “it doesn't works",就像人生里会让你纠结的无非是”她喜欢你“和”她不喜欢我“这样的问题。

即示例中的代码都是写在app.Use()方法中的,这样会使我们的Startup类显得臃肿,而熟悉OWIN或者http://ASP.NET Core的朋友,就会知道Startup类是一个非常重要的东西,我们通常会在这里配置相关的组件。在http://ASP.NET Core中,我们可以通过Configure()方法来为IApplicationBuilder增加相关组件,这种组件通常被称为中间件。那么,什么是中间件呢?

preview

 从这张图中可以看出,中间件实际上是指在HTTP请求管道中处理请求和响应的组件,每个组件都可以决定是否要将请求传递给下一个组件,比如身份认证、日志记录就是最为常见的中间件。在http://ASP.NET Core中,我们通过app.Use()方法来定义一个Func类型的参数,所以,我们可以简单地认为,在http://ASP.NET Core中,Func就是一个中间件,而通过app.Use()方法,这些中间件会根据注册的先后顺序组成一个链表,每一个中间件的输入是上一个中间件的输出,每一个中间件的输出则会成为下一个中间件的输入。简而言之,每一个RequestDelegate对象不仅包含了自身对请求的处理,而且包含了后续中间件对请求的处理,我们来看一个简单的例子:

app.Use(async (context,next)=>
{
    await context.Response.WriteAsync("这是第一个中间件\r\n");
    await next();
});

app.Use(async (context,next)=>
{
    await context.Response.WriteAsync("这是第二个中间件\r\n");
    await next();
});

app.Use(async (context,next)=>
{
    await context.Response.WriteAsync("这是第三个中间件\r\n");
    await next();
});

 通过Postman或者任意客户端发起请求,我们就可以得到下面的结果,现在想象一下,如果我们在第一种中间件中不调用next()会怎么样呢?答案是中间件之间的链路会被打断,这意味着后续的第二个、第三个中间件都不会被执行。什么时候我们会遇到这种场景呢?当我们的认证中间件认为一个请求非法的时候,此时我们不应该让用户访问后续的资源,所以直接返回403对该请求进行拦截。在大多数情况下,我们需要让请求随着中间件的链路传播下去,所以,对于每一个中间件来说,除了完成自身的处理逻辑以外,还至少需要调用一次next(),以保证下一个中间件会被调用,这其实和职责链模式非常相近,可以让数据在不同的处理管道中进行传播。

 OK,这里我们继续遵从这个约定,将整个聊天室相关的逻辑写到一个中间件里,这样做的好处是,我们可以将不同的WebSocket互相隔离开,同时可以为我们的Startup类”减负“。事实证明,这是一个正确的决定,在开发基于WebSocket的弹幕功能时,我们就是用这种方式开发了新的中间件。这里,我们给出的是WebSocketChat中间件中最为关键的部分,详细的代码我已经放在Github上啦,大家可以参考WebSocketChat类,其基本原理是:使用一个字典来存储每一个聊天室中的会话(Socket),当用户打开或者关闭一个WebSocket连接时,会向服务器端发送一个事件(Event),这样客户端中持有的用户列表将被更新,而根据发送的消息,可以决定这条消息是被发给指定联系人还是群发:

public async Task Invoke(HttpContext context)
{
    if (!IsWebSocket(context))
    {
        await _next.Invoke(context);
        return;
    }

    var userName = context.Request.Query["username"].ToArray()[0];
    var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    while (webSocket.State == WebSocketState.Open)
    {
         var entity = await Receiveentity<MessageEntity>(webSocket);
         switch (entity.Type)
         {
             case MessageType.Chat:
                  await HandleChat(webSocket, entity);
                  break;
             case MessageType.Event:
                  await HandleEvent(webSocket, entity);
                  break;
         }
    }

    await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", default(CancellationToken));
}

其中,HandleEvent负责对事件进行处理,HandleChat负责对消息进行处理。当有用户加入聊天室的时候,首先会向所有客户端广播一条消息,告诉大家有新用户加入了聊天室,与此同时,为了让大家可以和新用户进行通信,必须将新的用户列表推送到客户端。同理,当有用户离开聊天室的时候,服务器端会有类似的事件推送到客户端。事件同样是基于消息来实现的,不过这两种采用的数据结构不同,具体大家可以通过源代码来了解。发送消息就非常简单啦,给指定用户发送消息是通过用户名来找WebSocket对象,而群发消息就是遍历字典中的所有WebSocket对象,这一点我们不再详细说啦!

Vue驱动的客户端

在实现服务端的WebSocket以后,我们就可以着手客户端的开发啦!这里我们采用原生的WebSocket API来开发相关功能。具体来讲,我们只需要实例化一个WebSocket类,并设置相应地回调函数就可以了,我们一起来看下面的例子:

var username = "PayneQin"
var websocket = new WebSocket("ws://localhost:8002/ws?username=" + username);

这里我们使用/s这个路由来访问WebSocket,相应地,在服务端代码中我们需要判断context.Request.Path,WebSocket在握手阶段是基于HTTP协议的,所以我们可以以QueryString的形式给后端传递一个参数,这里我们需要一个用户名,它将作为服务端存储WebSocket时的一个键。一旦建立了WebSocket,我们就可以通过回调函数来监听服务器端的响应,或者是发送消息给服务器端。主要的回调函数有onopen、onmessage、onerror和onclose四个,基本使用方法如下:

websocket.onopen = function () {
    console.log("WebSocket连接成功");
};

websocket.onmessage = function (event) {
    console.log("接收到服务端消息:" + event.data)
};

websocket.onerror = function () {
    console.log("WebSocket连接发生错误");
};

websocket.onclose = function () {
console.log("WebSocket连接关闭");
};

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HTML 5 Web Sockets is a powerful and effective technique for real-time information processing. There exists many techniques such as Poling, Long Poling and Streaming that are said to be better earier days. With web sockets, it shows a better outcome for the end user as well as a proper utilization of the server bandwidth. WebSocket is a web technology providing full-duplex communications channels over a single TCP connection. The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011, and the WebSocket API for in Web IDL is being standardized by the W3C.WebSocket is designed to be implemented in web browsers and web servers, but it can be used by any client or server application. The WebSocket Protocol is an independent TCP-based protocol. Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request. The WebSocket protocol makes possible more interaction between a browser and a web site, facilitating live content and the creation of real-time games. This is made possible by providing a standardized way for the server to send content to the browser without being solicited by the client, and allowing for messages to be passed back and forth while keeping the connection open. In this way a two-way (bi-directional) ongoing conversation can take place between a browser and the server. A similar effect has been achieved in non-standardized ways using stop-gap technologies such as Comet.In addition, the communications are done over TCP port number 80, which is of benefit for those environments which block non-standard Internet connections using a firewall. WebSocket protocol is currently supported in several browsers including Google Chrome, Internet Explorer, Firefox, Safari and Opera. WebSocket also requires web applications on the server to support it. Here goes a comparison of polling vs Web Sockets.
好的,下面是一个简单的 .NET Core 6 WebSocket 聊天应用程序示例,包含了群聊和私聊功能: 1. 创建 ASP.NET Core 6 Web 应用程序 首先,我们需要创建一个 ASP.NET Core 6 Web 应用程序。可以使用 Visual Studio 或者 Visual Studio Code 来创建。 2. 安装 Microsoft.AspNetCore.WebSockets 包 在项目中安装 Microsoft.AspNetCore.WebSockets 包,使用以下命令: ``` dotnet add package Microsoft.AspNetCore.WebSockets ``` 3. 创建 WebSocket 控制器 创建一个名为 ChatController 的控制器,并添加以下代码: ```csharp using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebSockets; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; namespace WebSocketChat.Controllers { [Route("api/[controller]")] [ApiController] public class ChatController : ControllerBase { private readonly WebSocketManager _webSocketManager; public ChatController(WebSocketManager webSocketManager) { _webSocketManager = webSocketManager; } [HttpGet("{username}")] public async Task Get(string username, CancellationToken cancellationToken) { if (HttpContext.WebSockets.IsWebSocketRequest) { WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var socketConnection = new SocketConnection(username, webSocket); _webSocketManager.AddSocket(socketConnection); await socketConnection.ReceiveAsync(_webSocketManager, cancellationToken); } else { HttpContext.Response.StatusCode = 400; } } } } ``` 这个控制器包含了 WebSocket 连接的处理逻辑。它从 HTTP 上下文中获取 WebSocket 对象,然后创建一个 SocketConnection 对象来处理连接。接下来,将 SocketConnection 对象添加到 WebSocketManager 中,以便以后可以使用。 4. 创建 WebSocketManager 和 SocketConnection 类 创建一个名为 WebSocketManager 的类,表示所有 WebSocket 连接的集合。还需要创建名为 SocketConnection 的类,表示单个 WebSocket 连接。 ```csharp using System.Collections.Concurrent; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace WebSocketChat { public class WebSocketManager { private ConcurrentDictionary<string, SocketConnection> _sockets = new ConcurrentDictionary<string, SocketConnection>(); public void AddSocket(SocketConnection socket) { _sockets.TryAdd(socket.Username, socket); } public void RemoveSocket(string username) { _sockets.TryRemove(username, out _); } public async Task SendMessageAsync(string username, string message) { if (_sockets.TryGetValue(username, out var socket)) { await socket.SendAsync(message); } } public async Task BroadcastAsync(string message) { foreach (var socket in _sockets.Values) { await socket.SendAsync(message); } } } public class SocketConnection { public string Username { get; } public WebSocket WebSocket { get; } public SocketConnection(string username, WebSocket webSocket) { Username = username; WebSocket = webSocket; } public async Task ReceiveAsync(WebSocketManager webSocketManager, CancellationToken cancellationToken) { var buffer = new byte[1024 * 4]; while (WebSocket.State == WebSocketState.Open) { var result = await WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken); if (result.MessageType == WebSocketMessageType.Text) { var message = Encoding.UTF8.GetString(buffer, 0, result.Count); var index = message.IndexOf(':'); if (index != -1) { var toUser = message.Substring(0, index); var content = message.Substring(index + 1); await webSocketManager.SendMessageAsync(toUser, $"{Username}: {content}"); } else { await webSocketManager.BroadcastAsync($"{Username}: {message}"); } } else if (result.MessageType == WebSocketMessageType.Close) { await WebSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, cancellationToken); webSocketManager.RemoveSocket(Username); } } } public async Task SendAsync(string message) { var bytes = Encoding.UTF8.GetBytes(message); var buffer = new ArraySegment<byte>(bytes); await WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } } } ``` WebSocketManager 类维护了所有 WebSocket 连接的集合,并提供了一些方法来发送消息。SocketConnection 类表示单个 WebSocket 连接,它维护了连接的用户名和 WebSocket 对象。 5. 创建聊天页面 最后,我们需要创建一个聊天页面,让用户可以在其中输入聊天消息。可以使用 HTML 和 JavaScript 来创建这个页面。 ```html <!DOCTYPE html> <html> <head> <title>WebSocket Chat</title> </head> <body> <h1>WebSocket Chat</h1> <div> <input type="text" id="username" placeholder="Enter your username"> <button onclick="connect()">Connect</button> </div> <div> <input type="text" id="toUser" placeholder="Enter recipient (optional)"> <input type="text" id="message" placeholder="Enter message"> <button onclick="send()">Send</button> </div> <div> <ul id="messages"></ul> </div> <script> var socket; function connect() { var username = document.getElementById("username").value; var url = "ws://" + window.location.host + "/api/chat/" + username; socket = new WebSocket(url); socket.onmessage = function (event) { var messages = document.getElementById("messages"); var li = document.createElement("li"); li.textContent = event.data; messages.appendChild(li); }; } function send() { var toUser = document.getElementById("toUser").value; var message = document.getElementById("message").value; if (toUser) { message = toUser + ":" + message; } socket.send(message); } </script> </body> </html> ``` 这个页面包含了一个表单,让用户输入他们的用户名和聊天消息。当用户单击 Connect 按钮时,将会创建一个 WebSocket 连接。当用户单击 Send 按钮时,将会向服务器发送聊天消息。 以上就是一个简单的 .NET Core 6 WebSocket 聊天应用程序示例,包含了群聊和私聊功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值