利用SignalR实现实时聊天
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 版权声明:本文为博主原创文章,转载请附上博文链接!
利用SignalR实现实时聊天的更多相关文章
- SignalR实现网页实时聊天功能
SignalR是利用html5 sokit方式实现网页的实时性,在客户端不支持html5的情况下通过轮询实现 实现原理是客户端发送的消息先去服务器,然后服务器根据需要将消息广播到需要接收信息的客户群. ...
- SignalR入门一、通过 SignalR 2 进行实时聊天
一:什么是signalR Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,signalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务 ...
- 使用SignalR+Asp.net创建实时聊天应用程序
一.概述: 使用 ASP.NET 那么 SignalR 2 创建一个实时聊天应用程序.将 SignalR 添加 MVC 5 应用程序中,并创建聊天视图发送并显示消息. 在Demo中,将学习Signal ...
- 使用signalr实现网页和微信公众号实时聊天(上)
最近项目中需要实现客户在公众号中和客服(客服使用后台网站系统)进行实时聊天的功能.折腾了一段时间,实现了这个功能.现在将过程记录下,以便有相同需求的同行可以参考,也是自己做个总结.这篇是上,用手机编辑 ...
- SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 SignalR 简单示例 通过三个DEMO学会SignalR的三种实现方式 SignalR推送框架两个项目永久连接通讯使用 SignalR 集线器简单实例2 用SignalR创建实时永久长连接异步网络应用程序
SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 异常汇总:http://www ...
- Asp.net SignalR 让实时通讯变得如此简单
巡更项目中,需要发送实时消息,以及需要任务开始提醒,于是便有机会接触到SignalR,在使用过程中,发现用SignalR实现通信非常简单,下面我思明将从三个方面分享一下: 一.SignalR是什么 A ...
- 网页实时聊天之PHP实现websocket
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- MVC5中使用SignalR2.0实现实时聊天室
原文 MVC5中使用SignalR2.0实现实时聊天室 有时候需要浏览器和服务端保持实时的通讯(比如在线聊天),SignalR的出现让这一切变得非常简单.它能够让服务端向客户端实时的推送消息.如果用户 ...
- vue+websocket+express+mongodb实战项目(实时聊天)(二)
原项目地址:[ vue+websocket+express+mongodb实战项目(实时聊天)(一)][http://blog.csdn.net/blueblueskyhua/article/deta ...
随机推荐
- VUE之环境安装
一.环境安装 软件安装: nodejs https://nodejs.org/en/ vscode https://code.visualstudio.com/docs/?dv=win python- ...
- How to install your SSL Certificate to your Windows Server
Installation: Open the ZIP file containing your certificate. Save the file named your_domain_name.ce ...
- C-晾衣服
链接:https://ac.nowcoder.com/acm/contest/892/C 题意: 鸡尾酒从杭州回来,囤积了许多衣服,洗好之后,他发现晾衣服是一件麻烦的事. 晾衣绳的长度只有L,而鸡尾酒 ...
- Codeforces 163C(实数环上的差分计数)
要点 都在注释里了 #include <cstdio> #include <cstring> #include <iostream> #include <al ...
- Kestrel服务器
Kestrel服务器 什么是Kestrel服务器 Kestrel是开源的(GitHub提供的源代码),事件驱动的异步I / O服务器,用于在任何平台上托管ASP.NET应用程序.这是一个监听服务器和一 ...
- 使用docker save load 的时候的一个小问题
当你使用docker save image_id > aa.tar ; 然后再使用 docker load < aa.tar 时, 你会发现此时导入的镜像的repository和 tag ...
- 算法导论课后习题解答 第一部分 练习1.1-1->1.1-5
很高兴能和大家一起共同学习算法导论这本书.笔者将在业余时间把算法导论后面的题解以博文的形式展现出来希望能得到大家的支持谢谢.如果有可能我会做一些教学视频免费的供大家观看. 练习题选自算法导论中文第三版 ...
- IIS访问网站出错[要求输入用户名密码]的解决方案
症状: 1.HTTP 500 - 内部服务器错误 2.您不具备使用所提供的凭据查看该目录或页的权限 3.基于所提供的凭据,您没有权限查看此目录或网页.HTTP 错误 401.3 - 访问被资源 ACL ...
- win10红警黑屏和无法打开的处理
原因:win10或者win7无法打红警的原因,除开软件本身坏了等情况,多半是因为显示比率不对不上和系统不兼容导致的处理方法是: 1.将快捷方式发送到桌面(只是为了方便打开,当然你也可以不发送到桌面,关 ...
- windows 用VMware创建linux虚拟机,安装操作系统CentOS7.2
1.按照向导创建虚拟机 以下是安装虚拟机的步骤,没有写的直接下一步 [1]主页-创建新虚拟机 [2]选择 自定义(高级) [3]选择稍后安装操作系统 [4]给虚拟机命名并指定所在位置 [5]给处理器配 ...