史上最全面的SignalR系列教程-6、SignalR 實現聊天室
1、概述
通過前面幾篇文章對SignalR的詳細介紹。我們知道Asp.net SignalR是微軟為實現實時通訊的一個類庫。一般情況下,SignalR會使用JavaScript的長輪詢(long polling)的方式來實現客戶端和伺服器通訊,隨著Html5中WebSockets出現,SignalR也支援WebSockets通訊。另外SignalR開發的程式不僅僅限制於宿主在IIS中,也可以宿主在任何應用程式,包括控制檯,客戶端程式和Windows服務等,另外還支援Mono,這意味著它可以實現跨平臺部署在Linux環境下。
SignalR內部有兩類物件:
Http持久連線(Persisten Connection)物件:用來解決長時間連線的功能。還可以由客戶端主動向伺服器要求資料,而伺服器端不需要實現太多細節,只需要處理PersistentConnection 內所提供的五個事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。
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
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敏捷開發框架通過SignalR技術整合即時通訊(IM)
史上最全面的SignalR系列教程-1、認識SignalR
史上最全面的SignalR系列教程-2、SignalR 實現推送功能-永久連線類實現方式
史上最全面的SignalR系列教程-3、SignalR 實現推送功能-集線器類實現方式
史上最全面的SignalR系列教程-4、SignalR 自託管全解(使用Self-Host)-附各終端詳細例項
史上最全面的SignalR系列教程-5、SignalR 實現一對一聊天
Real-time ASP.NET with SignalR
微信公眾號開發系列-玩轉微信開發-目錄彙總
RDIFramework.NET — 基於.NET的快速資訊化系統開發框架 — 系列目錄
RDIFramework.NET ━ .NET快速資訊化系統開發框架 ━ 工作流程元件介紹
RDIFramework.NET框架SOA解決方案(集Windows服務、WinForm形式與IIS形式釋出)-分散式應用
RDIFramework.NET程式碼生成器全新V3.5版本釋出-重大升級
一路走來數個年頭,感謝RDIFramework.NET框架的支持者與使用者,大家可以通過下面的地址瞭解詳情。
RDIFramework.NET官方網站:http://www.rdiframework.net/
RDIFramework.NET官方部落格:http://blog.rdiframework.net/
同時需要說明的,以後的所有技術文章以官方網站為準,歡迎大家收藏!
RDIFramework.NET框架由海南國思軟體科技有限公司專業團隊長期打造、一直在更新、一直在升級,請放心使用!
歡迎關注RDIFramework.net框架官方公眾微信(微訊號:guosisoft),及時瞭解最新動態。
掃描二維碼立即關注