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

  • 2019 年 10 月 3 日
  • 筆記

1、概述

通過前面幾篇文章對SignalR的詳細介紹。我們知道Asp.net SignalR是微軟為實現實時通訊的一個類庫。一般情況下,SignalR會使用JavaScript的長輪詢(long polling)的方式來實現客戶端和伺服器通訊,隨著Html5中WebSockets出現,SignalR也支援WebSockets通訊。另外SignalR開發的程式不僅僅限制於宿主在IIS中,也可以宿主在任何應用程式,包括控制台,客戶端程式和Windows服務等,另外還支援Mono,這意味著它可以實現跨平台部署在Linux環境下。

SignalR內部有兩類對象:

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

  2. Hub(集線器)對象:用來解決實時(realtime)資訊交換的功能,服務端可以利用URL來註冊一個或多個Hub,只要連接到這個Hub,就能與所有的客戶端共享發送到伺服器上的資訊,同時服務端可以調用客戶端的腳本。

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

2、SignalR實現聊天室(群聊)功能

要想實現群聊的功能,首先我們需要創建一個房間,然後每個在線用戶可以加入這個房間裡面進行群聊,我們可以為房間設置一個唯一的名字來作為標識。那SignalR類庫裡面是否有這樣現有的方法呢?答案是肯定的。SignalR作為一個強大的集線器,已經在hub裡面集成了Gorups,也就是分組管理。

// 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)方法來將消息群發到每個客戶端。以上也就是實現聊天室的原理。

2.1、 創建ASP.NET Mvc項目

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

2.2、安裝Nuget包

創建好項目後,要使用SignalR,需要先安裝SignalR包,可以通過程式包管理控制台輸入包安裝命令進行安裝。

Install-Package Microsoft.AspNet.SignalR  Install-Package Microsoft.Owin.Cors

2.3、聊天室後台程式碼實現

要實現聊天室功能,我們需要一些基礎實體,如:用戶類、房間類等,直接上程式碼:

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.4、頁面部分程式碼參考

@{      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>  }

3、效果展示

效果展示

4、程式碼下載

實例源碼可以移步github下載,地址:https://github.com/yonghu86/SignalRTestProj

5、參考文章


一路走來數個年頭,感謝RDIFramework.NET框架的支援者與使用者,大家可以通過下面的地址了解詳情。

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

RDIFramework.NET官方部落格:http://blog.rdiframework.net/

同時需要說明的,以後的所有技術文章以官方網站為準,歡迎大家收藏!

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

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

掃描二維碼立即關注

微訊號:guosisoft