计算机网络 --- WebSocket协议 和 Signalr
什么是WebSocket
- HTTP是基于TCP协议的,同一时间里,客户端和服务器只能有一方主动发数据,是半双工通信。
- 通常,打开某个网页,我们每点击一次网页上的某个选项,前端就会发送一次HTTP请求,网站返回一次HTTP响应。这种由客户端主动请求,服务器响应的方式满足大部分网页的功能场景。但这种情况下,服务器不会主动给客户端发消息。而类似网页游戏这样的场景,是需要客户端和服务器之间互相主动发大量数据的。
- 因此,我们需要一个基于TCP的新协议,即新的应用层协议WebSocket。
- 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种
- 其他特点包括:
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和
- 并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL
- 使用WebSocket注意事项
- 资源消耗:WebSocket 连接是持久的,这意味着服务器端需要分配和管理每个连接的资源。如果同时有大量连接存在,可能会对服务器的性能和资源消耗造成压力
- 安全性考虑:由于 WebSocket 允许服务器端向客户端发送实时数据,因此存在安全风险。如果不正确地实施安全措施,可能会导致跨站点脚本攻击(XSS)和其他安全漏洞
- 连接状态维护:与传统的 HTTP 请求不同,WebSocket 建立了一个持久的连接,需要维护连接状态。这意味着服务器端需要消耗额外的资源来管理连接、保持连接状态和处理连接断开等情况
- 防火墙和代理限制:某些网络环境中的防火墙或代理服务器可能会阻止 WebSocket 连接。这可能导致在特定网络配置下,无法建立或保持 WebSocket 连接,从而影响实时通信功能的可用性
什么是Signalr
- 因为WebSocket是无状态的,如果因为网络问题中断的话, 则重连之后, 服务器会丢失所有客户端的信息. 所以使用原生WebSocket的话, 需要自己在服务器端实现状态管理.
- SignalR 是一个开源的 Microsoft .NET 库,用于实现实时双向通信功能。它简化了在 Web 应用程序中实现实时功能的开发过程,允许服务器端代码能够主动推送数据到客户端,并支持客户端与服务器之间的实时双向通信。
- SignalR 支持多种传输协议
- WebSocket
- Server-Sent Events(SSE)
- 长轮询
- Forever Frame
- Signalr对于每个链接提供了一个connectionId, 这样可以服务器端可以通过connectionId识别出来客户端, 实现状态管理
SignalR 的主要特点包括:
- 实时双向通信:SignalR 允许服务器端向客户端实时推送数据,同时也支持客户端向服务器端发送消息,实现双向通信。
- 广播和组管理:SignalR 提供了广播功能,可以将消息同时发送给多个客户端。它还支持将客户端分组,可以根据需要将消息发送给特定的客户端组。
- 自动重连和状态管理:SignalR 具有自动重连功能,如果连接中断,它会尝试重新建立连接。同时,它还能够管理客户端和服务器之间的连接状态,包括连接建立、连接断开和错误处理等。
- 扩展性和可定制性:SignalR 可以在不同的扩展点进行定制,开发人员可以根据需要修改消息传输、身份验证和授权等方面的行为。
- 总体而言,SignalR 提供了一种简单且强大的方式来实现实时双向通信功能,并且能够与 ASP.NET、ASP.NET Core 和其他 .NET 平台无缝集成。
Signalr Example – SimpleChat
服务器端
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace SignalRServer
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
//将Hub和Http uri进行绑定
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chathub");
});
}
}
public class ChatHub : Microsoft.AspNetCore.SignalR.Hub
{
public async Task Broadcast(string message)
{
//"ReceiveMessage" 是客户端注册的接收消息的回调函数的名字
await Clients.All.SendAsync("ReceiveMessage", message);
}
public async Task SendMessage(string message)
{
var connectionId = Context.ConnectionId;
await Clients.All.SendAsync("ReceiveMessage", connectionId, message);
}
}
}
- 在上述代码中,我们首先在 ConfigureServices 方法中添加了 SignalR 服务。然后,在 Configure 方法中注册了 ChatHub,并将其映射到 /chathub 路由。
- ChatHub 是一个继承自 Microsoft.AspNetCore.SignalR.Hub 的类,它包含了一个名为 SendMessage 的方法,用于接收客户端发送的消息,并将消息通过 Clients.All.SendAsync 发送给所有连接的客户端。
客户端
from signalrcore.hub_connection_builder import HubConnectionBuilder
# 创建 SignalR 连接
connection = HubConnectionBuilder() \
.with_url("http://example.com/chathub") \
.build()
# 定义收到消息时的回调函数
def on_message(message):
print("Received message:", message)
# 注册消息回调函数
connection.on("ReceiveMessage", on_message)
# 启动连接
connection.start()
# 发送消息, SendMessage 是服务器端Hub里定义的接受消息的方法名
while True:
message = input("Enter a message to send: ")
connection.send("SendMessage", message)
# 断开连接
connection.stop()
- 在这个例子中,我们使用 signalrcore 库创建了一个 SignalR 连接,并指定了服务器的 URL(http://example.com/chathub)。然后,我们定义了一个回调函数 on_message,用于处理接收到的消息。接下来,我们通过调用 connection.on() 注册回调函数,以便在收到消息时触发回调函数。
- 然后,我们通过调用 connection.start() 启动连接。在连接启动后,我们进入一个循环,等待用户输入要发送的消息,并通过调用 connection.send() 发送消息到服务器.
注意
- Hub 类是Transient的, 也就是用完一次就会被销毁
- 不要将状态存储在 Hub 类的属性中。每次调用 Hub 方法时,都会在一个新的 Hub 实例上执行。
- 不要直接通过依赖注入实例化一个 Hub。可以使用 IHubContext 来从应用程序的其他位置向客户端发送消息。
- 在调用依赖于 Hub 保持活动状态的异步方法时,请使用 await。例如,如果在没有使用 await 的情况下调用类似 Clients.All.SendAsync(…) 的方法,并且在 SendAsync 完成之前 Hub 方法已经完成,那么该方法可能会失败.
使用 IHubContext 从应用程序的其他位置向客户端发送消息
public class NotificationService
{
private readonly IHubContext<ChatHub> _hubContext;
public NotificationService(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public async Task SendNotificationToAllClients(string message)
{
await _hubContext.Clients.All.SendAsync("ReceiveNotification", message);
}
}