本文介绍在ASP.NET MVC框架中如何使用SignalR进行实时通讯
1.如何在Web中实现实时通讯
实时通讯:
例如“消息提示”、“web聊天室”等。由于web浏览器中使用的是http协议(大部分请求)进行通讯,http被称为是无状态,每次http请求和应答都是通过建立tcp连接,发送数据反馈应答,关闭tcp连接。而且必须是客户端先请求服务器端,服务器端再反馈给客户端消息。并不能实现服务器端主动给客户端推送消息的功能。
实时通讯的传统实现方法:
1.刷新整个页面。这种方法最为原始,页面中设置一个时间触发器,指定时间周期执行页面刷新操作。即隔一定时间向服务器请求一次最新的数据。
2.刷新部分页面。为了避免全部页面的刷新,可通过ajax动态的刷新部分页面。
3.轮询(现阶段较好的实现方式)。通过ajax向服务器询问是否有新的数据,如果有新的数据,则动态的刷新当前页面的指定内容。
SignalR方式:
SignalR是一个实现实时通讯的完整解决方案,包含服务器端和客户端(浏览器端)的全部内容。
它会根据客户端的不同而动态的选择低层实现方案,如果浏览器支持html5,signalr就会选择WebSocket方式;如果不支持,就会选择Comet方式。实际上都是尝试在服务器端和客户端(浏览器端)建立持久连接。
关于SignalR的原理这里就不再敖述,具体内容请参见官方介绍:http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/introduction-to-signalr
实现原理图:
即:用户A在浏览器中的某个操作,使得用户A的浏览器端调用了服务器端的某个函数MyServerFunc(),同时传过来想将消息发送给谁的用户Id。服务器端再根据这个Id号,调用这个Id对应的用户的浏览器端定义个某个函数:myClientFunc()。从而实现了用户A实时发送消息给用户B。
2.如何在ASP.NET MVC框架中使用SignalR
微软的官方网站上介绍了两种搭配方案:
1.ASP.NET MVC4搭配SignalR1.x
2.ASP.NET MVC5搭配SignalR2
(按照asp.net mvc框架和signalr的发布时间顺序,他们应该就是这样的搭配。但是笔者自己在项目开发中先选择了MVC4框架,但是后来在选择signalr时选择了最新版本2.0.3。之前也是由于直接安装的最新版本,后来发现项目中确实需要使用signalr2.x版本,后面会阐述为什么需要使用2.x版本)
所以,本文笔者的项目中的搭配方案为:ASP.NET MVC4 和 SignalR2.0.3
如果读者中也有类似的需求,或者原来使用的是signalr1.x现在需要迁移到signalr2.x的,可以参考微软的一篇文档,介绍如何从signalr1.x迁移到signalr2
如何在项目中加入SignalR:
1.使用Nuget程序包管理工具引入signalr。
Visual Studio2012中依次点击:项目->管理Nuget程序包。搜索signalr,点击安装即可。(默认为最新版本)
或者
命令行方式:工具->库程序包管理器->程序包管理器控制台,输入:install-package Microsoft.AspNet.SignalR
安装完成以后可以再scripts文件夹中看到新增加的文件:jquery.signalR-2.0.3.js等
服务器端:
2.创建自己的hub类(如果想实现signalr的功能,服务器端函数必须实现自己的类,该类继承signalr的hub类)
比如,我新建了一个文件夹叫Hubs,用于存放与signalr操作相关的文件。在hubs文件夹中新建了一个MessageHub.cs类,该类继承Hub类
[csharp] view plaincopy
- public class MessageHub: Hub
- {
- public void NoticeUser()
- {
- Clients.User(UserId).newLog();
- }
- }
完成后,浏览器端一旦连接上MessageHub后,就可以调用服务器端的NoticeUser函数。
3.添加Startup.cs
在项目中新建类文件,命名为:Startup.cs,内容为:
[csharp] view plaincopy
- <span style="font-size:12px;">using Microsoft.AspNet.SignalR;
- using Microsoft.Owin;
- using Owin;
- [assembly: OwinStartup(typeof(Sample.Startup))]
- namespace Sample
- {
- public class Startup
- {
- public void Configuration(IAppBuilder app)
- {
- //使用自己的idprovider,文章后面有介绍,如果不需要可以删除这两行
- var idProvider = new CustomUserIdProvider();
- GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
- // Any connection or hub wire up and configuration should go here
- app.MapSignalR();
- }
- }
- }</span>
浏览器端:
3.添加script引用,添加必要的script代码:
[javascript] view plaincopy
- <span style="font-size:12px;">$(function () {
- //创建与服务器的连接,指定连接到MessageHub这个hub类(注意MessageHub的m小写)
- var chat = $.connection.messageHub;
- //定义浏览器端函数,该函数可通过服务器端的hub类中调用
- chat.client.newLog = function () {
- //some code by yourself
- }
- //启动signalr的与服务器的连接
- $.connection.hub.start();
- })</span>
其中newlog是我自己定义的函数,你可以根据自己的需要定义多个别的函数。
4.如何指定自己的connectionId(往往使用自己系统中原有的成员关系),实现发送给指定用户消息
SignalR的微软官网中介绍了大部分的功能,你可以直接参考:
但是这些介绍中只介绍了如何广播,并没有详细介绍怎么给指定的某个用户发送消息
比如在笔者所参与的项目中,系统原本有一套成员关系表,每个登陆到web项目中的用户,都有自己的用户名,id和密码等信息。
默认情况下,当某个客户端通过SignalR连接到服务器时,SignalR帮我们动态的制定了一个ConnectionId,你可以通过这个ConnectionId去给指定的某个用户发消息。但是这样的效果往往不是我们想要的。我们希望使用系统原有的成员Id作为这个ConnectionId。
比如系统中的用户A,希望发送一则消息给用户B,那么在使用SignalR的:Clients.User(userId).send(message);
但是这里的userId希望使用系统自己的用户成员关系
因此可以自定义IUserIdProvider类,比如我的系统中:
[csharp] view plaincopy
- public class CustomUserIdProvider : IUserIdProvider
- {
- public string GetUserId(IRequest request)
- {
- var userId = WebSecurity.CurrentUserId;
- return userId.ToString();
- }
- }
5.直接从服务器端调用某用户前端函数
正如上面所讲的那样,普通的通讯模式为:
1)客户端A在页面中触发某一事件(比如点击某个button)
2)在button.click的事件处理函数中调用了SignalR的某一函数,比如上面讲到的noticeUser函数
3)该noticeUser函数为服务器端方法,所以客户A的前端会通过SignalR调用服务器端的noticeUser方法
4)服务器会根据客户端A调用newlog时传递的若干参数(其中通常包括一个id号,即客户端A想要发送消息给哪个用户,比如客户端B。当然,如果你可以在服务器中通过其他业务查询的到,也可不必传递这个id参数),通过SignalR调用指定Id号的客户端的某个函数,比如上面提到的newlog方法
有时候,由于系统的业务需求,可能需要直接在服务器端调用noticeUser函数,或者直接调用客户端B的newlog函数。比如客户端A提交某个form表单后,服务器端处理完成form表单的数据,存入数据库,然后想通知客户端B,有新消息。此时,不希望直接在客户端A的前段调用noticeUser,然后在经过服务器调用newlog。而希望直接在服务器调用newlog。
其实很简单,你只需要在你需要调用的地方添加如下代码即可:
[csharp] view plaincopy
- //call client method on server but not in hub class
- IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
- _hubContext.Clients.User(docUserId.ToString()).newLog();
注:
笔者也是由于项目需要,刚接触SignalR不久,对内部机理并不是很熟悉。在使用的过程中遇到了不少麻烦,因此写出这篇文章希望对初用者有帮助。如有问题请留言交流。
本文为原创,如需转载请注明。谢谢!