2018/10/10:博主第一次写原创博文而且还是关于C#的(博主是从前端转过来的),菜鸟一枚,如果有什么写的不对,理解错误,还望各位轻喷。,从SignalR开始!
首先先介绍一下关于SignalR的一些基本概念,
ASP.NET SignalR是为简化开发开发人员将实时web内容添加到应用程序过程而提供的类库。实时web功能指的是让服务器代码可以随时主动推送内容给客户端,而不是让服务器等待客户端的请求(才返回内容)。 所有"实时"种类的web功能都可以使用SignalR来添加到你的ASP.NET应用程序中。最常用的例子有聊天室,但我们能做的比这要多得多。考虑以下情况:用户需要不停的刷新网页来看最新的数据;或者在页面上通过实现长轮询来检索新数据(并显示),那你就可以考虑使用SignalR来实现了。比如:仪表板及监视型应用程序;协作型应用程序(如多人同时对文档进行编辑);作业进度更新及实时呈现表单等。 SignalR也适合新型的,需要从服务器上进行高频率更新的web应用程序,例如实时游戏。这里有一个好例子:ShoorR。 SignalR提供了一个简单的API用户创建服务器到客户端的远程过程调用(RPC),可以方便地从服务器端的.Net代码中对客户端浏览器及其他客户端平台中的的JS函数进行调用。SignalR还包括了用于管理连接(例如:连接和断开事件)及连接分组。
SignalR可以自动对连接进行管理。并让你发送广播消息到所有已连接的客户端上,就像一个聊天室一样。当然除了群发外,你也可以发送到消息到特定的客户端。客户端和服务器的连接是持久的,不像传统的每次通信都需要重新建立连接的HTTP协议。 SignalR支持“服务器推送”功能,即服务器代码可以通过使用远程过程调用(RPC)来调用浏览器中的客户端代码,而不是当前在web上常用的请求-相应处理模型。 SignalR的应用可以使用服务总线,SQL SERVER或者Redis来扩展到数以千计的客户端上。 SignalR是开源的,可以通过GitHub访问。
2、有人可能会问SignalR和WebSocket有什么区别
ignalR使用WebSocket传输方式——在可能的情况下。并且会自动切换到旧的传输方式(如HTTP长连接)。你当然可以直接使用WebSocket来编写你的应用程序,但使用SignalR意味着你将有更多的额外功能而无需重新发明轮子。最重要的是,你可以将注意力关注在业务实现上,而无需考虑为旧的客户端单独创建兼容代码。SignalR还能够使你不必担心WebSocket更新,因为SignalR将会持续更新以支持变化的底层传输方式,跨不同版本的WebSocket来为应用程序提供一个一致的访问接口。
当然,你可以创建只使用WebSocket传输的解决方案,SignalR提供了你可能需要自行编写代码的所有功能,比如回退到其他传输方式及针对更新的WebSocket实现来修改你的应用程序。博主的理解中就是SignalR是微软为了简化开发开发人员工作而造出的轮子,他不仅拥有WebSocket的功能,而且还有传统的HTTP长连接的方式。
接下来就是安装SignalR,SignalR在nuget上可以下载安装(SignalR要求.net 4.5的框架),在vs的工具里能找到nuget管理:搜Microsoft.AspNet.SignalR安装就好了,安装后会自动生成一下文件夹。
服务端/接口Api端代码
-
从Nuget上搜索SignalR并引入
-
创建的脚本拷贝到客户端Lib中到时使用requirejs引用,并删除服务端生成的脚本
-
在Startup.cs中注册SignalR中间件
1、注册视频聊天中间件:LiveVideoChat(app); 其中"/LiveVideoChat"是前端脚本要调用的地址2、中间件需要授权登录的情况在,需要配置【QueryStringOAuthBearerProvider】,以及在集线器(LiveVideoChat)标注【Authorize】 using System;using System.Threading.Tasks;using Ilikexx.Framework;using Microsoft.AspNet.SignalR;using Microsoft.Owin;using Microsoft.Owin.Cors;using Microsoft.Owin.Security.OAuth;using Owin;[assembly: OwinStartup(typeof(Brand.Api.Startup))]namespace Brand.Api{ public partial class Startup { private readonly ILog logger = LogManager.GetLogger(typeof(Startup)); /// <summary> /// /// </summary> /// <param name="app"></param> public void Configuration(IAppBuilder app) { logger.Info("Startup开始运行"); ConfigureAuth(app); LogManager.Flush(); LiveVideoChat(app); } /// <summary> /// 注册视频聊天模块 /// </summary> /// <param name="app"></param> private void LiveVideoChat(IAppBuilder app) { //LiveVideoChat 前端脚本要调用的地址 app.Map("/LiveVideoChat", map => { map.UseCors(CorsOptions.AllowAll); map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions() { Provider = new QueryStringOAuthBearerProvider() }); var hubConfiguration = new HubConfiguration { Resolver = GlobalHost.DependencyResolver, EnableJavaScriptProxies = true }; map.RunSignalR(hubConfiguration); }); } }}/// <summary>/// 验证token/// </summary>public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider{ /// <summary> /// 从请求地址中获取token /// </summary> /// <param name="context"></param> /// <returns></returns> public override Task RequestToken(OAuthRequestTokenContext context) { var value = context.Request.Query.Get("access_token"); if (!string.IsNullOrEmpty(value)) { context.Token = value; } return Task.FromResult<object>(null); } /// <summary> /// 验证token的有效性 /// </summary> /// <param name="context"></param> /// <returns></returns> public override Task ValidateIdentity(OAuthValidateIdentityContext context) { return base.ValidateIdentity(context); }}-
LiveVideoChat:聊天中间件(核心)
1、类标注Authorize标记是否需要token访问2、类标注HubName给集线器起名,客户端在调用的时候会用到3、方法标注HubMethodName供客户端脚本调用4、熟悉分组广播,全部广播,单一广播 using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.Linq;using System.Security.Claims;using System.Threading;using System.Threading.Tasks;using System.Web;using Brand.Model;using Brand.Service;using Ilikexx.Framework;using Ilikexx.Framework.Web.Mvc;using Microsoft.AspNet.SignalR;using Microsoft.AspNet.SignalR.Hubs;using Microsoft.AspNet.SignalR.Infrastructure;using Newtonsoft.Json.Linq;namespace Brand.Api.Controllers.SignalR{ /// <summary> /// 视频聊天模块 /// </summary> [HubName("LiveVideoChatService")] [Authorize] public class LiveVideoChat : BaseHub { /// <summary> /// 在线用户类,按各个房间存储 /// </summary> public static ConcurrentDictionary<string, List<Dictionary<string, User>>> OnLineUsers = new ConcurrentDictionary<string, List<Dictionary<string, User>>>(); private UserService userService; /// <summary> /// /// </summary> public LiveVideoChat() { userService = new UserService(); } /// <summary> /// 获取调用者的用户信息 /// </summary> public JObject CallerUserInfo { get { JObject data = new JObject(); User user = null; List<Dictionary<string, User>> listUser; OnLineUsers.TryGetValue(RoomId, out listUser); if (listUser != null) { Dictionary<string, User> mapUser = listUser.Find(x => { return x.ContainsKey(Context.ConnectionId); }); if (mapUser != null) { mapUser.TryGetValue(Context.ConnectionId, out user); data.Add("Id", user.Id); data.Add("NickName", user.NickName); data.Add("HeadUrl", user.HeadUrl); } } return data; } } /// <summary> /// 重写连接 /// </summary> /// <returns></returns> public override Task OnConnected() { Connected(); return base.OnConnected(); } /// <summary> /// 重新链接 /// </summary> /// <returns></returns> public override Task OnReconnected() { Connected(); return base.OnReconnected(); } /// <summary> /// 重写断开连接 /// </summary> /// <param name="stopCalled"></param> /// <returns></returns> public override Task OnDisconnected(bool stopCalled) { User user = null; List<Dictionary<string, User>> listUser; OnLineUsers.TryGetValue(RoomId, out listUser); if (listUser != null) { Dictionary<string, User> mapUser = listUser.Find(x => { return x.ContainsKey(Context.ConnectionId); }); if (mapUser != null) { mapUser.TryGetValue(Context.ConnectionId, out user); listUser.Remove(mapUser); OnLineUsers.TryAdd(RoomId, listUser); } } //当前离线移除房间 Groups.Remove(Context.ConnectionId, RoomId); SendToGroup(0); return base.OnDisconnected(stopCalled); } /// <summary> /// 连接上的处理 /// </summary> private void Connected() { //房间号 long Id = cvt.ToLong(RoomId); User user = userService.GetModel(UserId); List<Dictionary<string, User>> listUser; OnLineUsers.TryGetValue(RoomId, out listUser); if (listUser == null) { listUser = new List<Dictionary<string, User>>(); Dictionary<string, User> mapUser = new Dictionary<string, User>(); mapUser.Add(Context.ConnectionId, user); listUser.Add(mapUser); OnLineUsers.TryAdd(RoomId, listUser); Groups.Add(Context.ConnectionId, RoomId); SendToGroup(1); } else { Dictionary<string, User> mapUser = listUser.Find(x => { return x.ContainsKey(Context.ConnectionId); }); //不等于null 说明是重新连接的(重新连接不等于要退出后才连接) if (mapUser == null) { mapUser = new Dictionary<string, User>(); mapUser.Add(Context.ConnectionId, user); listUser.Add(mapUser); OnLineUsers.TryAdd(RoomId, listUser); Groups.Add(Context.ConnectionId, RoomId); SendToGroup(1); } } } /// <summary> /// 给房间内的所有用户发消息 /// </summary> /// <param name="JoinOrExit">0=退出 1=加入</param> private void SendToGroup(int JoinOrExit) { int totalCount = 0; List<Dictionary<string, User>> listUser; OnLineUsers.TryGetValue(RoomId, out listUser); if (listUser != null) { totalCount = listUser.Count; } resultMessage.data = new { TotalCount = totalCount, User = CallerUserInfo }; resultMessage.ret = 0; #region 特别注意不用 Clients.Group(RoomId) 可能是刚连接(连接后就可以使用)的时候,调用者加入到组会有延迟,所以分二条发送 if (JoinOrExit == 1) { //给调用者(登录或退出者)发一条 Clients.Caller.JoinRoom(resultMessage); //所在房间,要排除调用者 Clients.Group(RoomId, Context.ConnectionId).JoinRoom(resultMessage); } else { //给调用者(登录或退出者)发一条 Clients.Caller.ExitRoom(resultMessage); //所在房间,要排除调用者 Clients.Group(RoomId, Context.ConnectionId).ExitRoom(resultMessage); } #endregion } #region 提供给JS事件调用的方法 /// <summary> /// 发送【文本】消息给当前房间 /// </summary> /// <param name="message"></param> [HubMethodName("SendMsgText")] public void SendMsgText(string message) { resultMessage.ret = 0; resultMessage.data = new { Content = message, User = CallerUserInfo }; Clients.Group(RoomId, Context.ConnectionId).ReceiveMsgText(resultMessage); } /// <summary> /// 发送【图片】消息给当前房间 /// </summary> /// <param name="message"></param> [HubMethodName("SendMsgImg")] public void SendMsgImg(string message) { resultMessage.ret = 0; resultMessage.data = new { Content = message, User = CallerUserInfo }; Clients.Group(RoomId, Context.ConnectionId).ReceiveMsgImg(resultMessage); } #endregion }}-
BaseHub:继承 Hub,封装常用方法和属性
using Ilikexx.Framework;using Ilikexx.Framework.Web.Mvc;using Microsoft.AspNet.SignalR;using Newtonsoft.Json;using Newtonsoft.Json.Linq;using System;using System.Collections.Generic;using System.Linq;using System.Net.Http;using System.Security.Claims;using System.Text;using System.Web;using System.Web.Http;using System.Web.Mvc;namespace Brand.Api.Controllers{ /// <summary> /// /// </summary> public partial class BaseHub : Hub { /// <summary> /// /// </summary> public ResultMessage resultMessage; /// <summary> /// /// </summary> public BaseHub() { resultMessage = new ResultMessage(); } /// <summary> /// 获取房间号 /// </summary> public string RoomId { get { return Context.QueryString["Id"]; } } /// <summary> /// 转为JSON串 /// </summary> /// <param name="obj"></param> /// <returns></returns> protected string toJsonString(object obj) { return JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.None); } /// <summary> /// 获取所请求的用户ID /// </summary> protected long UserId { get { var identity = System.Web.HttpContext.Current.User.Identity as ClaimsIdentity; //因为保存的是userid,当然也可以用其他方式 return long.Parse(identity.Name); } } }}WEB端代码
客户端/移动端脚本
-
使用Requirejs引入Signalr脚本
require(["jquery", "ui", "lib/jquery.signalR-2.2.2"], function ($, ui) {});-
开始使用:
1、$.hubConnection创建集线器连接,地址LiveVideoChat要跟服务端的一致,若有参数可以在qs中添加(token,房间号等)2、createHubProxy创建服务代理LiveVideoChatService也要与服务端集线器类起名(服务名)一致3、接收服务器方法,其中JoinRoom为服务端委托调用名称,data为回调的数据(目前都统一跟接口返回格式一致为JSON串,{ret:0,data:{},msg:""}) self.connHusService .on("JoinRoom", function (data) { });4、调用服务器方法,其中SendMsgText为服务端的方法, $msg.val()为发送的数据。要注意若有多个参数要与服务端一致 self.connHusService.invoke("SendMsgText", $msg.val())
.done(function () { var txtResponse = $("#txtResponse") txtResponse.append("OKOK"); }) .fail(function (e) { alert(e); }); /**************************************************************************************************** 注释:脚本模板示例****************************************************************************************************/require(["jquery", "ui", "lib/jquery.signalR-2.2.2"], function ($, ui) { var self = { $wrap: $("body"), tpl: '<div id="txtResponse"></div>\ <div style="position:absolute;bottom:50px;width:100%"><input type="text" name="msg" style="width:80%;border:1px solid"/><a href="javasrcipt:void(0);" class="js_send" style="background:green;color:white;padding:10px">发送</a></div>', init: function () { /// <summary> /// 初始化 /// </summary> var size = ui.utils.getViewPort(); self.$wrap.append(ui.render(self.tpl, { play_width: size.width, play_height: size.width / 4 * 3 })); self.chatInit(); //绑定事件 self.bind(); }, bind: function () { self.$wrap.on("click", ".js_send", function () { var $msg = self.$wrap.find("[name=msg]"); //客户端代理调用服务端方法 self.connHusService.invoke("SendMsgText", $msg.val()) .done(function () { var txtResponse = $("#txtResponse") txtResponse.append("OKOK"); }) .fail(function (e) { alert(e); }); }); }, chatInit: function () { //创建集线器连接 self.connHus = $.hubConnection(Ilikexx.apiUrl + "LiveVideoChat"); //添加请求参数 self.connHus.qs = { access_token: Ilikexx.token, Id: 100 } //开启日志记录 //self.connHus.logging = true; // 获取代理 self.connHusService = self.connHus.createHubProxy("LiveVideoChatService"); // 设置state的值 // self.connHusService.state.ClientType = "HubNonAutoProxy"; // 客户端监听服务端发送的方法 self.connHusService .on("JoinRoom", function (data) { console.log(data) var txtResponse = $("#txtResponse") txtResponse.append(ui.render("<div>{{User.NickName}}进入了房间,总人数:{{TotalCount}}</div>", data.data)); }) .on("ExitRoom", function (data) { console.log(data) var txtResponse = $("#txtResponse") txtResponse.append(ui.render("<div>{{User.NickName}}退出了房间,总人数:{{TotalCount}}</div>", data.data)); }) .on("ReceiveMsgText", function (data) { console.log(data) var txtResponse = $("#txtResponse") txtResponse.append(ui.render('<div><img src="{{User.HeadUrl}}" />{{User.NickName}},说:{{Content}}</div>', data.data)); }) ; self.connHus.disconnected(function (e, conn) { console.log(conn) console.log('Wdisconnected。。。。。'); //几秒进行重连 // 开启连接 self.connHus.start() .done(function () { console.log("Hus已连接服务器OK"); }) .fail(function () { console.log("Hus已连接服务器失败"); }); }); // 开启连接 self.connHus.start() .done(function () { console.log("Hus已连接服务器OK"); }) .fail(function () { console.log("Hus已连接服务器失败"); }); }, render: function () { /// <summary> /// 渲染数据 /// </summary> } }; self.init();});
--------------------- 作者:qq_964878912 来源:CSDN 原文:https://blog.csdn.net/qq_18798917/article/details/53897586 版权声明:本文为博主原创文章,转载请附上博文链接!
浙公网安备 33010602011771号