利用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安装就好了,安装后会自动生成一下文件夹。

扫描二维码关注公众号,回复: 4229640 查看本文章

服务端/接口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 版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自www.cnblogs.com/lintaicheng/p/9849922.html