113°

实时web应用方案——SignalR(.net core)

何为实时

先从理论上解释一下两者的区别。

大多数传统的web应用是这样的:客户端发起http请求到服务端,服务端返回对应的结果。像这样:

 

也就是说,传统的web应用都是客户端主动发起请求到服务端。

那么实时web应用呢?它不需要主动发起请求,服务端可以主动推送信息到客户端。

举栗子的话,实时聊天工具、web游戏等都可以算是实时应用。

什么是SignalR

如果想做一个实时应用,最好用web socket。很早以前我也写过web socket的实现方式,但不够全面,这里再补上一篇。

来说说signalR,它是一款开源的实时框架,可以使用三种方式实现通信(long polling、server sent events、web socket)。它很好的整合了底层技术,让我们可以不用关注底层技术实现而把精力聚焦在业务实现上。一个完整的signalR包括客户端和服务端,服务端支持net core/net framework,还支持大部分客户端,比如浏览器和桌面应用。

回落机制

为了兼容不同浏览器(客户端)和服务端,signalR采用了回落机制,使得它可以根据情况协商使用不同的底层传输方式。假如浏览器不支持web socket,就自动降级使用sse,再不行就long polling。当然,也可以禁用这种机制,指定其中一种。

三种通信方式

long polling(长轮询)

长轮询是客户端发起请求到服务端,服务器有数据就会直接返回。如果没有数据就保持连接并且等待,一直到有新的数据返回。如果请求保持到一段时间仍然没有返回,这时候就会超时,然后客户端再次发起请求。

这种方式优点就是简单,缺点就是资源消耗太多,基本是不考虑的。

server sent events(sse)

如果使用了sse,服务器就拥有了向客户端推送的能力,这些信息和流信息差不多,期间会保持连接。

这种方式优点还是简单,也支持自动重连,综合来讲比long polling好用。缺点也很明显,不支持旧的浏览器不说,还只能发送本文信息,而且浏览器对sse还有连接数量的限制(6个)。

web socket

web socket允许客户端和服务端同时向对方发送消息(也就是双工通信),而且不限制信息类型。虽然浏览器同样有连接数量限制(可能是50个),但比sse强得多。理论上最优先使用。

进入正题

开始之前,还需要了解RPC和Hub的概念。

RPC:全程Remote Procedure Call,字面意思远程服务调用,可以像调用本地方法一样调用远程服务。前端可以调用后端方法,后端也可以调用前端方法。

Hub:基于RPC,接受从客户端发过来的消息,也同时负责把服务端的消息发送给客户端。客户端可以调用Hub里面的方法,服务端可以通过Hub调用客户端里面的方法。

好了,概念已经理解清楚了,接下来上代码。

在项目里新增Hub类:

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace SignalRDemo.Server { public class SignalRHub : Hub { /// <summary> /// 客户连接成功时触发 /// </summary> /// <returns></returns> public override async Task OnConnectedAsync() { var cid = Context.ConnectionId;

        </span><span style="color: #008000;">//</span><span style="color: #008000;">根据id获取指定客户端</span>
        <span style="color: #0000ff;">var</span> client =<span style="color: #000000;"> Clients.Client(cid);

        </span><span style="color: #008000;">//</span><span style="color: #008000;">向指定用户发送消息</span>
        <span style="color: #0000ff;">await</span> client.SendAsync(<span style="color: #800000;">"</span><span style="color: #800000;">Self</span><span style="color: #800000;">"</span><span style="color: #000000;">, cid);

        </span><span style="color: #008000;">//</span><span style="color: #008000;">像所有用户发送消息</span>
        <span style="color: #0000ff;">await</span> Clients.All.SendAsync(<span style="color: #800000;">"</span><span style="color: #800000;">AddMsg</span><span style="color: #800000;">"</span>, $<span style="color: #800000;">"</span><span style="color: #800000;">{cid}加入了聊天室</span><span style="color: #800000;">"</span><span style="color: #000000;">);
    }
}

}

为了让外部可以访问,我们还需要一个控制器。在控制器里声明随便建一个:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using SignalRDemo.Server;
using System.Threading.Tasks;

namespace SignalRDemo.Controllers { public class HomeController : Controller { private readonly IHubContext<SignalRHub> _countHub;

    </span><span style="color: #0000ff;">public</span> HomeController(IHubContext&lt;SignalRHub&gt;<span style="color: #000000;"> countHub)
    {
        _countHub </span>=<span style="color: #000000;"> countHub;
    }

    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
    <span style="color: #808080;">///</span><span style="color: #008000;"> 发送信息
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
    <span style="color: #808080;">///</span> <span style="color: #808080;">&lt;param name="msg"&gt;&lt;/param&gt;</span>
    <span style="color: #808080;">///</span> <span style="color: #808080;">&lt;param name="id"&gt;&lt;/param&gt;</span>
    <span style="color: #808080;">///</span> <span style="color: #808080;">&lt;returns&gt;&lt;/returns&gt;</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span> Task Send(<span style="color: #0000ff;">string</span> msg, <span style="color: #0000ff;">string</span><span style="color: #000000;"> id)
    {
        </span><span style="color: #0000ff;">await</span> _countHub.Clients.All.SendAsync(<span style="color: #800000;">"</span><span style="color: #800000;">AddMsg</span><span style="color: #800000;">"</span>, $<span style="color: #800000;">"</span><span style="color: #800000;">{id}:{msg}</span><span style="color: #800000;">"</span><span style="color: #000000;">);
    }
}

}

再然后进入StartUp设置端点:

endpoints.MapHub<SignalRHub>("/hub");

完成以后,配置signalr客户端:

setupConn = () => {
    conn = new signalR.HubConnectionBuilder()
        .withUrl("/hub")
        .build();
conn.on(</span>"AddMsg", (obj) =&gt;<span style="color: #000000;"> {
    $(</span>'#msgPanel').append(`&lt;p&gt;${obj}&lt;/p&gt;`);

});

conn.on(</span>"Finished", () =&gt;<span style="color: #000000;"> {
    conn.stop();
    $(</span>'#msgPanel').text('log out!'<span style="color: #000000;">);
});

conn.on(</span>"Self", (obj) =&gt;<span style="color: #000000;"> {
    $(</span>'#userId'<span style="color: #000000;">).text(obj);
});

conn.start()
    .</span><span style="color: #0000ff;">catch</span>(err =&gt;<span style="color: #000000;"> console.log(err));

}

要注意withUrl里面的路径就是之前设置好的端点。

运行效果:

 

 

 Hub还支持组操作,比如:

//将用户添加到A组
await
Groups.AddToGroupAsync(Context.ConnectionId, "GroupA");
//将用户踢出A组
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "GroupA");
//向A组所有成员广播消息
await Clients.Group("GroupA").SendAsync("AddMsg", "群组消息");

更多操作请参考官方文档。

本文演示demo的源码见git,地址:https://gitee.com/muchengqingxin/SignalRDemo.git

本文转载自博客园,原文链接:https://www.cnblogs.com/muchengqingxin/p/13195229.html

全部评论: 0

    我有话说: