Asp.NET MVC 使用 SignalR 實現推送功能一(Hubs 在線聊天室)

簡介

      ASP .NET SignalR 是一個ASP .NET 下的類庫,能夠在ASP .NET 的Web項目中實現實時通訊。什麼是實時通訊的Web呢?就是讓客戶端(Web頁面)和服務器端能夠互相通知消息及調用方法,固然這是實時操做的。
WebSockets是HTML5提供的新的API,能夠在Web網頁與服務器端間創建Socket鏈接,當WebSockets可用時(即瀏覽器支持Html5)SignalR使用WebSockets,當不支持時SignalR將使用其它技術來保證達到相同效果。
SignalR固然也提供了很是簡單易用的高階API,使服務器端能夠單個或批量調用客戶端上的JavaScript函數,而且很是 方便地進行鏈接管理,例如客戶端鏈接到服務器端,或斷開鏈接,客戶端分組,以及客戶端受權,使用SignalR都很是 容易實現。
SignalR 將與客戶端進行實時通訊帶給了ASP .NET 。固然這樣既好用,並且也有足夠的擴展性。之前用戶須要刷新頁面或使用Ajax輪詢才能實現的實時顯示數據,如今只要使用SignalR,就能夠簡單實現了。
最重要的是您無需從新創建項目,使用現有ASP .NET項目便可無縫使用SignalR。
 
  以上是來自百度百科的解釋,我的以爲通俗來說就是WebSockets是一種握手協議,當用戶於服務器創建鏈接(握手成功)時,雙方就創建了一個鏈接通道,互相傳遞時時數據。在這以前,咱們通常來講實現這個功能的方式就是Ajax輪詢,經過Ajax循環獲取數據,這固然是很是消耗資源的,而且給服務器帶來必定的壓力。而WebSockets雖然可達到全雙工通訊,但依然須要發出請求,不過這種請求的Header是很小的-大概只有 2 Bytes。這裏咱們重點要知道的一點就是,WebSockets這個通道是雙工通道,簡單理解就是客戶端(javascript、jquery)的方法不但能夠調取服務器的功能程序方法,而且服務器的功能程序也能夠調取所有(廣播)或某個(單播)或某一類(組播)客戶端(javascript、jquery)的方法。而SignalR則是微軟給咱們集成的一個WebSockets API,原理跟WebSockets是一致的,只是當WebSockets可用時(即瀏覽器支持Html5)SignalR使用WebSockets,當不支持時SignalR將使用其它技術來保證達到相同效果。

使用

不少新的技術沒有必要非得理解,只須要知道你的應用環境能夠用這項技術很簡便的去實現,用的多了,用的久了,天然而然就會慢慢的理解這項技術的原理了。javascript

今天咱們一步一步來介紹一下如何使用SignalR,這一篇文章介紹的是使用Hubs SignalR集線器去實現,下一篇咱們將介紹使用SignalR永久鏈接類去實現,咱們作個簡單的聊天室。html

先給你們貼一下Demo的效果圖:java

1、準備:jquery

SignalR 運行在 .NET 4.5 平臺上,因此須要安裝 .NET 4.5。爲了方便演示,本示例使用 ASP.NET MVC 在 Win 7 系統來實現。這須要安裝 ASP.NET MVC 3 或 ASP.NET MVC 4數據庫

二:Hubs代碼示例:瀏覽器

一、首先咱們建立一個MVC項目工程名字叫作SignalR_Chat服務器

二、而後打開 工具 - NuGet程序包管理器 - 程序包管理器控制檯app

三、安裝SignalRide

輸出NuGet命令:Install-Package Microsoft.AspNet.SignalR函數

安裝成功後咱們發現咱們的bin裏已經添加了咱們須要的組件,而且在Scripts文件夾下添加了SignalR的Jquery引用

四、咱們新建一個文件夾叫Hubs,而後添加SignalR集線器類ChatHub.cs

在上面的代碼中:

(1)HubName 這個特性是爲了讓客戶端知道如何創建與服務器端對應服務的代理對象,若是沒有設定該屬性,則以服務器端的服務類名字做爲 HubName 的缺省值;

(2)Chat 繼承自 Hub,從下面 Hub 的接口圖能夠看出:Hub 支持向發起請求者(Caller),全部客戶端(Clients),特定組(Group) 推送消息。

五、添加OWIN Startup Class

修改 Configuration方法

using Microsoft.Owin;
using Owin;
using SignalR_Chat.Connections;

[assembly: OwinStartupAttribute(typeof(SignalR_Chat.Startup))]
namespace SignalR_Chat
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
       //這個是下一篇永久鏈接類的 咱們先不用
            //app.MapSignalR<MyConnection>("/echo");
        }
    }
}

 

六、咱們實現一個聊天室,代碼我是分步貼出來的,後面我會附上完整的代碼和Demo。

首先,咱們新建一個類 標記用戶和在線狀態

    public class OnlineUserInfo
        {
            //用戶ID
            public string UserId { get; set; }
            //用戶鏈接ID
            public string ConnectionId { get; set; }
            //用戶暱稱
            public string UserNickName { get; set; }
            //用戶頭像
            public string UserFaceImg { get; set; }
            //用戶狀態
            public string UserStates { get; set; }
        }

而後,咱們在ChatHub中實例化一下這個類

       /// <summary>
        /// 這個是經過Hub集線器 你們能夠參考 Connections下的 MyConnection 永久鏈接
        /// </summary>
        [HubName("chat")]
        public class ChatHub : Hub
        {
            static List<OnlineUserInfo> UserList = new List<OnlineUserInfo>();
        }

咱們寫一個用戶鏈接時註冊一個羣組,用戶後面的組播

            /// <summary>
            /// 註冊羣組 註冊用戶信息
            /// </summary>
            /// <param name="groupid">羣組ID</param>
            /// <param name="usernickname">用戶暱稱</param>
            /// <param name="userfaceimg">用戶頭像</param>
            /// <param name="userid">用戶在網站中的惟一標識ID</param>
            public void Group(string groupid, string usernickname, string userfaceimg, string userid)
            {
                //添加用戶到羣組 Groups.Add(用戶鏈接ID,羣組)
                Groups.Add(Context.ConnectionId, groupid);

                //若是說是一個簡單的聊天室 下面這段代碼是沒有什麼做用的 由於Context.ConnectionId是惟一的用戶於服務器之間的鏈接
                //這裏我傳遞進來了 用戶的暱稱和頭像 還有網站中用戶的ID 因此我要把用戶的信息添加到咱們上面創建的那個列表類中

                //若是用戶不存在在線列表中
                if (UserList.Where(p => p.UserId == userid).FirstOrDefault() == null)
                {
                    //咱們在列表中 添加這個用戶 而且標記用戶在線 UserStates = "True"
                    UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "True" });
                }
                    //若是用戶已經存在於在線列表中
                else
                {
                    //咱們更新用戶列表中用戶的信息 (這裏更新的信息主要是用戶的鏈接ID  ConnectionId = Context.ConnectionId)
                    var UserInfo = UserList.Where(p => p.UserId == userid).FirstOrDefault();
                    UserList.Remove(UserInfo);
                    UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "True" });
                }
                //這個方法是調用客戶端LoginUser方法 而且傳遞當前用戶列表 客戶端會刷新當前用戶列表 調用的是所有的已鏈接的用戶 Clients.All
                Clients.All.LoginUser(Common.JsonConverter.Serialize(UserList));
                //這個方法是調用客戶端的 addNewMessageToPage方法 目的是實現 當一個用戶上線是 提醒全部的用戶 某個用戶上線了 提醒的是全部的已鏈接用戶 因此也是Clients.All
                Clients.All.addNewMessageToPage("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系統消息:" + DateTime.Now.ToString("HH:mm:ss") + "&nbsp;" + usernickname + "&nbsp;上線了<dd></dl>");
            }

爲了方便你們理解,我這裏就先把客戶端的LoginUser和addNewMessageToPage方法貼出來,讓你們好理解服務器是怎樣調用客戶端的js方法的

    //接收服務器信息
    chat.client.addNewMessageToPage = function (message) {
        //#chatContent就是一個div層 咱們把服務器返回的信息追加到這個層上 跟QQ聊天相反,新的信息咱們追加頂部
        $('#chatContent').prepend(message);
    };
    //服務器端調用的LoginUser方法,根據返回的用戶列表 輸出用戶列表到頁面上
    chat.client.LoginUser = function (UserList) {
        //在下一篇介紹的持久鏈接類中 是能夠直接返回Json的 這裏不知道怎麼回事 接收的Json老是被接收成字符串 因此這裏咱們解析一下
        var data = eval("(" + UserList + ")");
        var html = "";
        for(var i=0;i<data.length;i++)
        {
            //這裏咱們作了一個判斷 就是 解析用戶列表Json時 若是用戶的ID 就是當前用戶的ID 那麼就不添加 這跟QQ不同啊 QQ中好友列表中是有本身的
            if (data[i].UserId != $("#Juser-userid").val()) {
                //若是用戶的在線狀態是在線呢 咱們就添加onclick方法 實現 點擊用戶的用戶 能夠私聊 若是不在線 就不添加了 由於咱們這個是沒有存數據庫的 因此沒有作離線消息
                if (data[i].UserStates == "True") {
                    html += "<dl onclick=\"javascript:sendPerMessage('" + data[i].ConnectionId + "','" + data[i].UserNickName + "')\" class=\"clearfix tab-item-1\"><dt><img src=\"" + data[i].UserFaceImg + "\"></dt><dd>" + data[i].UserNickName + "</dd></dl>";
                }
                else
                {
                    html += "<dl onclick=\"javascript:void(0)\" class=\"clearfix tab-item-1 liveout\"><dt><img src=\"" + data[i].UserFaceImg + "\"></dt><dd>" + data[i].UserNickName + "</dd></dl>";
                }
            }
        }
        //更新頁面用戶列表
        $("#OnlineUsers").html(html);
    };

這裏注意的是服務器調用的客戶端方法跟客戶端寫的JS方法大小寫是同樣的,後面咱們介紹客戶端調用服務器方法的時候會將 客戶端調用服務器方法的時候 服務器方法的首字母是小寫的,這裏提醒一下。

下面是咱們的發送消息的方法,當我發送消息的時候 傳遞個人頭像和暱稱給服務器 讓別人顯示消息的時候能顯示出誰發送的(這個跟QQ消息相似啊 方便你們理解)

一個是羣組消息固然也能夠是所有消息,另外一個是私聊,就是指定發送個某一個用戶

/// <summary>
            /// 發送消息 自定義判斷是發送給所有用戶仍是某一個組(相似於羣聊啦)
            /// </summary>
            /// <param name="groupid">接收的組</param>
            /// <param name="userfaceimg">發送用戶的頭像</param>
            /// <param name="usernickname">發送用戶的暱稱</param>
            /// <param name="message">發送的消息</param>
            public void Send(string groupid, string userfaceimg, string usernickname, string message)
            {
                if (groupid == "All")//所有用戶(廣播)
                {
                    //調用全部客戶端的addNewMessageToPage方法 推送一條消息
                    Clients.All.addNewMessageToPage("<dl class=\"clearfix\"><dt><img src=\"" + userfaceimg + "\" /></dt><dd><i></i><div class=\"J_Users\">" + usernickname + "</div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                }
                else//指定組(組播)
                {
                    //調用指定客戶端的addNewMessageToPage方法 推送一條消息(全部屬於組groupid的已鏈接用戶)
                    Clients.Group(groupid).addNewMessageToPage("<dl class=\"clearfix\"><dt><img src=\"" + userfaceimg + "\" /></dt><dd><i></i><div class=\"J_Users\">" + usernickname + "</div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                }
            }

            /// <summary>
            /// 發送給指定用戶(單播)
            /// </summary>
            /// <param name="clientId">接收用戶的鏈接ID</param>
            /// <param name="userfaceimg">發送用戶的頭像</param>
            /// <param name="usernickname">發送用戶的暱稱</param>
            /// <param name="message">發送的消息</param>
            public void SendSingle(string clientId, string userfaceimg, string usernickname, string message)
            {
                //首先咱們獲取一下接收用戶的信息
                var UserInfo = UserList.Where(p => p.ConnectionId == clientId).FirstOrDefault();
                //若是用戶不存在或用戶的在線狀態爲False 那麼提醒一下 發送用戶 對方不在線
                if (UserInfo == null || UserInfo.UserStates == "False")
                {
                    Clients.Client(Context.ConnectionId).addNewMessageToPage("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系統消息:當前用戶不在線<dd></dl>");
                }
                else
                {
                    //若是用戶存在而且在線呢 就把消息推送給接收的用戶 而且加上當前用戶信息 以及添加一個onclick事件 讓接收的用戶 能夠直接點擊消息的用戶 回覆 私聊信息 (否則還要在用戶列表中找到誰給我發的消息 點擊回覆 這不科學...)
                    Clients.Client(clientId).addNewMessageToPage("<dl class=\"clearfix\"><dt onclick=\"javascript:sendPerMessage('" + Context.ConnectionId + "','" + usernickname + "')\"><img src=\"" + userfaceimg + "\" /></dt><dd class=\"per\"><s></s><div onclick=\"javascript:sendPerMessage('" + Context.ConnectionId + "','" + usernickname + "')\" class=\"J_Users\">" + usernickname + "<span>私聊</span></div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                    //這句是發送給發送用戶的 總不能我發送個私聊 對方收到了信息 我這裏什麼都不顯示是吧 我也顯示我發送的私聊信息 由於發送發就是我本身 因此不加onclick事件了 不容許本身跟本身聊天哦
                    Clients.Client(Context.ConnectionId).addNewMessageToPage("<dl class=\"clearfix\"><dt><img src=\"" + userfaceimg + "\" /></dt><dd class=\"per\"><s></s><div class=\"J_Users\">" + usernickname + "<span>私聊</span></div><div class=\"J_Content\">" + message + "</div></dd></dl>");
                }
            }

這裏我貼一下前臺代碼

    $.connection.hub.start().done(function () {
        //用戶鏈接時 註冊一下羣組和我的信息哦 這個的服務器代碼 咱們上面貼出來了
        //這個Demo是爲了讓你們理解SigalR因此沒有作多複雜的流程 我的信息 我是直接傳遞的 
        //$("#groupid").val()這個是要註冊的羣組,能夠本身定義 組播的時候 只要是在這一個組裏的都會收到 
        //$("#Juser-login").val()這個是發送方也就是個人暱稱
        //$("#Juser-faceimg").val()這個是個人頭像
        //$("#Juser-userid").val()這個是我在網站中的惟一標識ID,用戶鏈接的ID(Context.ConnectionId)也是惟一的,那麼爲何還要我在這個網站中的ID呢?
        //解釋一會兒:單頁面的聊天室是沒有多大必要的,可是好比咱們這個功能是放到公用裏的,就像網站的在線客服同樣,你總不能每一個頁面都寫一套吧 既然是引用的這一個頁面 
        //那麼用戶打開其餘頁面的時候 這個Context.ConnectionId是會變的,那我怎麼知道這又是誰呢 咱們就用用戶在網站中的惟一標識ID做爲參照,當新的鏈接進來時 咱們看下是否是ID同樣 
        //同樣的話咱們就更新用戶列表中這個惟一標識ID用戶的Context.ConnectionId和在線狀態 不同的話就添加新用戶
        chat.server.group($("#groupid").val(), $("#Juser-login").val(), $("#Juser-faceimg").val(), $("#Juser-userid").val());
        $('.sendBtn').click(function () {
            //這裏作一下判斷 若是沒有輸入消息就發送 那麼提示一下
            if ($('#MessageBox').val().length <= 0)
            {
                $('#chatContent').prepend("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系統消息:請輸入信息<dd></dl>");
            }
            else
            {
                //sendToConnectId 是咱們自定義的一個字段 若是你點擊了某一個用戶 那麼就把他的ConnectionId賦給sendToConnectId 咱們知道是私聊
                //固然,用戶點擊退出私聊的時候 這個字段會被賦爲空 表示是羣聊 這個你們在Demo中一看就明白了
                if (sendToConnectId != "" && sendToConnectId.length > 0) {
                    //調用服務器私聊方法 !!!注意啊!!!服務器的私聊方法是 public void SendSingle(string clientId, string userfaceimg, string usernickname, string message)
                    //這裏是chat.server.sendSingle 首字母小寫啊 客戶端調用的服務器方法首字母要小寫  服務器調用的客戶端方法 大小寫一致 
                    chat.server.sendSingle(sendToConnectId, $("#Juser-faceimg").val(), $("#Juser-login").val(), $('#MessageBox').val());
                    $('#MessageBox').val("").focus();
                }
                else {
                    //這裏是羣聊 咱們演示的沒有作羣組聊天 因此這裏傳遞的是"All"表示 所有,會發送給所有用戶
                    //說明一下方便理解:好比咱們有這麼一個情景,這個聊天是一個討論,對某一篇文章或產品的討論,那麼是否是應該只在這篇文章或這個產品頁面的用戶才能收發屬於這篇文章或產品消息呢,在其餘頁面
                    //的用戶不該該能收發這裏的消息呀 那麼咱們上面的代碼chat.server.group中傳遞的groupid就應該是某篇文章或產品的標識,把他們劃分到一個組裏好比chat.server.group("A123")一個自定義字符串加上文章或產品ID,
                    //或直接用文章或產品的IDchat.server.group("123") 這裏就chat.server.send("123", ...);就實現了 只有在這個頁面中的用戶才能收到此消息 就跟QQ的羣是同樣的
                    chat.server.send("All", $("#Juser-faceimg").val(), $("#Juser-login").val(), $('#MessageBox').val());
                    $('#MessageBox').val("").focus();
                }
            }
        });
    });

使用者離線或從新鏈接 重寫Hub的方法

 //使用者離線
            public override Task OnDisconnected(bool stopCalled)
            {
                var UserInfo = UserList.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
                var userid = UserInfo.UserId;
                var usernickname = UserInfo.UserNickName;
                var userfaceimg = UserInfo.UserFaceImg;
                UserList.Remove(UserInfo);
                UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "False" });

                Clients.All.LoginUser(Common.JsonConverter.Serialize(UserList));
                Clients.All.addNewMessageToPage("<dl  class=\"messageTip clearfix\"><dt></dt><dd>系統消息:" + DateTime.Now.ToString("HH:mm:ss") + "&nbsp;" + usernickname + "&nbsp;離線了<dd></dl>");

                return base.OnDisconnected(true);
            }

            //使用者從新鏈接
            public override Task OnReconnected()
            {
                var UserInfo = UserList.Where(p => p.ConnectionId == Context.ConnectionId).FirstOrDefault();
                if (UserInfo != null)
                {
                    var userid = UserInfo.UserId;
                    var usernickname = UserInfo.UserNickName;
                    var userfaceimg = UserInfo.UserFaceImg;
                    UserList.Remove(UserInfo);
                    UserList.Add(new OnlineUserInfo() { UserId = userid, ConnectionId = Context.ConnectionId, UserNickName = usernickname, UserFaceImg = userfaceimg, UserStates = "True" });
                    Clients.All.LoginUser(Common.JsonConverter.Serialize(UserList));
                }
                return base.OnReconnected();
            }

還有一些輔助的JS方法,在這裏我就不一一貼出來了,我把demo地址留給你們 ,你們能夠搭建起來研究一下。

這篇文章僅僅是我的的一些理解和實現,可能中間會出現一些不合理的地方或是錯誤,請你們指正,咱們共同窗習研究。

Demo是用VS 2013寫的 

下載:百度網盤 

補充:Demo是我寫這個博客以前寫的 沒有用到HubName 這個特性 因此Demo跑起來會有錯誤 你們刪除這個特性就沒有錯誤了 在Hubs文件夾下的ChatHub.cs

原創文章 轉載請尊重勞動成果 http://yuangang.cnblogs.com

相關文章
相關標籤/搜索