zoukankan      html  css  js  c++  java
  • Asp.net SignalR 应用并实现群聊功能 开源代码

    ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程。实时 Web 功能是指这样一种功能:当所连接的客户端变得可用时服务器代码可以立即向其推送内容,而不是让服务器等待客户端请求新的数据。(来自官方介绍。)

    SignalR官网

     -1、写这篇的原因

    在上篇文章B/S(Web)实时通讯解决方案中,并没有详情介绍SignalR,所以另起一篇专门介绍SignalR,本文的侧重点是Hub功能。

    0、先看最终实现效果

    github:https://github.com/Emrys5/SignalRGroupChatDemo

    在线演示:http://chat.lining.name/

    1、准备工作

    1.1、在NuGet上首先下载SignalR的包。

    1.2、配置Owin与SignalR

    1.2.1、新建Startup类,注册SignalR

    1 public class Startup
    2     {
    3         public void Configuration(IAppBuilder app)
    4         {
    5             app.MapSignalR();
    6         }
    7     }

    然后在web.config配置Startup类,在configuration=>appSettings节点中添加

    <add key="owin:AppStartup" value="SignalRChat.App_Start.Startup"/>

    1.2.2、在页面引入SignalR的js

    1、由于SignalR前端是基于jQuery的,所以页面需引入jQuery。

    2、引入SignalR的js 。

    3、引入最重要的hubs js,这个js其实并不存在,SignalR会反射获取所有供客户端调用的方法放入hubs js中。

    <script src="~/Scripts/jquery-1.10.2.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script> 
    <script src="~/signalr/hubs"></script>

    1.2.3、新建GroupChatHub类,并继承Hub抽象类

    在hub类中的方法就是提供给客户端调用的js方法。

    在js中就可以用signalr调用SendMsg。

    [HubName("simpleHub")]
        public class SimpleHub : Hub
        { 
            public void SendMsg(string msg)
            {
                 
            }
    
        }

     这样基本上前期准备工作就做完了,后面就是具体的操作。

    2、原理与简单的编程

    其实原理如果简单点理解就很简单,因为http是无状态的,所以每次请求以后都会与服务器断开链接,那就是说客户端可以很容易找到服务器,但是服务器如果想给你客户端发送消息就比较麻烦,如果不明白的可以参考上一篇文章 B/S(Web)实时通讯解决方案

    SignalR就很好的解决了这个问题,也就说实现了实现了浏览器与服务器的全双工通信。

    2.1、客户端至服务端(B=>S)

    客户端代码

    <script  type="text/javascript">  
        var ticker = $.connection.simpleHub;
        $.connection.hub.start();
    
        $("#btn").click(function () {
    
            // 链接完成以后,可以发送消息至服务端
            ticker.server.sendMsg("需要发送的消息");
        });
        
    </script>

    服务端代码

      [HubName("simpleHub")]
        public class SimpleHub : Hub
        {
            public void SendMsg(string msg)
            {
                // 获取链接id
                var connectionId = Context.ConnectionId; 
             // 获取cookie
                var cookie = Context.RequestCookies;
    
            }
    
        }

    其中SimpleHub就是我们定义的继承HubSimpleHub,然后我们可以用特性HubName进行重命名。

    然后开始链接。

    在链接完成以后,我们就可以调用在SimpleHub类中调用的方法。这就就很简单的实现了客户端至服务端发送消息。

    我们还可以在Context中获取我们想要的东西,比如链接id,cookie等。

    2.2、服务端至客户端(S=>B)

    服务端代码

     [HubName("simpleHub")]
        public class SimpleHub : Hub
        {
            public void SendMsg(string msg)
            {
                Clients.All.msg("发送给客户端的消息"); 
            }
    
        }

    客户端代码

    <script type="text/javascript">
    
        var ticker = $.connection.groupChatHub;
        $.connection.hub.start();
    
        ticker.client.msg = function (data) {
            console.log(data);
        } 
    </script>

    这里演示了怎么发送消息至客户端,也是SignalR比较重要的功能,这里有两个问题需要解决。

    问题一、这里是发送消息给所有连着的客户端,如果是单个客户端或者是一批客户端应该怎么发送。

    问题二、我们在调用msg给个客户端发送消息时是在接收消息以后做的反馈,然后发送消息给客户端,这样就很类似ajax了,服务端并没有主动给客户端发送消息。

    解决:

    问题一、Clients可以给特性的一群或者一个客户端发送消息

           // 所有人
                Clients.All.msg("发送给客户端的消息");  
    
                // 特定 cooectionId
                Clients.Client("connectionId").msg("发送给客户端的消息");
    
                // 特定 group
                Clients.Group("groupName").msg("发送给客户端的消息");

    这是比较常用的三个,当然还有很多,比如AllExcept,Clients。

    在SignalR2.0中还添加了Others,OthersInGroup,OthersInGroups等等。

    问题二、我们可以在需要发送消息的地方调用GlobalHost.ConnectionManager.GetHubContext<SimpleHub>().Clients中获取Clients。获取Clients并发送消息我们最好写成单例模式,因为这种需求很符合单例,群聊中有详细的代码。

    3、SignalR实现群聊

    以上的介绍和代码已经可以实现b=>s和s=>b了,那实现群聊和单独聊天就比较简单了。

    由于功能比较简单,所有我把用户名存到了cookie里,也就说第一次进来时需要设置cookie。

    还有就是在hub中要实现OnConnectedOnDisconnectedOnReconnected,然后在方法中设置用户和connectionid和统计在线用户,以便聊天使用。

    hub代码

    /// <summary>
        /// SignalR Hub 群聊类
        /// </summary>
        [HubName("groupChatHub")] // 标记名称供js调用
        public class GroupChatHub : Hub
        {
            /// <summary>
            /// 用户名
            /// </summary>
            private string UserName
            {
                get
                {
                    var userName = Context.RequestCookies["USERNAME"];
                    return userName == null ? "" : HttpUtility.UrlDecode(userName.Value);
                }
            }
    
            /// <summary>
            /// 在线用户
            /// </summary>
            private static Dictionary<string, int> _onlineUser = new Dictionary<string, int>();
    
            /// <summary>
            /// 开始连接
            /// </summary>
            /// <returns></returns>
            public override Task OnConnected()
            {
                Connected();
                return base.OnConnected();
            }
    
    
            /// <summary>
            /// 重新链接
            /// </summary>
            /// <returns></returns>
            public override Task OnReconnected()
            {
                Connected();
                return base.OnReconnected();
            }
    
    
    
            private void Connected()
            {
                // 处理在线人员
                if (!_onlineUser.ContainsKey(UserName)) // 如果名称不存在,则是新用户
                {
    
                    // 加入在线人员
                    _onlineUser.Add(UserName, 1);
    
                    // 向客户端发送在线人员
                    Clients.All.publshUser(_onlineUser.Select(i => i.Key));
    
                    // 向客户端发送加入聊天消息
                    Clients.All.publshMsg(FormatMsg("系统消息", UserName + "加入聊天"));
                }
                else
                {
                    // 如果是已经存在的用户,则把在线链接的个数+1
                    _onlineUser[UserName] = _onlineUser[UserName] + 1;
                }
    
                // 加入Hub Group,为了发送单独消息
                Groups.Add(Context.ConnectionId, "GROUP-" + UserName);
            }
    
    
    
            /// <summary>
            /// 结束连接
            /// </summary>
            /// <param name="stopCalled"></param>
            /// <returns></returns>
            public override Task OnDisconnected(bool stopCalled)
            {
                // 人员链接数-1
                _onlineUser[UserName] = _onlineUser[UserName] - 1;
    
                // 判断是否断开了所有的链接
                if (_onlineUser[UserName] == 0)
                {
                    // 移除在线人员
                    _onlineUser.Remove(UserName);
    
                    // 向客户端发送在线人员
                    Clients.All.publshUser(_onlineUser.Select(i => i.Key));
    
                    // 向客户端发送退出聊天消息
                    Clients.All.publshMsg(FormatMsg("系统消息", UserName + "退出聊天"));
                }
    
                // 移除Hub Group
                Groups.Remove(Context.ConnectionId, "GROUP-" + UserName);
                return base.OnDisconnected(stopCalled);
            }
    
            /// <summary>
            /// 发送消息,供客户端调用
            /// </summary>
            /// <param name="user">用户名,如果为0,则是发送给所有人</param>
            /// <param name="msg">消息</param>
            public void SendMsg(string user, string msg)
            {
                if (user == "0")
                {
                    // 发送给所有用户消息
                    Clients.All.publshMsg(FormatMsg(UserName, msg));
                }
                else
                {
                    //// 发送给自己消息
                    //Clients.Group("GROUP-" + UserName).publshMsg(FormatMsg(UserName, msg));
    
                    //// 发送给选择的人员
                    //Clients.Group("GROUP-" + user).publshMsg(FormatMsg(UserName, msg));
    
    
                    // 发送给自己消息
                    Clients.Groups(new List<string> { "GROUP-" + UserName, "GROUP-" + user }).publshMsg(FormatMsg(UserName, msg));
    
                }
            }
    
    
            /// <summary>
            /// 格式化发送的消息
            /// </summary>
            /// <param name="name"></param>
            /// <param name="msg"></param>
            /// <returns></returns>
            private dynamic FormatMsg(string name, string msg)
            {
                return new { Name = name, Msg = HttpUtility.HtmlEncode(msg), Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") };
            }
        }

    js代码

    <script type="text/javascript">
            $(function () {
    
                // 链接hub
                var ticker = $.connection.groupChatHub;
                $.connection.hub.start();
    
                // 接收服务端发送的消息
                $.extend(ticker.client, {
    
                    // 接收聊天消息
                    publshMsg: function (data) {
                        $("#msg").append("<li><span class='p'>" + data.Name + ":</span>" + data.Msg + " <span class='time'>" + data.Time + "</span></li>")
                        $("#msg").parents("div")[0].scrollTop = $("#msg").parents("div")[0].scrollHeight;
                    },
    
                    // 接收在线人员,然后加入Select,以供单独聊天选中
                    publshUser: function (data) {
                        $("#count").text(data.length);
                        $("#users").empty();
                        $("#users").append('<option value="0">所有人</option>');
                        for (var i = 0; i < data.length; i++) {
                            $("#users").append('<option value="' + data[i] + '">' + data[i] + '</option>')
                        }
    
                    }
                });
    
                // 发送消息按钮
                $("#btn-send").click(function () {
                    var msg = $("#txt-msg").val();
                    if (!msg) {
                        alert('请输入内容!'); return false;
                    }
                    $("#txt-msg").val('');
    
                    // 主动发送消息,传入发送给谁,和发送的内容。
                    ticker.server.sendMsg($("#users").val(), msg);
                });
    
            });
        </script>

    html代码

    <h2>
        群聊系统(<span id="count">1</span>人在线):@ViewBag.UserName
    </h2>
    
    
    <div style="overflow:auto;height:300px">
        <ul id="msg"></ul>
    </div>
    
    <select id="users" class="form-control" style="max-150px;">
        <option value="0">所有人</option>
    </select>
    
    <input type="text" onkeydown='if (event.keyCode == 13) { $("#btn-send").click() }' class="form-control" id="txt-msg" placeholder="内容" style="max-400px;" />
    <br />
    <button type="button" id="btn-send">发送</button>

    这样就消息了群聊和发送给特定的人聊天功能。

    3.1、封装主动发送消息的单例

    /// <summary>
        /// 主动发送给用户消息,单例模式
        /// </summary>
        public class GroupChat
        {
            /// <summary>
            /// Clients,用来主动发送消息
            /// </summary>
            private IHubConnectionContext<dynamic> Clients { get; set; }
    
            private readonly static GroupChat _instance = new GroupChat(GlobalHost.ConnectionManager.GetHubContext<GroupChatHub>().Clients);
    
            private GroupChat(IHubConnectionContext<dynamic> clients)
            {
                Clients = clients;
            }
    
            public static GroupChat Instance
            {
                get
                {
                    return _instance;
                }
            }
    
    
            /// <summary>
            /// 主动给所有人发送消息,系统直接调用
            /// </summary>
            /// <param name="msg"></param>
            public void SendSystemMsg(string msg)
            {
                Clients.All.publshMsg(new { Name = "系统消息", Msg = msg, Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") });
            }
        }

    如果需要发送消息,直接调用SendSystemMsg即可。

    GroupChat.Instance.SendSystemMsg("消息");

    4、结语

    啥也不说了直接源码

    github:https://github.com/Emrys5/SignalRGroupChatDemo

    在线演示:http://chat.lining.name/

    最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。  

    作者:Emrys
    出处:http://www.cnblogs.com/emrys5/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    Hash索引与B-Tree索引
    Android -- taskAffinity
    Android -- getSystemService
    Android面试,与Service交互方式
    Quartz.NET开源作业调度框架系列(四):Plugin Job-转
    Quartz.NET开源作业调度框架系列(三):IJobExecutionContext 参数传递-转
    Quartz.NET开源作业调度框架系列(一):快速入门step by step-转
    Quartz.NET开源作业调度框架系列(二):CronTrigger-转
    quartz.net 的配置文件资料
    基于.net 的加载自定义配置-误操作
  • 原文地址:https://www.cnblogs.com/emrys5/p/groupchat.html
Copyright © 2011-2022 走看看