ASP.NET SignalR 应用并实现群聊功能 (开源代码)

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASP.NET SignalR 应用并实现群聊功能 (开源代码)相关的知识,希望对你有一定的参考价值。


来源:Emrys5

cnblogs.com/emrys5/p/groupchat.html


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


SignalR官网(https://www.asp.net/signalr)


1、写这篇的原因


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


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


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

 

1、准备工作


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


 

1.2、配置Owin与SignalR


1.2.1、新建Startup类,注册SignalR


public class Startup

{

    public void Configuration(IAppBuilder app)

    {

        app.MapSignalR();

    }

}


然后在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 ></script>

<script ></script> 

<script ></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)实时通讯解决方案(http://www.cnblogs.com/emrys5/p/real_time_communication.html)。


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就是我们定义的继承Hub类SimpleHub,然后我们可以用特性HubName进行重命名。


然后开始链接。


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


ASP.NET SignalR 应用并实现群聊功能 (开源代码)


我们还可以在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中要实现OnConnected、OnDisconnected和OnReconnected,然后在方法中设置用户和connectionid和统计在线用户,以便聊天使用。


hub代码


/// SignalR Hub 群聊类

[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>

    /// 开始连接

    public override Task OnConnected()

    {

        Connected();

        return base.OnConnected();

    }

    /// 重新链接

    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);

    }

    /// 结束连接

    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));

        }

    }


    /// 格式化发送的消息

    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、封装主动发送消息的单例


/// 主动发送给用户消息,单例模式

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;

        }

    }

    /// 主动给所有人发送消息,系统直接调用

    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/


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

以上是关于ASP.NET SignalR 应用并实现群聊功能 (开源代码)的主要内容,如果未能解决你的问题,请参考以下文章

[Asp.net 开发系列之SignalR篇]专题二:使用SignalR实现酷炫端对端聊天功能

SignalR 2.1 简单入门项目

SignaIR实现聊天功能

SignalR来做实时Web聊天

在 Asp.NET MVC 中使用 SignalR 实现推送功能

SignalR在ASP.NET MVC中的应用