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/

最后望對各位有所幫助,本文原創,歡迎拍磚和推薦。  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM