MyException - 我的异常网
当前位置:我的异常网» 开源软件 » Asp.net SignalR 应用并实现群聊效能 开源代码

Asp.net SignalR 应用并实现群聊效能 开源代码

www.MyException.Cn  网友分享于:2013-04-21  浏览:0次
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-width: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-width: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/

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

 

文章评论

科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
旅行,写作,编程
旅行,写作,编程
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序员都该阅读的书
程序员都该阅读的书
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
一个程序员的时间管理
一个程序员的时间管理
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员的鄙视链
程序员的鄙视链
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
鲜为人知的编程真相
鲜为人知的编程真相
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
代码女神横空出世
代码女神横空出世
我是如何打败拖延症的
我是如何打败拖延症的
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
程序员必看的十大电影
程序员必看的十大电影
程序员应该关注的一些事儿
程序员应该关注的一些事儿
漫画:程序员的工作
漫画:程序员的工作
 程序员的样子
程序员的样子
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
如何成为一名黑客
如何成为一名黑客
Java程序员必看电影
Java程序员必看电影
那些争议最大的编程观点
那些争议最大的编程观点
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
老程序员的下场
老程序员的下场
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
10个调试和排错的小建议
10个调试和排错的小建议
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
总结2014中国互联网十大段子
总结2014中国互联网十大段子
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
编程语言是女人
编程语言是女人
我的丈夫是个程序员
我的丈夫是个程序员
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
程序员和编码员之间的区别
程序员和编码员之间的区别
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有