随着互联网的普及,实时通信在很多应用场景中已经变得不可或缺。例如,在聊天室应用程序中,用户需要实时收到其他人的消息;在在线协作应用程序中,用户需要实时看到其他人的操作等等。本文将介绍如何使用 Server-Sent Events 和.NET Core 构建一个实时通信的应用程序。
什么是 Server-Sent Events?
Server-Sent Events(简称 SSE)是一种 HTML5 技术,可以使服务端向客户端推送实时数据。它与 WebSocket 不同,WebSocket 通常用于双向通信,而 SSE 主要用于单向通信。SSE 以 HTTP 请求方式(GET)进行交互,服务器将实时事件(event)以块(chunk)的方式发送给客户端。客户端通过 JavaScript 中的 EventSource 对象进行接收和处理。
SSE 的优势
与其他实时通信技术相比,SSE 具有以下优势:
- SSE 协议是基于 HTTP 的,因此可以通过防火墙和负载均衡器等网络设备,不需要额外的端口或协议,便于部署和维护。
- SSE 的 API 简单易用,只需要一个 HTTP 请求和一个 EventSource 对象,即可实现实时通信。
- SSE 内置了自动重连机制,当连接意外中断时,客户端会自动重新连接。
- SSE 支持自定义事件类型和数据格式,可以根据应用场景进行灵活的配置和扩展。
SSE 的缺点
SSE 的缺点主要有以下几点:
- SSE 是基于 HTTP 的,因此只能实现单向通信,无法实现双向通信。
- SSE 虽然支持自动重连,但对于客户端来说,原始连接断开后需要一定时间才能重新连接成功,这可能会导致一定程度的延迟。
- SSE 不支持二进制数据的传输,只能传输字符串或 JSON 形式的数据。
- SSE 在某些浏览器上不支持或支持不完整,例如 Internet Explorer。
使用 .NET Core 实现 SSE
在 .NET Core 中,实现 SSE 的方式很简单。只需要在控制器中编写一个返回 SSE 的方法,然后将需要推送的数据通过事件的形式发送给客户端即可。
代码示例
首先,我们来定义一个 SSE 事件模型:
public class SseEvent { public string Type { get; set; } public object Data { get; set; } }
其中,Type 字段表示事件类型,可以用来进行数据分类和过滤。Data 字段表示事件的数据内容,可以是任何对象。
接下来,我们在控制器中定义一个 SSE 推送方法:
-- -------------------- ---- ------- ------------------- ------ ----- ------------------- -------- - ------------------------------------ --------------------- ------------------------------------- ------------ --- --- - ---------------------------------------------------------------------- --- ----- - ------------------------------------- --- -------- - ----- ----------------------- ---------------------------- ----- --------- -- ----- - --- ---- - ----------------------------------- --- ------- - -------- ---------------------- ------------ ----- ----------------------------- -------- - ----- ----------------------- ---------------------------- - ------ --- -------------- -展开代码
在此方法中,我们首先设置响应的 MIME 类型为 “text/event-stream”,禁用缓存,并获取一个可以处理 SSE 事件的服务(后面会详细介绍)。然后,我们通过 HttpContext.TraceIdentifier 生成一个唯一的 SSE id,以便服务端可以区分不同的 SSE 连接。接着,我们循环接收 SSE 事件,并将事件内容以块的形式发送给客户端,事件类型使用 "event" 字段,数据部分使用 "data" 字段。
最后,返回一个空结果,表示完成响应。
在上述代码中,我们依赖了一个 SseService 服务,用于发送和接收 SSE 事件。下面是 SseService 的实现:
-- -------------------- ---- ------- ------ ----- ---------- - ------- -------- ---------------------------- --------- ------- - --- ---------------------------- ------------ ------- -------- ------------------- -------- ------ ------------------------------ ------- - ------- - ------- - ------ ----- ---- ---------------- ----- ------ ----- ----------------- ----------------- - -------- - --- -------- - --- -------- - ---- - ----- ---- - ---- -- --- ----- - ------------------------------- - -- --- ------------ ----- ---------------------------- ------------------- ---------------------------- --- ------ -------- ------ - ------ ----- -------------- ------------------- --- ----------------- ----------------- - -------- - --- ----- - -------------------- - -- --- ------------ ------ ----- -------------------------------------- - ------- ----- -------- - ------- -------- ------------------------- ------ - --- ---------------------------- ------- -------- ------------- -------------- - --- ----------------- ------ ----- ---- --------------------- --------- ----------------- ----------------- - -------- - ------------------------- ------------------------- ----- ------------------- - ------ ----- -------------- ------------------------------ ----------------- - -------- - ----- -------------------------------------------- -- ---------------------- --- ---------- - ------ --------- - ------ ----- - - -展开代码
此处我们使用了一个 SseService 服务,用于维护 SSE 连接和事件队列。SseService 使用了一个线程安全的 ConcurrentDictionary 来保存事件队列,根据事件类型进行区分。我们通过 SendAsync 方法向队列中添加事件,通过 ReceiveAsync 方法从队列中获取事件。
事件队列使用了一个内部类 SseQueue 来实现,在此类中,我们使用了一个 SemaphoreSlim 对象来实现一个异步的事件队列,可以很轻松地实现事件的排队和出队操作。
示例应用程序
为了更好地演示如何使用.NET Core 和 Server-Sent Events 实现实时通信,我们可以构建一个简单的聊天室应用程序。这个应用程序包括以下几个部分:
- 前端页面:使用 HTML、JavaScript 和 Bootstrap 框架构建一个简单的聊天室页面,包括一个消息列表和一个输入框,用于发送和接收消息。
- .NET Core 服务器:使用上述代码示例中的 SSE 服务和控制器实现服务器端的 SSE 推送和接收功能,以及处理客户端发来的消息。
- SignalR:SignalR 是 .NET Core 中提供的一个实时通信框架,支持双向通信和多种传输协议。在本应用程序中,我们将使用 SignalR 实现双向通信和多人聊天功能。
前端页面
前端页面使用了 Bootstrap 框架,其中包括 CSS 样式、JS 文件和 HTML 页面。其中,我们使用了 js/sse.js 脚本文件,用于连接 SSE 服务,接收服务器端推送的消息。HTML 页面的代码如下:
-- -------------------- ---- ------- --------- ----- ------ ------ ----- --------------- -- ----- --------------- ---------------------------- ------------------- ------------------ ----- -------------------------------------------------------------------------------------- ----------------- ------- ------ ---- ---------------- ------ ---- ------------ ---- ------------ ------------ ------ ------ ---- ------------ ---- ------------ --- ----------------- --------------------------- ------ ------ ---- ------------ ---- ------------ ----- --------------- ---- -------------------- ------ ------------------ ----------- -------------------- ------------------ ----------------- -------------------------------- ------- ---------- ------------ ------------- ---------------------------- ------ ------- ------ ------ ------ ------- ---------------------------------------------------------------------------- ------- --------------------------------------------------------------------------------------------- ------- -------------------------- ------- --------------------------- ------- -------展开代码
在上述代码中,我们定义了一个基本的聊天室界面,包括一个消息列表和一个输入框。我们将会在后面的 JavaScript 脚本中实现与服务器端的连接和消息发送/接收功能。在聊天室页面中,我们需要使用消息列表和输入框中的 ID,以便在处理 JavaScript 事件时能够正确地获取相关元素。
JavaScript 代码
在前端页面中,我们通过 SSE 技术实现服务器端推送消息的功能。本部分讲解如何使用 JavaScript 脚本连接 SSE 服务和接收服务器端的消息。代码如下:
-- -------------------- ---- ------- ---- -------- --- ---- - - ------------ ----- ----- -------- -- - -- -------- ---------------- - ---------------------------------------- -- -- --- -- --- ------ - --- ---------------------------- ---------------- - --------------- -------------- - ------------- ------------- - -------- -- - ---------------------- -- --- ---------- -- ------------------------------- -------- -- - ----------------- ------------ --- -- ----------- --- ---- - ------------------------------------- ------------------------------- --------------- -- ---------- -------- ------- - --------------------- ------ - - ------------ --- ---- - ----------------------- -- ---------- --- ---------- - --------------------------- - -- -------- -------- ------- - ------------------ ------ - - ------------------ -- --------- -------- ------- - ----------------------- --- ----- - ----------------------------------------- --- ---- - ------------------- -- ------ - ----------------------- ----------- - --- - -------------- -- ----------- -------- --------- - --- -- - ----------------------------- ------------ - -------- --------------------------------- -- ------------ -------- ------ - -------------------- - -------- ---- -- -------- -- - -------------------- ----- - - ------ --- - -- ---------- -- - ------------ ---展开代码
在上述代码中,我们使用了一个 chat 对象,用于处理多个事件。首先,我们获取 HTML 页面中的消息列表元素,以便在接收到新消息时能够正确的渲染到聊天室界面中。
接着,我们使用 JavaScript 的 EventSource 对象连接 SSE 服务。我们将 SSE 服务的 URL 在 JavaScript 程序中硬编码了,实际应用中需要根据当前页面的 URL 动态生成。我们通过 onmessage 事件来处理接收到的 SSE 事件,根据事件类型(“message”)进行判断,将数据内容添加到消息列表中。
我们也可以处理 SSE 的错误事件(onerror ),输出事件的状态编号,以便在调试应用程序时更加方便。在此处,我们使用 onopen 事件和 addEventListener 方法处理一个 ping 事件,以确保 SSE 服务和客户端之间的连接状态。
同时,我们在表单提交事件中处理用户输入的消息内容。当用户点击发送按钮时,在 JavaScript 程序中处理表单提交事件,在向服务端发送用户消息之前,需要先验证消息内容是否为空,如果不为空则通过 POST 方式将消息发送给服务器。
在最后一步,我们实现了三个基本的函数:addMessage、sendMessage 和 init。其中,addMessage 用于添加消息到消息列表,sendMessage 用于发送消息到服务器。init 是 chat 对象的初始化函数,我们通过它来连接 SSE 服务器和注册表单事件。
.NET Core Controller
在服务器端,我们使用 .NET Core 的控制器来处理 SSE 事件和消息发送功能。代码如下:
-- -------------------- ---- ------- --------------- ----------------------- ------ ----- -------------- - -------------- - ------- -------- ----------------------- -------- ------- -------- ---------- ------------ ------- -------- ------------- ------------ - --- --------------- ------ ------------------------- ----------- ----------------------- ------- - ----------- - ----------- ------- - ------- - ------------------- ------ ----- ---- -------- - ------------------------------------ --------------------- ------------------------------------- ------------ --- ----- - ------------------------------------- ------------------------ ----- ----------------- ----- -------------------------------- --------- --------- ----- ----------------- ------- ---- ------- -- ------------- - ----- -------------------------------- ----------- --------- - --- ----------------- - --------------------------- --- -------- - ----- ------------------------------- ------------------- ----- --------- -- ----- - ------------------------------- -------- -------------- --------------- ----- -------------------------------- ---------- ------------------ -------- - ----- ------------------------------- ------------------- - - ------------------ ------ ----- ------------------- --------------- ------ -------- - -- ------------------------------- - ------ ------------- - ----- -------------------------------- --------- ------ ----- - -展开代码
在上述代码中,我们首先注入了一个 SseService 对象和 ILogger 对象,用于处理 SSE 事件和日志功能。我们也定义了一个连接池(_connections)来保存当前所有连接的 SSE Id。
在控制器中,我们处理了 SSE 的连接请求(Stream 方法),并在连接建立后将客户端的 SSE Id 添加到连接池中。接着,我们使用 Delay 方法模拟几次 SSE 事件发送,将用户上线和加入聊天室的信息发送给所有客户端。
紧接着,我们从 SSE 服务中获取客户端发送的消息,并处理该事件。我们将消息转发给所有客户端。此处,我们使用了 ReceiveAsync 方法从 SSE 服务的队列中读取事件,每次只读取一次,以便其它 SSE 事件可以在此期间得到处理。
在发送消息到服务器的方法(Send 方法)中,我们从 POST 请求的参数中获取消息内容,并验证其是否为空。如果不为空,则通过 SSE 服务将消息发送给所有客户端。
SignalR 消息处理
在聊天室应用程序中,我们希望能够实现多人聊天的功能。因此,我们需要实现双向通信的功能,以便客户端之间能够相互通信。为了实现这个功能,我们将使用 SignalR 框架来处理消息的发送和接收。
首先,我们需要在 .NET Core 应用程序中添加 SignalR 包:
dotnet add package Microsoft.AspNetCore.SignalR --version 5.0.10
接着,在 Startup.cs 文件中,我们注册 SignalR 的服务:
-- -------------------- ---- ------- ----- ----------------------------- ----- ----------------------------- ----- ----------------------------------- ----- ----------------------------------------- ----- ----------------------------- --------- ------- - ------ ----- ------- - ------ -------------- ------------- - ---- - ------ ---------------------- -------------- - ------------- - -------------- - ------ ---- ------------------------------------ --------- - ---------------------- ------------------------------------ -------------------------- - ------ ---- ----------------------------- ---- ------------------- ---- - -- --- ----------------- -------------------------- -- - --------------------------- ---------------------------------------- --- - - -展开代码
在本处,我们使用了 SignalR 的 AddSignalR 扩展方法,注册 SignalR 服务。这个方法的作用是注册 SignalR 的相关选项和服务,包括 SignalR 中间件和选项配置项等。我们也使用了 singleton 生命周期添加了一个 SseService 服务,用于管理 SSE 的发送和接收操作。
为了使 SignalR 功能生效,我们在 Configure 方法中调用 UseEndpoints 方法,并在其中添加 MapHub 扩展方法。MapHub 方法的作用是将 SignalR 中心的 URL 映射到当前应用程序的 URL 地址,以便客户端能够连接到正确的中心。
接着,我们定义一个 ChatHub 类,用于处理 SignalR 消息的接收和发送。
-- -------------------- ---- ------- ----- ----------------------- ----- ----------------------------- --------- ------------ - ------ ----- ------- - --- - ------ ----- ---- ------------------ -------- - ----- --------------------------------------- --------- - - -展开代码
在 ChatHub 类中,我们使用 SignalR 的 Hub 基类来处理消息的接收和发送。我们定义了一个 SendMessage 方法,用于从客户端接收消息,并使用 Clients.All.SendAsync 方法将消息发送到所有连接到 SignalR 中心的客户端。
最后,在前端页面中,我们使用 SignalR 的 JavaScript 客户端来处理消息的接收和发送,代码如下:
-- -------------------- ---- ------- --- ---- - - ------------ ----- ----------- ----- ----- -------- -- - -- -------- ---------------- - ---------------------------------------- -- -- ------- -- --------------- - --- ------------------------------ ---------------------- --------- ------------------------------------ ---------------- ------------------------------------- -- - ---------------------- -- ------- ---------- --- -- ----------- --- ---- - ------------------------------------- ------------------------------- --------------- -- ---------- -------- --------- - --------------------- -------- - - --------- ------------------------- -- --------- -------- ------- - ----------------------- --- ----- - ----------------------------------------- --- ---- - ------------------- -- ------ - ----------------------- ----------- - --- - -------------- -- ----------- -------- --------- - --- -- - ----------------------------- ------------ - -------- --------------------------------- -- ------------ -------- ------ - ------------------------------------- ------------------- -- - -------------------- ----- - - ------ --- - -- ---------- -- - ------------ ---展开代码
此时,我们已经完成了这个简单的聊天室应用程序,包括使用 SSE 技术实现服务器端推送消息,使用 SignalR 实现双向通信和多人聊天功能。
总结
本文介绍了 Server-Sent Events 和.NET Core 的相关知识,以及如何使用两者结合实现一个实时通信的聊天室应用程序。我们使用了 SSE 技术推送消息和 SignalR 实现多人聊天功能,并详细介绍了代码实现和工作原理,希望这些内容能够对读者理解网络通信和实时通信技术有所帮助。在实际应用中,我们可以结合自身需求和技术条件,采用最合适的实时通信技术来实现应用程序。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/651d330495b1f8cacd4b98bb