Asp.net SignalR應(yīng)用并實(shí)現(xiàn)群聊功能
ASP.NET SignalR 是為 ASP.NET 開發(fā)人員提供的一個(gè)庫,可以簡(jiǎn)化開發(fā)人員將實(shí)時(shí) Web 功能添加到應(yīng)用程序的過程。實(shí)時(shí) Web 功能是指這樣一種功能:當(dāng)所連接的客戶端變得可用時(shí)服務(wù)器代碼可以立即向其推送內(nèi)容,而不是讓服務(wù)器等待客戶端請(qǐng)求新的數(shù)據(jù)。(來自官方介紹。)
-1、寫這篇的原因
在上篇文章B/S(Web)實(shí)時(shí)通訊解決方案中,并沒有詳情介紹SignalR,所以另起一篇專門介紹SignalR,本文的側(cè)重點(diǎn)是Hub功能。
0、先看最終實(shí)現(xiàn)效果

github:https://github.com/Emrys5/SignalRGroupChatDemo
1、準(zhǔn)備工作
1.1、在NuGet上首先下載SignalR的包。

1.2、配置Owin與SignalR
1.2.1、新建Startup類,注冊(cè)SignalR
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
然后在web.config配置Startup類,在configuration=>appSettings節(jié)點(diǎn)中添加
<add key="owin:AppStartup" value="SignalRChat.App_Start.Startup"/>
1.2.2、在頁面引入SignalR的js
1、由于SignalR前端是基于jQuery的,所以頁面需引入jQuery。
2、引入SignalR的js 。
3、引入最重要的hubs js,這個(gè)js其實(shí)并不存在,SignalR會(huì)反射獲取所有供客戶端調(diào)用的方法放入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類中的方法就是提供給客戶端調(diào)用的js方法。
在js中就可以用signalr調(diào)用SendMsg。
[HubName("simpleHub")]
public class SimpleHub : Hub
{
public void SendMsg(string msg)
{
}
}
這樣基本上前期準(zhǔn)備工作就做完了,后面就是具體的操作。
2、原理與簡(jiǎn)單的編程
其實(shí)原理如果簡(jiǎn)單點(diǎn)理解就很簡(jiǎn)單,因?yàn)閔ttp是無狀態(tài)的,所以每次請(qǐng)求以后都會(huì)與服務(wù)器斷開鏈接,那就是說客戶端可以很容易找到服務(wù)器,但是服務(wù)器如果想給你客戶端發(fā)送消息就比較麻煩,如果不明白的可以參考上一篇文章B/S(Web)實(shí)時(shí)通訊解決方案。
SignalR就很好的解決了這個(gè)問題,也就說實(shí)現(xiàn)了實(shí)現(xiàn)了瀏覽器與服務(wù)器的全雙工通信。
2.1、客戶端至服務(wù)端(B=>S)
客戶端代碼
<script type="text/javascript">
var ticker = $.connection.simpleHub;
$.connection.hub.start();
$("#btn").click(function () {
// 鏈接完成以后,可以發(fā)送消息至服務(wù)端
ticker.server.sendMsg("需要發(fā)送的消息");
});
</script>
服務(wù)端代碼
[HubName("simpleHub")]
public class SimpleHub : Hub
{
public void SendMsg(string msg)
{
// 獲取鏈接id
var connectionId = Context.ConnectionId;
// 獲取cookie
var cookie = Context.RequestCookies;
}
}
其中SimpleHub就是我們定義的繼承Hub類SimpleHub,然后我們可以用特性HubName進(jìn)行重命名。
然后開始鏈接。
在鏈接完成以后,我們就可以調(diào)用在SimpleHub類中調(diào)用的方法。這就就很簡(jiǎn)單的實(shí)現(xiàn)了客戶端至服務(wù)端發(fā)送消息。

我們還可以在Context中獲取我們想要的東西,比如鏈接id,cookie等。
2.2、服務(wù)端至客戶端(S=>B)
服務(wù)端代碼
[HubName("simpleHub")]
public class SimpleHub : Hub
{
public void SendMsg(string msg)
{
Clients.All.msg("發(fā)送給客戶端的消息");
}
}
客戶端代碼
<script type="text/javascript">
var ticker = $.connection.groupChatHub;
$.connection.hub.start();
ticker.client.msg = function (data) {
console.log(data);
}
</script>

這里演示了怎么發(fā)送消息至客戶端,也是SignalR比較重要的功能,這里有兩個(gè)問題需要解決。
問題一、這里是發(fā)送消息給所有連著的客戶端,如果是單個(gè)客戶端或者是一批客戶端應(yīng)該怎么發(fā)送。
問題二、我們?cè)谡{(diào)用msg給個(gè)客戶端發(fā)送消息時(shí)是在接收消息以后做的反饋,然后發(fā)送消息給客戶端,這樣就很類似ajax了,服務(wù)端并沒有主動(dòng)給客戶端發(fā)送消息。
解決:
問題一、Clients可以給特性的一群或者一個(gè)客戶端發(fā)送消息
// 所有人
Clients.All.msg("發(fā)送給客戶端的消息");
// 特定 cooectionId
Clients.Client("connectionId").msg("發(fā)送給客戶端的消息");
// 特定 group
Clients.Group("groupName").msg("發(fā)送給客戶端的消息");
這是比較常用的三個(gè),當(dāng)然還有很多,比如AllExcept,Clients。
在SignalR2.0中還添加了Others,OthersInGroup,OthersInGroups等等。
問題二、我們可以在需要發(fā)送消息的地方調(diào)用GlobalHost.ConnectionManager.GetHubContext<SimpleHub>().Clients中獲取Clients。獲取Clients并發(fā)送消息我們最好寫成單例模式,因?yàn)檫@種需求很符合單例,群聊中有詳細(xì)的代碼。
3、SignalR實(shí)現(xiàn)群聊
以上的介紹和代碼已經(jīng)可以實(shí)現(xiàn)b=>s和s=>b了,那實(shí)現(xiàn)群聊和單獨(dú)聊天就比較簡(jiǎn)單了。
由于功能比較簡(jiǎn)單,所有我把用戶名存到了cookie里,也就說第一次進(jìn)來時(shí)需要設(shè)置cookie。
還有就是在hub中要實(shí)現(xiàn)OnConnected、OnDisconnected和OnReconnected,然后在方法中設(shè)置用戶和connectionid和統(tǒng)計(jì)在線用戶,以便聊天使用。
hub代碼
/// <summary>
/// SignalR Hub 群聊類
/// </summary>
[HubName("groupChatHub")] // 標(biāo)記名稱供js調(diào)用
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);
// 向客戶端發(fā)送在線人員
Clients.All.publshUser(_onlineUser.Select(i => i.Key));
// 向客戶端發(fā)送加入聊天消息
Clients.All.publshMsg(FormatMsg("系統(tǒng)消息", UserName + "加入聊天"));
}
else
{
// 如果是已經(jīng)存在的用戶,則把在線鏈接的個(gè)數(shù)+1
_onlineUser[UserName] = _onlineUser[UserName] + 1;
}
// 加入Hub Group,為了發(fā)送單獨(dú)消息
Groups.Add(Context.ConnectionId, "GROUP-" + UserName);
}
/// <summary>
/// 結(jié)束連接
/// </summary>
/// <param name="stopCalled"></param>
/// <returns></returns>
public override Task OnDisconnected(bool stopCalled)
{
// 人員鏈接數(shù)-1
_onlineUser[UserName] = _onlineUser[UserName] - 1;
// 判斷是否斷開了所有的鏈接
if (_onlineUser[UserName] == 0)
{
// 移除在線人員
_onlineUser.Remove(UserName);
// 向客戶端發(fā)送在線人員
Clients.All.publshUser(_onlineUser.Select(i => i.Key));
// 向客戶端發(fā)送退出聊天消息
Clients.All.publshMsg(FormatMsg("系統(tǒng)消息", UserName + "退出聊天"));
}
// 移除Hub Group
Groups.Remove(Context.ConnectionId, "GROUP-" + UserName);
return base.OnDisconnected(stopCalled);
}
/// <summary>
/// 發(fā)送消息,供客戶端調(diào)用
/// </summary>
/// <param name="user">用戶名,如果為0,則是發(fā)送給所有人</param>
/// <param name="msg">消息</param>
public void SendMsg(string user, string msg)
{
if (user == "0")
{
// 發(fā)送給所有用戶消息
Clients.All.publshMsg(FormatMsg(UserName, msg));
}
else
{
//// 發(fā)送給自己消息
//Clients.Group("GROUP-" + UserName).publshMsg(FormatMsg(UserName, msg));
//// 發(fā)送給選擇的人員
//Clients.Group("GROUP-" + user).publshMsg(FormatMsg(UserName, msg));
// 發(fā)送給自己消息
Clients.Groups(new List<string> { "GROUP-" + UserName, "GROUP-" + user }).publshMsg(FormatMsg(UserName, msg));
}
}
/// <summary>
/// 格式化發(fā)送的消息
/// </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();
// 接收服務(wù)端發(fā)送的消息
$.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,以供單獨(dú)聊天選中
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>')
}
}
});
// 發(fā)送消息按鈕
$("#btn-send").click(function () {
var msg = $("#txt-msg").val();
if (!msg) {
alert('請(qǐng)輸入內(nèi)容!'); return false;
}
$("#txt-msg").val('');
// 主動(dòng)發(fā)送消息,傳入發(fā)送給誰,和發(fā)送的內(nèi)容。
ticker.server.sendMsg($("#users").val(), msg);
});
});
</script>
html代碼
<h2>
群聊系統(tǒng)(<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="內(nèi)容" style="max-width:400px;" />
<br />
<button type="button" id="btn-send">發(fā)送</button>
這樣就消息了群聊和發(fā)送給特定的人聊天功能。
3.1、封裝主動(dòng)發(fā)送消息的單例
/// <summary>
/// 主動(dòng)發(fā)送給用戶消息,單例模式
/// </summary>
public class GroupChat
{
/// <summary>
/// Clients,用來主動(dòng)發(fā)送消息
/// </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>
/// 主動(dòng)給所有人發(fā)送消息,系統(tǒng)直接調(diào)用
/// </summary>
/// <param name="msg"></param>
public void SendSystemMsg(string msg)
{
Clients.All.publshMsg(new { Name = "系統(tǒng)消息", Msg = msg, Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") });
}
}
如果需要發(fā)送消息,直接調(diào)用SendSystemMsg即可。
GroupChat.Instance.SendSystemMsg("消息");
4、結(jié)語
啥也不說了直接源碼
github:https://github.com/Emrys5/SignalRGroupChatDemo
最后望對(duì)各位有所幫助,本文原創(chuàng),歡迎拍磚和推薦。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 關(guān)于B/S判斷瀏覽器斷開的問題討論
- 在B/S開發(fā)中經(jīng)常用到的JavaScript技術(shù)
- b/s開發(fā)常用javaScript技術(shù)
- .net的socket異步通訊示例分享
- ASP.NET 使用application與session對(duì)象寫的簡(jiǎn)單聊天室程序
- Asp.net使用SignalR實(shí)現(xiàn)酷炫端對(duì)端聊天功能
- Asp.net使用SignalR實(shí)現(xiàn)聊天室的功能
- ASP.NET網(wǎng)站聊天室的設(shè)計(jì)與實(shí)現(xiàn)(第3節(jié))
- asp.net mvc signalr簡(jiǎn)單聊天室制作過程分析
- Asp.net SignalR創(chuàng)建實(shí)時(shí)聊天應(yīng)用程序
相關(guān)文章
asp.net Web Services上傳和下載文件(完整代碼)
隨著Internet技術(shù)的發(fā)展和跨平臺(tái)需求的日益增加,Web Services的應(yīng)用越來越廣,我們不但需要通過Web Services傳遞字符串信息,而且需要傳遞二進(jìn)制文件信息。2008-12-12
最簡(jiǎn)單的.NET生成隨機(jī)數(shù)函數(shù)
眾所周知 .Net中Random類生成的隨機(jī)數(shù)是假隨機(jī)數(shù),關(guān)鍵要看構(gòu)造函數(shù)里的種子2009-05-05
.Net Core3.0 WEB API中使用FluentValidation驗(yàn)證(批量注入)
這篇文章主要介紹了.Net Core3.0 WEB API中使用FluentValidation驗(yàn)證(批量注入),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
給Asp.Net初學(xué)者的關(guān)于繼承和多態(tài)性的例子
給Asp.Net初學(xué)者的關(guān)于繼承和多態(tài)性的例子...2006-09-09
ASP.NET XmlHttp跨域訪問實(shí)現(xiàn)代碼
最近項(xiàng)目需要實(shí)現(xiàn)XmlHttp的POST方法到另一服務(wù)器上的頁面進(jìn)行數(shù)據(jù)的更新,可是IE會(huì)提出“該頁正在訪問其控制范圍之外的信息,是否繼續(xù)?”等警告信息,而在其他瀏覽器上直接禁止掉,GOOGLE一下原來是XmlHttp的跨域訪問問題,找了很多資料,說是提供很多解決方案,可是都沒有用處。2008-11-11
DataGrid中實(shí)現(xiàn)超鏈接的3種方法
這篇文章介紹了DataGrid中實(shí)現(xiàn)超鏈接的3種方法,有需要的朋友可以參考一下2013-09-09

