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实现实时聊天的更多相关文章

  1. SignalR实现网页实时聊天功能

    SignalR是利用html5 sokit方式实现网页的实时性,在客户端不支持html5的情况下通过轮询实现 实现原理是客户端发送的消息先去服务器,然后服务器根据需要将消息广播到需要接收信息的客户群. ...

  2. SignalR入门一、通过 SignalR 2 进行实时聊天

    一:什么是signalR Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,signalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务 ...

  3. 使用SignalR+Asp.net创建实时聊天应用程序

    一.概述: 使用 ASP.NET 那么 SignalR 2 创建一个实时聊天应用程序.将 SignalR 添加 MVC 5 应用程序中,并创建聊天视图发送并显示消息. 在Demo中,将学习Signal ...

  4. 使用signalr实现网页和微信公众号实时聊天(上)

    最近项目中需要实现客户在公众号中和客服(客服使用后台网站系统)进行实时聊天的功能.折腾了一段时间,实现了这个功能.现在将过程记录下,以便有相同需求的同行可以参考,也是自己做个总结.这篇是上,用手机编辑 ...

  5. 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 ...

  6. Asp.net SignalR 让实时通讯变得如此简单

    巡更项目中,需要发送实时消息,以及需要任务开始提醒,于是便有机会接触到SignalR,在使用过程中,发现用SignalR实现通信非常简单,下面我思明将从三个方面分享一下: 一.SignalR是什么 A ...

  7. 网页实时聊天之PHP实现websocket

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  8. MVC5中使用SignalR2.0实现实时聊天室

    原文 MVC5中使用SignalR2.0实现实时聊天室 有时候需要浏览器和服务端保持实时的通讯(比如在线聊天),SignalR的出现让这一切变得非常简单.它能够让服务端向客户端实时的推送消息.如果用户 ...

  9. vue+websocket+express+mongodb实战项目(实时聊天)(二)

    原项目地址:[ vue+websocket+express+mongodb实战项目(实时聊天)(一)][http://blog.csdn.net/blueblueskyhua/article/deta ...

随机推荐

  1. java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment

    最近项目用到了java程序动态编译java源文件,运行程序一直报错,提示错误如下: Can't initialize javac processor due to (most likely) a cl ...

  2. C# 操作 Excel 文件(.xls 或 .xlsx)

    在.net中,常用的操作excel文件的方式,有三种: OLE DB的形式, 第三方框架NPOI, Office组件. 总结: 通过对比,在读取大数据量的excel文件,建议用OLE DB的形式,把e ...

  3. mac mysql 编码配置

    mac mysql 编码配置 (mysql目录下没有my.cnf) 想要修改编码发现自己的/usr/local/mysql/support-files里面根本没有my.cnf 安装方式是去mysql官 ...

  4. 长春理工大学第十四届程序设计竞赛(重现赛)I.Fate Grand Order

    链接:https://ac.nowcoder.com/acm/contest/912/I 题意: Fate Grand Order是型月社发行的角色扮演类手机游戏,是著名的氪金抽卡"垃圾&q ...

  5. bzoj3159决战 码农题 树剖套splay

    最近沉迷码农题无法自拔 首先有一个暴力的想法:对于每个重链维护一个splay,需要翻转的连起来,翻转,接回去 然后发现这样没问题... 一条链只能跨log个重链,也就只有log个splay的子树参与重 ...

  6. hdu5709Claris Loves Painting主席树 奇妙的DFS序

    先不考虑层数限制 一棵树上每个点有个颜色,问一棵子树的颜色数 感觉简单多了是吧 考虑每个点的贡献:自己到根的路径上的一个包含自己的连续段 观察最顶端的点的父亲: 它满足有了额外的同色孩子(咦) 这一条 ...

  7. ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”

    ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面” DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求 ...

  8. node.js安装Oracledb指导文档

    https://community.oracle.com/docs/DOC-931127

  9. webpack.config.js====插件html-webpack-plugin

    1. 安装 cnpm install html-webpack-plugin --save-dev 2. webpack.config.js中使用 const htmlWebpackPlugin = ...

  10. 解决resteasy上传表单文件名乱码

    Dubbo在2.6版本后合并了dubbox的resteasy代码后,可以支持rest风格的接口发布,但是在使用form表单上传文件的时候,获取的文件名称是乱码. 下面通过对源码分析一下原因,并提供一种 ...