史上最全面的SignalR系列教程-六、SignalR 實現聊天室

一、概述

經過前面幾篇文章對SignalR的詳細介紹。咱們知道Asp.net SignalR是微軟爲實現實時通訊的一個類庫。通常狀況下,SignalR會使用JavaScript的長輪詢(long polling)的方式來實現客戶端和服務器通訊,隨着Html5中WebSockets出現,SignalR也支持WebSockets通訊。另外SignalR開發的程序不單單限制於宿主在IIS中,也能夠宿主在任何應用程序,包括控制檯,客戶端程序和Windows服務等,另外還支持Mono,這意味着它能夠實現跨平臺部署在Linux環境下。javascript

SignalR內部有兩類對象:html

  1. Http持久鏈接(Persisten Connection)對象:用來解決長時間鏈接的功能。還能夠由客戶端主動向服務器要求數據,而服務器端不須要實現太多細節,只須要處理PersistentConnection 內所提供的五個事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 便可。java

  2. Hub(集線器)對象:用來解決實時(realtime)信息交換的功能,服務端能夠利用URL來註冊一個或多個Hub,只要鏈接到這個Hub,就能與全部的客戶端共享發送到服務器上的信息,同時服務端能夠調用客戶端的腳本。jquery

SignalR將整個信息的交換封裝起來,客戶端和服務器都是使用JSON來溝通的,在服務端聲明的全部Hub信息,都會生成JavaScript輸出到客戶端,.NET則依賴Proxy來生成代理對象,而Proxy的內部則是將JSON轉換成對象。git

二、SignalR實現聊天室(羣聊)功能

要想實現羣聊的功能,首先咱們須要建立一個房間,而後每一個在線用戶能夠加入這個房間裏面進行羣聊,咱們能夠爲房間設置一個惟一的名字來做爲標識。那SignalR類庫裏面是否有這樣現有的方法呢?答案是確定的。SignalR做爲一個強大的集線器,已經在hub裏面集成了Gorups,也就是分組管理。github

// IGroupManager接口提供以下方法 
// 做用:將鏈接ID加入某個組 
// Context.ConnectionId 鏈接ID,每一個頁面鏈接集線器即會產生惟一ID 
// roomName分組的名稱 
Groups.Add(Context.ConnectionId, roomName); 
 
// 做用:將鏈接ID從某個分組移除 
Groups.Remove(Context.ConnectionId, roomName); 
 
// IHubConnectionContext接口提供了以下方法 
// 調用客戶端方法向房間內全部用戶羣發消息  
// Room:分組名稱 
// new string[0]:過濾(不發送)的鏈接ID數組 
Clients.Group(Room, new string[0]).clientMethod
複製代碼

上面的代碼就是實現羣聊的核心方法。Groups對象就是SignalR類庫維護的一個列表對象而已,咱們徹底能夠本身維護一個Dictionary<string, List>對象,建立一個房間的時候,咱們將房間名稱和進入房間的客戶端的ConnectionId加入到這個字典裏面,而後在聊天室裏麪點發送消息的時候,咱們根據房間名查找到全部加入羣聊的ConnectionId,而後調用Clients.Clients(IList connectionIds)方法來將消息羣發到每一個客戶端。以上也就是實現聊天室的原理。json

2.一、 建立ASP.NET Mvc項目

新建一個空的ASP.NET Mvc項目,取名爲:SignalRGroupChat。數組

2.二、安裝Nuget包

建立好項目後,要使用SignalR,須要先安裝SignalR包,能夠經過程序包管理控制檯輸入包安裝命令進行安裝。服務器

Install-Package Microsoft.AspNet.SignalR
Install-Package Microsoft.Owin.Cors
複製代碼

2.三、聊天室後臺代碼實現

要實現聊天室功能,咱們須要一些基礎實體,如:用戶類、房間類等,直接上代碼:微信

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace SignalRGroupChat
{
    public class UserContext
    {
        public UserContext()
        {
            Users = new List<User>();
            Connections = new List<Connection>();
            Rooms = new List<ConversationRoom>();
        }

        /// <summary>
        /// 用戶集合
        /// </summary>
        public List<User> Users { get; set; }

        /// <summary>
        /// 鏈接集合
        /// </summary>
        public List<Connection> Connections { get; set; }

        /// <summary>
        /// 房間集合
        /// </summary>
        public List<ConversationRoom> Rooms { get; set; }
    }

    public class User
    {
        /// <summary>
        /// 用戶名
        /// </summary>
        [Key]        
        public string UserName { get; set; }

        /// <summary>
        /// 用戶的鏈接
        /// </summary>
        public List<Connection> Connections { get; set; }

        /// <summary>
        /// 用戶房間集合
        /// </summary>
        public virtual List<ConversationRoom> Rooms { get; set; }

        public User()
        {
            Connections = new List<Connection>();
            Rooms = new List<ConversationRoom>();
        }
    }

    public class Connection
    {
        /// <summary>
        /// 鏈接ID
        /// </summary>
        public string ConnectionID { get; set; }

        /// <summary>
        /// 用戶代理
        /// </summary>
        public string UserAgent { get; set; }

        /// <summary>
        /// 是否鏈接
        /// </summary>
        public bool Connected { get; set; }
    }

    /// <summary>
    /// 房間類
    /// </summary>
    public class ConversationRoom
    {
        /// <summary>
        /// 房間名稱
        /// </summary>
        [Key]
        public string RoomName { get; set; }

        /// <summary>
        /// 用戶集合
        /// </summary>
        public virtual List<User> Users { get; set; }

        public ConversationRoom()
        {
            Users = new List<User>();
        }
    }
}
複製代碼

實現聊天室的SignalR Hub代碼:

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace SignalRGroupChat.Hubs
{
    /// <summary>
    /// 聊天室(羣聊)
    /// </summary>
    [HubName("groupHub")]
    public class GroupHub : Hub
    {
        public static UserContext db = new UserContext();
        public void Hello()
        {
            Clients.All.hello();
        }

        /// <summary>
        /// 重寫Hub鏈接事件
        /// </summary>
        /// <returns></returns>
        public override Task OnConnected()
        {
            // 查詢用戶。
            var user = db.Users.SingleOrDefault(u => u.UserName == Context.ConnectionId);

            //判斷用戶是否存在,不然添加
            if (user == null)
            {
                user = new User()
                {
                    UserName = Context.ConnectionId
                };
                db.Users.Add(user);
            }
            //發送房間列表
            var itme = from a in db.Rooms
                       select new { a.RoomName };
            Clients.Client(this.Context.ConnectionId).getRoomlist(JsonConvert.SerializeObject(itme.ToList()));
            return base.OnConnected();
        }

        /// <summary>
        /// 更新全部用戶的房間列表
        /// </summary>
        private void GetRoomList()
        {
            var itme = from a in db.Rooms
                       select new { a.RoomName };
            string jsondata = JsonConvert.SerializeObject(itme.ToList());
            Clients.All.getRoomlist(jsondata);
        }

        // 重寫Hub鏈接斷開的事件 
        public override Task OnDisconnected(bool stopCalled)
        {
            // 查詢用戶 
            var user = db.Users.FirstOrDefault(u => u.UserName == Context.ConnectionId);
            if (user != null)
            {
                // 刪除用戶 
                db.Users.Remove(user);
                // 從房間中移除用戶 
                foreach (var item in user.Rooms)
                {
                    RemoveFromRoom(item.RoomName);
                }
            }
            return base.OnDisconnected(stopCalled);
        }

        /// <summary>
        /// 加入聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void AddToRoom(string roomName)
        {
            //查詢聊天室
            var room = db.Rooms.Find(a => a.RoomName == roomName);
            //存在則加入
            if (room != null)
            {
                //查找房間中是否存在此用戶
                var isuser = room.Users.Where(a => a.UserName == Context.ConnectionId).FirstOrDefault();
                //不存在則加入
                if (isuser == null)
                {
                    var user = db.Users.Find(a => a.UserName == Context.ConnectionId);
                    user.Rooms.Add(room);
                    room.Users.Add(user);
                    Groups.Add(Context.ConnectionId, roomName);
                    //調用此鏈接用戶的本地JS(顯示房間)
                    Clients.Client(Context.ConnectionId).addRoom(roomName);                   
                }
                else
                {
                    Clients.Client(Context.ConnectionId).showMessage("請勿重複加入房間!");
                }
            }
        }

        /// <summary>
        /// 建立聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void CreatRoom(string roomName)
        {
            var room = db.Rooms.Find(a => a.RoomName == roomName);
            if (room == null)
            {
                ConversationRoom cr = new ConversationRoom()
                {
                    RoomName = roomName
                };
                //將房間加入列表
                db.Rooms.Add(cr);
                AddToRoom(roomName);
                Clients.Client(Context.ConnectionId).showMessage("房間建立完成!");
                GetRoomList();
            }
            else
            {
                Clients.Client(Context.ConnectionId).showMessage("房間名重複!");
            }
        }

        /// <summary>
        /// 退出聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void RemoveFromRoom(string roomName)
        {

            //查找房間是否存在
            var room = db.Rooms.Find(a => a.RoomName == roomName);
            //存在則進入刪除
            if (room != null)
            {
                //查找要刪除的用戶
                var user = room.Users.Where(a => a.UserName == Context.ConnectionId).FirstOrDefault();
                //移除此用戶
                room.Users.Remove(user);
                //若是房間人數爲0,則刪除房間
                if (room.Users.Count <= 0)
                {
                    db.Rooms.Remove(room);

                }
                Groups.Remove(Context.ConnectionId, roomName);
                //提示客戶端
                Clients.Client(Context.ConnectionId).removeRoom("退出成功!");
            }
        }

        /// <summary>
        /// 給分組內全部的用戶發送消息
        /// </summary>
        /// <param name="Room">分組名</param>
        /// <param name="Message">信息</param>
        public void SendMessage(string Room, string Message)
        {
            Clients.Group(Room, new string[0]).sendMessage(Room, Message + " " + DateTime.Now.ToString("HH:mm:ss"));
        }
    }
}
複製代碼

2.四、頁面部分代碼參考

@{
    ViewBag.Title = "GroupChat";
}

<h2>聊天室(羣聊)實例</h2>
<div class="row">
    當前用戶:<label id="username"></label> 
</div>
<div class="row">
    輸入房間名:<input type="text" class="form-control" style="display: initial;" value="技術交流1" id="Roomname" /><button id="CreatRoom" class="btn btn-success">建立聊天室</button>     
</div>
<div class="row">
    <div class="col-md-3">
        <div style="float:left;border:1px solid #ff0000;margin:5px;">
            <div>房間列表</div>               
            <ul id="roomlist">
            </ul>
        </div> 
    </div>
    <div class="col-md-9">
         <div id="RoomList">
    </div>
    </div>  
</div>
<div class="row">
    <div class="col-md-4"></div>
    <ul id="UserList"></ul>
    <div class="col-md-8"></div>  
</div>

<br />

@section scripts {
    <script src="~/Scripts/jquery-3.3.1.min.js"></script>
    <script src="~/Scripts/jquery.signalR-2.4.1.min.js"></script>
    <script src="~/signalr/hubs"></script>
    <script type="text/javascript">
        var chat
        var roomcount = 0;
        $(function () {
            chat = $.connection.groupHub;
            chat.client.showMessage = function (Message) {
                alert(Message);
            }

            chat.client.sendMessage = function (roomname, message) {
                $("#" + roomname).find("ul").each(function () {
                    $(this).append('<li>'+message+'</li>')
                })
            }
            chat.client.removeRoom = function (data) {
                alert(data);
            }
            chat.client.addRoom = function (roomname) {
                var html = '<table class="table"><tr><td><div style="width: 80%;margin:5px;border:1px solid #ff0000;" id="' + roomname + '" roomname="' + roomname + '"><button onclick="RemoveRoom(this)"  class="btn-danger">退出</button>\
                                <label>' + roomname + '</label>房間\
                                            聊天記錄以下:<ul>\
                                            </ul>\
                                <input type="text" /> <button class="btn btn-success" onclick="SendMessage(this)">發送</button>\
                                </div></td></tr></table>'
                $("#RoomList").append(html);
            }

            //註冊查詢房間列表的方法
            chat.client.getRoomlist = function (data) {
                if (data) {
                    var jsondata = $.parseJSON(data);
                    $("#roomlist").html(" ");
                    for (var i = 0; i < jsondata.length; i++) {
                        var html = ' <li>房間名:' + jsondata[i].RoomName + '<button roomname="'+jsondata[i].RoomName+'"  class="btn-sm btn-info" onclick="AddRoom(this)">加入</button></li>';
                        $("#roomlist").append(html);
                    }
                }
            }

            // 獲取用戶名稱。
            $('#username').html(prompt('請輸入您的名稱:', ''));

            $.connection.hub.start().done(function () {
                $('#CreatRoom').click(function () {
                    if (roomcount < 2) {
                        chat.server.creatRoom($("#Roomname").val());
                        roomcount++;
                    } else {
                        alert("聊天窗口只容許有2個")
                    }
                })
            });
        });

        function SendMessage(btn) {
            var message = $(btn).prev().val();
            var room = $(btn).parent();
            var username = $("#username").html();
            message = username + ":" + message;
            var roomname = $(room).attr("roomname");
            chat.server.sendMessage(roomname,message);
        }

        function RemoveRoom(btn) {
            var room = $(btn).parent();
            var roomname = $(room).attr("roomname");
            $(room).remove();
            chat.server.removeFromRoom(roomname);
        }

        function AddRoom(roomname) {
             var data =$(roomname).attr("roomname");
             chat.server.addToRoom(data);
        }
    </script>
}
複製代碼

三、效果展現

效果展現

四、代碼下載

實例源碼能夠移步github下載,地址:github.com/yonghu86/Si…

五、參考文章


一路走來數個年頭,感謝RDIFramework.NET框架的支持者與使用者,你們能夠經過下面的地址瞭解詳情。

RDIFramework.NET官方網站:www.rdiframework.net/

RDIFramework.NET官方博客:blog.rdiframework.net/

同時須要說明的,之後的全部技術文章以官方網站爲準,歡迎你們收藏!

RDIFramework.NET框架由海南國思軟件科技有限公司專業團隊長期打造、一直在更新、一直在升級,請放心使用!

歡迎關注RDIFramework.net框架官方公衆微信(微信號:guosisoft),及時瞭解最新動態。

掃描二維碼當即關注

微信號:guosisoft
相關文章
相關標籤/搜索