2018/10/10:博主第一次寫原創博文而且還是關於C#的(博主是從前端轉過來的),菜鳥一枚,如果有什么寫的不對,理解錯誤,還望各位輕噴。,從SignalR開始!
首先先介紹一下關於SignalR的一些基本概念,
ASP.NET SignalR是為簡化開發開發人員將實時web內容添加到應用程序過程而提供的類庫。實時web功能指的是讓服務器代碼可以隨時主動推送內容給客戶端,而不是讓服務器等待客戶端的請求(才返回內容)。 所有"實時"種類的web功能都可以使用SignalR來添加到你的ASP.NET應用程序中。最常用的例子有聊天室,但我們能做的比這要多得多。考慮以下情況:用戶需要不停的刷新網頁來看最新的數據;或者在頁面上通過實現長輪詢來檢索新數據(並顯示),那你就可以考慮使用SignalR來實現了。比如:儀表板及監視型應用程序;協作型應用程序(如多人同時對文檔進行編輯);作業進度更新及實時呈現表單等。 SignalR也適合新型的,需要從服務器上進行高頻率更新的web應用程序,例如實時游戲。這里有一個好例子:ShoorR。 SignalR提供了一個簡單的API用戶創建服務器到客戶端的遠程過程調用(RPC),可以方便地從服務器端的.Net代碼中對客戶端瀏覽器及其他客戶端平台中的的JS函數進行調用。SignalR還包括了用於管理連接(例如:連接和斷開事件)及連接分組。
SignalR可以自動對連接進行管理。並讓你發送廣播消息到所有已連接的客戶端上,就像一個聊天室一樣。當然除了群發外,你也可以發送到消息到特定的客戶端。客戶端和服務器的連接是持久的,不像傳統的每次通信都需要重新建立連接的HTTP協議。 SignalR支持“服務器推送”功能,即服務器代碼可以通過使用遠程過程調用(RPC)來調用瀏覽器中的客戶端代碼,而不是當前在web上常用的請求-相應處理模型。 SignalR的應用可以使用服務總線,SQL SERVER或者Redis來擴展到數以千計的客戶端上。 SignalR是開源的,可以通過GitHub訪問。
2、有人可能會問SignalR和WebSocket有什么區別
ignalR使用WebSocket傳輸方式——在可能的情況下。並且會自動切換到舊的傳輸方式(如HTTP長連接)。你當然可以直接使用WebSocket來編寫你的應用程序,但使用SignalR意味着你將有更多的額外功能而無需重新發明輪子。最重要的是,你可以將注意力關注在業務實現上,而無需考慮為舊的客戶端單獨創建兼容代碼。SignalR還能夠使你不必擔心WebSocket更新,因為SignalR將會持續更新以支持變化的底層傳輸方式,跨不同版本的WebSocket來為應用程序提供一個一致的訪問接口。
當然,你可以創建只使用WebSocket傳輸的解決方案,SignalR提供了你可能需要自行編寫代碼的所有功能,比如回退到其他傳輸方式及針對更新的WebSocket實現來修改你的應用程序。博主的理解中就是SignalR是微軟為了簡化開發開發人員工作而造出的輪子,他不僅擁有WebSocket的功能,而且還有傳統的HTTP長連接的方式。
接下來就是安裝SignalR,SignalR在nuget上可以下載安裝(SignalR要求.net 4.5的框架),在vs的工具里能找到nuget管理:搜Microsoft.AspNet.SignalR安裝就好了,安裝后會自動生成一下文件夾。
服務端/接口Api端代碼
-
從Nuget上搜索SignalR並引入
-
創建的腳本拷貝到客戶端Lib中到時使用requirejs引用,並刪除服務端生成的腳本
-
在Startup.cs中 注冊SignalR中間件
1、注冊視頻聊天中間件:LiveVideoChat(app); 其中"/LiveVideoChat"是前端腳本要調用的地址
2、中間件需要授權登錄的情況在,需要配置【QueryStringOAuthBearerProvider】,以及在集線器(LiveVideoChat)標注【Authorize】
using
System;
using
System.Threading.Tasks;
using
Ilikexx.Framework;
using
Microsoft.AspNet.SignalR;
using
Microsoft.Owin;
using
Microsoft.Owin.Cors;
using
Microsoft.Owin.Security.OAuth;
using
Owin;
[assembly: OwinStartup(
typeof
(Brand.Api.Startup))]
namespace
Brand.Api
{
public
partial
class
Startup
{
private
readonly
ILog logger = LogManager.GetLogger(
typeof
(Startup));
/// <summary>
///
/// </summary>
/// <param name="app"></param>
public
void
Configuration(IAppBuilder app)
{
logger.Info(
"Startup開始運行"
);
ConfigureAuth(app);
LogManager.Flush();
LiveVideoChat(app);
}
/// <summary>
/// 注冊視頻聊天模塊
/// </summary>
/// <param name="app"></param>
private
void
LiveVideoChat(IAppBuilder app)
{
//LiveVideoChat 前端腳本要調用的地址
app.Map(
"/LiveVideoChat"
, map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(
new
OAuthBearerAuthenticationOptions()
{
Provider =
new
QueryStringOAuthBearerProvider()
});
var hubConfiguration =
new
HubConfiguration
{
Resolver = GlobalHost.DependencyResolver,
EnableJavaScriptProxies =
true
};
map.RunSignalR(hubConfiguration);
});
}
}
}
/// <summary>
/// 驗證token
/// </summary>
public
class
QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
/// <summary>
/// 從請求地址中獲取token
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public
override
Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get(
"access_token"
);
if
(!
string
.IsNullOrEmpty(value))
{
context.Token = value;
}
return
Task.FromResult<
object
>(
null
);
}
/// <summary>
/// 驗證token的有效性
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public
override
Task ValidateIdentity(OAuthValidateIdentityContext context)
{
return
base
.ValidateIdentity(context);
}
}
-
LiveVideoChat:聊天中間件(核心)
1、類標注Authorize標記是否需要token訪問
2、類標注HubName給集線器起名,客戶端在調用的時候會用到
3、方法標注HubMethodName供客戶端腳本調用
4、熟悉分組廣播,全部廣播,單一廣播
using
System;
using
System.Collections.Concurrent;
using
System.Collections.Generic;
using
System.Linq;
using
System.Security.Claims;
using
System.Threading;
using
System.Threading.Tasks;
using
System.Web;
using
Brand.Model;
using
Brand.Service;
using
Ilikexx.Framework;
using
Ilikexx.Framework.Web.Mvc;
using
Microsoft.AspNet.SignalR;
using
Microsoft.AspNet.SignalR.Hubs;
using
Microsoft.AspNet.SignalR.Infrastructure;
using
Newtonsoft.Json.Linq;
namespace
Brand.Api.Controllers.SignalR
{
/// <summary>
/// 視頻聊天模塊
/// </summary>
[HubName(
"LiveVideoChatService"
)]
[Authorize]
public
class
LiveVideoChat : BaseHub
{
/// <summary>
/// 在線用戶類,按各個房間存儲
/// </summary>
public
static
ConcurrentDictionary<
string
, List<Dictionary<
string
, User>>> OnLineUsers =
new
ConcurrentDictionary<
string
, List<Dictionary<
string
, User>>>();
private
UserService userService;
/// <summary>
///
/// </summary>
public
LiveVideoChat()
{
userService =
new
UserService();
}
/// <summary>
/// 獲取調用者的用戶信息
/// </summary>
public
JObject CallerUserInfo
{
get
{
JObject data =
new
JObject();
User user =
null
;
List<Dictionary<
string
, User>> listUser;
OnLineUsers.TryGetValue(RoomId,
out
listUser);
if
(listUser !=
null
)
{
Dictionary<
string
, User> mapUser = listUser.Find(x =>
{
return
x.ContainsKey(Context.ConnectionId);
});
if
(mapUser !=
null
)
{
mapUser.TryGetValue(Context.ConnectionId,
out
user);
data.Add(
"Id"
, user.Id);
data.Add(
"NickName"
, user.NickName);
data.Add(
"HeadUrl"
, user.HeadUrl);
}
}
return
data;
}
}
/// <summary>
/// 重寫連接
/// </summary>
/// <returns></returns>
public
override
Task OnConnected()
{
Connected();
return
base
.OnConnected();
}
/// <summary>
/// 重新鏈接
/// </summary>
/// <returns></returns>
public
override
Task OnReconnected()
{
Connected();
return
base
.OnReconnected();
}
/// <summary>
/// 重寫斷開連接
/// </summary>
/// <param name="stopCalled"></param>
/// <returns></returns>
public
override
Task OnDisconnected(
bool
stopCalled)
{
User user =
null
;
List<Dictionary<
string
, User>> listUser;
OnLineUsers.TryGetValue(RoomId,
out
listUser);
if
(listUser !=
null
)
{
Dictionary<
string
, User> mapUser = listUser.Find(x =>
{
return
x.ContainsKey(Context.ConnectionId);
});
if
(mapUser !=
null
)
{
mapUser.TryGetValue(Context.ConnectionId,
out
user);
listUser.Remove(mapUser);
OnLineUsers.TryAdd(RoomId, listUser);
}
}
//當前離線移除房間
Groups.Remove(Context.ConnectionId, RoomId);
SendToGroup(0);
return
base
.OnDisconnected(stopCalled);
}
/// <summary>
/// 連接上的處理
/// </summary>
private
void
Connected()
{
//房間號
long
Id = cvt.ToLong(RoomId);
User user = userService.GetModel(UserId);
List<Dictionary<
string
, User>> listUser;
OnLineUsers.TryGetValue(RoomId,
out
listUser);
if
(listUser ==
null
)
{
listUser =
new
List<Dictionary<
string
, User>>();
Dictionary<
string
, User> mapUser =
new
Dictionary<
string
, User>();
mapUser.Add(Context.ConnectionId, user);
listUser.Add(mapUser);
OnLineUsers.TryAdd(RoomId, listUser);
Groups.Add(Context.ConnectionId, RoomId);
SendToGroup(1);
}
else
{
Dictionary<
string
, User> mapUser = listUser.Find(x =>
{
return
x.ContainsKey(Context.ConnectionId);
});
//不等於null 說明是重新連接的(重新連接不等於要退出后才連接)
if
(mapUser ==
null
)
{
mapUser =
new
Dictionary<
string
, User>();
mapUser.Add(Context.ConnectionId, user);
listUser.Add(mapUser);
OnLineUsers.TryAdd(RoomId, listUser);
Groups.Add(Context.ConnectionId, RoomId);
SendToGroup(1);
}
}
}
/// <summary>
/// 給房間內的所有用戶發消息
/// </summary>
/// <param name="JoinOrExit">0=退出 1=加入</param>
private
void
SendToGroup(
int
JoinOrExit)
{
int
totalCount = 0;
List<Dictionary<
string
, User>> listUser;
OnLineUsers.TryGetValue(RoomId,
out
listUser);
if
(listUser !=
null
)
{
totalCount = listUser.Count;
}
resultMessage.data =
new
{
TotalCount = totalCount,
User = CallerUserInfo
};
resultMessage.ret = 0;
#region 特別注意不用 Clients.Group(RoomId) 可能是剛連接(連接后就可以使用)的時候,調用者加入到組會有延遲,所以分二條發送
if
(JoinOrExit == 1)
{
//給調用者(登錄或退出者)發一條
Clients.Caller.JoinRoom(resultMessage);
//所在房間,要排除調用者
Clients.Group(RoomId, Context.ConnectionId).JoinRoom(resultMessage);
}
else
{
//給調用者(登錄或退出者)發一條
Clients.Caller.ExitRoom(resultMessage);
//所在房間,要排除調用者
Clients.Group(RoomId, Context.ConnectionId).ExitRoom(resultMessage);
}
#endregion
}
#region 提供給JS事件調用的方法
/// <summary>
/// 發送【文本】消息給當前房間
/// </summary>
/// <param name="message"></param>
[HubMethodName(
"SendMsgText"
)]
public
void
SendMsgText(
string
message)
{
resultMessage.ret = 0;
resultMessage.data =
new
{
Content = message,
User = CallerUserInfo
};
Clients.Group(RoomId, Context.ConnectionId).ReceiveMsgText(resultMessage);
}
/// <summary>
/// 發送【圖片】消息給當前房間
/// </summary>
/// <param name="message"></param>
[HubMethodName(
"SendMsgImg"
)]
public
void
SendMsgImg(
string
message)
{
resultMessage.ret = 0;
resultMessage.data =
new
{
Content = message,
User = CallerUserInfo
};
Clients.Group(RoomId, Context.ConnectionId).ReceiveMsgImg(resultMessage);
}
#endregion
}
}
-
BaseHub:繼承 Hub,封裝常用方法和屬性
using
Ilikexx.Framework;
using
Ilikexx.Framework.Web.Mvc;
using
Microsoft.AspNet.SignalR;
using
Newtonsoft.Json;
using
Newtonsoft.Json.Linq;
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Net.Http;
using
System.Security.Claims;
using
System.Text;
using
System.Web;
using
System.Web.Http;
using
System.Web.Mvc;
namespace
Brand.Api.Controllers
{
/// <summary>
///
/// </summary>
public
partial
class
BaseHub : Hub
{
/// <summary>
///
/// </summary>
public
ResultMessage resultMessage;
/// <summary>
///
/// </summary>
public
BaseHub()
{
resultMessage =
new
ResultMessage();
}
/// <summary>
/// 獲取房間號
/// </summary>
public
string
RoomId
{
get
{
return
Context.QueryString[
"Id"
];
}
}
/// <summary>
/// 轉為JSON串
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
protected
string
toJsonString(
object
obj)
{
return
JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.None);
}
/// <summary>
/// 獲取所請求的用戶ID
/// </summary>
protected
long
UserId
{
get
{
var identity = System.Web.HttpContext.Current.User.Identity
as
ClaimsIdentity;
//因為保存的是userid,當然也可以用其他方式
return
long
.Parse(identity.Name);
}
}
}
}
WEB端代碼
客戶端/移動端腳本
-
使用Requirejs引入Signalr腳本
require(["jquery"
,
"ui"
,
"lib/jquery.signalR-2.2.2"
], function ($, ui) {
});
-
開始使用:
1、$.hubConnection創建集線器連接,地址LiveVideoChat要跟服務端的一致,若有參數可以在qs中添加(token,房間號等)
2、createHubProxy創建服務代理LiveVideoChatService也要與服務端集線器類起名(服務名)一致
3、接收服務器方法,其中
JoinRoom
為服務端委托調用名稱,data為回調的數據(目前都統一跟接口返回格式一致為JSON串,{ret:0,data:{},msg:""})
self.connHusService
.on(
"JoinRoom"
, function (data) {
});
4、調用服務器方法,其中SendMsgText為服務端的方法, $msg.val()為發送的數據。要注意若有多個參數要與服務端一致
self.connHusService.invoke("SendMsgText"
, $msg.val())
.done(function () {
var txtResponse = $(
"#txtResponse"
)
txtResponse.append(
"OKOK"
);
})
.fail(function (e) {
alert(e);
});
/****************************************************************************************************
注釋:腳本模板示例
****************************************************************************************************/
require([
"jquery"
,
"ui"
,
"lib/jquery.signalR-2.2.2"
], function ($, ui) {
var self = {
$wrap: $(
"body"
),
tpl: '<div id=
"txtResponse"
></div>\
<div style=
"position:absolute;bottom:50px;width:100%"
><input type=
"text"
name=
"msg"
style=
"width:80%;border:1px solid"
/><a href=
"javasrcipt:void(0);"
class
=
"js_send"
style=
"background:green;color:white;padding:10px"
>發送</a></div>',
init: function () {
/// <summary>
/// 初始化
/// </summary>
var size = ui.utils.getViewPort();
self.$wrap.append(ui.render(self.tpl, {
play_width: size.width,
play_height: size.width / 4 * 3
}));
self.chatInit();
//綁定事件
self.bind();
},
bind: function () {
self.$wrap.on(
"click"
,
".js_send"
, function () {
var $msg = self.$wrap.find(
"[name=msg]"
);
//客戶端代理調用服務端方法
self.connHusService.invoke(
"SendMsgText"
, $msg.val())
.done(function () {
var txtResponse = $(
"#txtResponse"
)
txtResponse.append(
"OKOK"
);
})
.fail(function (e) {
alert(e);
});
});
},
chatInit: function () {
//創建集線器連接
self.connHus = $.hubConnection(Ilikexx.apiUrl +
"LiveVideoChat"
);
//添加請求參數
self.connHus.qs = {
access_token: Ilikexx.token,
Id: 100
}
//開啟日志記錄
//self.connHus.logging = true;
// 獲取代理
self.connHusService = self.connHus.createHubProxy(
"LiveVideoChatService"
);
// 設置state的值
// self.connHusService.state.ClientType = "HubNonAutoProxy";
// 客戶端監聽服務端發送的方法
self.connHusService
.on(
"JoinRoom"
, function (data) {
console.log(data)
var txtResponse = $(
"#txtResponse"
)
txtResponse.append(ui.render(
"<div>{{User.NickName}}進入了房間,總人數:{{TotalCount}}</div>"
, data.data));
})
.on(
"ExitRoom"
, function (data) {
console.log(data)
var txtResponse = $(
"#txtResponse"
)
txtResponse.append(ui.render(
"<div>{{User.NickName}}退出了房間,總人數:{{TotalCount}}</div>"
, data.data));
})
.on(
"ReceiveMsgText"
, function (data) {
console.log(data)
var txtResponse = $(
"#txtResponse"
)
txtResponse.append(ui.render(
'<div><img src="{{User.HeadUrl}}" />{{User.NickName}},說:{{Content}}</div>'
, data.data));
})
;
self.connHus.disconnected(function (e, conn) {
console.log(conn)
console.log(
'Wdisconnected。。。。。'
);
//幾秒進行重連
// 開啟連接
self.connHus.start()
.done(function () {
console.log(
"Hus已連接服務器OK"
);
})
.fail(function () {
console.log(
"Hus已連接服務器失敗"
);
});
});
// 開啟連接
self.connHus.start()
.done(function () {
console.log(
"Hus已連接服務器OK"
);
})
.fail(function () {
console.log(
"Hus已連接服務器失敗"
);
});
},
render: function () {
/// <summary>
/// 渲染數據
/// </summary>
}
};
self.init();
});
--------------------- 作者:qq_964878912 來源:CSDN 原文:https://blog.csdn.net/qq_18798917/article/details/53897586 版權聲明:本文為博主原創文章,轉載請附上博文鏈接!