SignalR
SignalR 支持的平台
ASP.NET Core SignalR 支持的平台
SignalR 目录
教程:ASP.NET Core SignalR 入门【添加 SignalR 客户端库】
ASP.NET Core SignalR 简介 (GitHub上的SignalR)
在 SignalR 中使用中心 ASP.NET Core(上下文对象、Clients 对象)
ASP.NET Core SignalR 配置(配置持有者身份验证、配置客户端选项、配置超时和 keep-alive 选项、JSON/MessagePack 序列化选项、配置服务器选项)
ASP.NET Core SignalR JavaScript 客户端(连接到中心、错误处理和日志记录、重新连接客户端、自动重新连接)
ASP.NET Core SignalR 身份验证和授权
Java 客户端 SignalR ASP.NET Core
ASP.NET Core SignalR 客户端功能
在 ASP.NET Core 中使用流式处理 SignalR
ASP.NET SignalR 和 ASP.NET Core 之间的差异 SignalR
从向外发送邮件中心(Controller 调用 Hub)
@microsoft/signalr - npm
@apavelm/vue-signalr - npm
signalr - npm
必须使用 IIS8 及以上
JavaScript 连接 signalR
let connection = new signalR.HubConnectionBuilder()
.withUrl("/pushHub", { accessTokenFactory: () => "Pcl9EWEa3sAY47VmvzJs59J86zoq_sTuBy09c7nCeFQ" })//ok
.withUrl("/myhub", {
accessTokenFactory: () => {
// Get and return the access token.
// This function can return a JavaScript Promise if asynchronous
// logic is required to retrieve the access token.
}
})
.build();
配置WebSockets连接方式
let connection = new signalR.HubConnectionBuilder()
.withUrl("/myhub", {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets
})
.build();
JavaScript 连接 SignalR 完整示例【push.js】
//github地址:https://github.com/lianggx/Examples/tree/master/SignalR/Ron.SignalRLesson2/Ron.SignalRLesson1/Controllers
"use strict";
var connection = new signalR.HubConnectionBuilder()
.withUrl("/pushHub")
//.withUrl("/heartbeatHub")//心跳
//.withUrl("/pushHub", { accessTokenFactory: () => "ekmoKVqJWgEkwPB4pqTkH_hjcGB7TSHJHoqciQobr5s" })
//.withUrl("https://localhost:44383/pushHub")
.configureLogging(signalR.LogLevel.Information)
//.withAutomaticReconnect()
//.withAutomaticReconnect([0, 3000, 6000, 12000, 18000, 24000])//以毫秒为单位
.build();
//connection.keepAliveIntervalInMilliseconds = 15000 //15s
//connection.serverTimeoutInMilliseconds = 30000 //3s
connection.qs = { username: "qs" + "1111" };
connection.start()
.then(function () {
console.log("SignalR 连接成功");
}).catch(function (err) {
console.log("SignalR 连接失败");
console.log(err);
});
//async function start() {
// try {
// await connection.start();
// console.log("connected");
// } catch (err) {
// console.log(err);
// setTimeout(() => start(), 5000);
// }
//};
connection.onclose(async () => {
console.log('监听到链接关闭');
connection.start()
.then(function () {
console.log("SignalR 连接成功");
}).catch(function (err) {
console.log("SignalR 连接失败");
console.log(err);
});
});
//连接时使用 onreconnected 更新 UI(onreconnecting 回调)
//connection.onreconnecting((error) => {
// console.assert(connection.state === signalR.HubConnectionState.Reconnecting);
// const status = "Connection lost due to error " + error;
// console.log(error);
//});
//onreconnected回调
//connection.onreconnected((connectionId) => {
// console.log("Ln 26:" + connectionId);
//});
//绑定事件("Receive"和服务器端的Receive方法中的第一个参数一致)
connection.on("Receive", function (data) {
debugger;
console.log(data);
var li = document.createElement("li");
if (data.length > 0) {
li = $(li).text(data[0].userName + ":" + data[0].content)
}
else {
li = $(li).text(data.userName + ":" + data.content)
}
$("#msgList").append(li);
});
connection.on("ReceiveMessage", (data) => {
console.log(data);//后台返回的数据,可以是json格式
});
connection.on("ReceivePublicMessage", function (data) {
debugger;
console.log("公共消息:" + data);
});
connection.on("ReceivePrivateMessage", function (data) {
debugger;
console.log("私有消息:" + data);
});
connection.on("ReceiveHeartbeat", function (data) {
debugger;
console.log(data);
var li = document.createElement("li");
if (data.length > 0) {
li = $(li).text("接收心跳包:" + data[0].userName + ":" + data[0].content)
}
$("#msgList").append(li);
});
connection.on("HeartbeatAsync", function (data) {
debugger;
console.log(data);
var li = document.createElement("li");
li = $(li).text("接收心跳包:" + data.toString());
$("#msgList").prepend(li);
});
$("#btnSend").on("click", () => {
debugger;
var userName = $("#userName").val();
var content = $("#content").val();
console.log(userName + ":" + content);
connection.invoke("send", { "Type": 0, "UserName": userName, "Content": content });
});
$("#btnStop").on("click", () => {
connection.stop();
});
$("#btnSendHeartbeat").on("click", () => {
connection.invoke("SendHeartbeat");
});
$("#btnSendToUser").on("click", () => {
var userName = $("#txtUserName").val();
var usercontent = $("#txtUserContent").val();
$.postJSON("api/v1/push/send_to_user", { Tyep: 1, UserName: userName, Content: usercontent }, (data) => {
console.log(data);
}, "json");
});
Java客户端添加header参数
HubConnection hubConnection = HubConnectionBuilder.create("https://example.com/myhub")
.withHeader("Foo", "Bar")
.shouldSkipNegotiate(true)
.withHandshakeResponseTimeout(30*1000)
.build();
Vue SignalR
@microsoft/signalr - npm
@apavelm/vue-signalr - npm
signalr - npm
npm install @aspnet/signalr
npm install @microsoft/signalr // 待测试
*、
<template>
<el-row>
<el-button type="success" @click="sendMsg">发送</el-button>
<el-button type="info" @click="stop">停止</el-button>
</el-row>
</template>
import * as signalR from '@aspnet/signalr'
export default {
data() {
return {
user: {
name: '',
avatar: '',
}
}
},
methods: {
sendMsg() {
this.connection.invoke('SendHeartbeat').catch(function (err) {
return console.error(err)
})
},
stop() {
this.connection.stop()
},
conn() {
var $this = this
var equipment = JSON.parse(localStorage.getItem('small_equipment'))
console.log(`Ln 281 ${localStorage.getItem('small_equipment')};${equipment.equipment_code}`)
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:60548/pushHub?Bearer=123&password=abc", {
skipNegotiation: true, /// 必填
transport: signalR.HttpTransportType.WebSockets, /// 必填
// accessTokenFactory: () => 'Pcl9EWEa3sAY99J86' /// 必填
accessTokenFactory: () => { /// 必填
// 访问令牌
// 必填,后端获取context.Request.Query["access_token"]
// Get and return the access token.
// This function can return a JavaScript Promise if asynchronous
// logic is required to retrieve the access token.
}
})
// .configureLogging(signalR.LogLevel.Information)
.withAutomaticReconnect() // 自动重新连接
.build()
// this.connection.keepAliveIntervalInMilliseconds = 15000 //15s
// this.connection.serverTimeoutInMilliseconds = 30000 //3s
this.connection.on("Receive", function(message) {
var msg = JSON.parse(message)
var u = JSON.parse(msg.content)
$this.user.name = u.real_name
$this.user.avatar = u.avatar
var arrR = []
arrR.splice(0, 0, message)
arrR.push('5')
console.log(`Ln 133 ${JSON.stringify(message)}`)
console.log(`Ln 138 ${arrR}`)
// var url= "http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&spd=1&text=" + encodeURI(message)
// new Audio(url).play()
})
this.connection.onclose(async () => {
var d = new Date()
var hour = (d.getHours() < 10) ? ("0" + d.getHours()) : d.getHours()
var minute = (d.getMinutes() < 10) ? ("0" + d.getMinutes()) : d.getMinutes()
var second = (d.getSeconds() < 10) ? ("0" + d.getSeconds()) : d.getSeconds()
if(this.connection.state === signalR.HubConnectionState.Disconnected){
console.log(`Ln 46 state:${this.connection.state}`)
setInterval(() => {
if($this.connection.state == 0){
$this.conn()
}
}, 3000)
}
console.log(`监听到链接关闭 ${hour}:${minute}:${second}`)
})
this.connection.start()
.then(function () {
console.log("SignalR 连接成功")
}).catch(function (err) {
console.log("SignalR 连接失败")
console.log(err)
})
}
}
}
.NET Core(PushHub.cs)
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using NurseCall.CoreLibrary;
using NurseCall.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NurseCall.WebAPI
{
public class PushHub : Hub
{
private static volatile PushHub instance = null;
private static readonly object lockHelper = new object();
private IHttpContextAccessor _accessor;
public Dictionary<string, List<string>> UserList { get; set; } = new Dictionary<string, List<string>>();
/// <summary>
/// 单体模式返回当前类的实例
/// </summary>
/// <returns></returns>
public static PushHub GetInstance()
{
if (instance == null)
{
lock (lockHelper)
{
if (instance == null)
{
instance = new PushHub();
}
}
}
return instance;
}
public PushHub() { }
public PushHub(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
/// <summary>
/// 客户端连接到集线器时执行操作
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
var dic = _accessor.HttpContext.Request.Headers;
var token = dic["Authorization"].FirstOrDefault();
LogUtils.GetInstance().Info($"Ln 144 有人进来了:{token},{this.Context.ConnectionId}");
var bearer = Convert.ToString(_accessor.HttpContext.Request.Query["Bearer"]);
var access_token = Convert.ToString(_accessor.HttpContext.Request.Query["access_token"]);
var password = Convert.ToString(_accessor.HttpContext.Request.Query["password"]);
var userName = this.Context.User.Identity.Name;
if (string.IsNullOrEmpty(userName))
userName = "admin";
var user = VerificationToken(token);
if (user != null)
{
var userCode = user.data.user_code;
userName = user.data.user_name;
var connectionId = this.Context.ConnectionId;
if (!UserList.ContainsKey(userCode))
{
UserList[userCode] = new List<string>();
UserList[userCode].Add(connectionId);
}
else if (!UserList[userCode].Contains(connectionId))
{
UserList[userCode].Add(connectionId);
}
LogUtils.GetInstance().Info($"Ln 166 有人进来了:{userCode},{userName},{this.Context.ConnectionId}");
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
else
{
LogUtils.GetInstance().Info($"Ln 172 有人进来了:非法用户{token},{this.Context.ConnectionId}");
}
}
/// <summary>
/// 客户端断开连接时执行操作
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public override async Task OnDisconnectedAsync(Exception exception)
{
var dic = _accessor.HttpContext.Request.Headers;
//var token = dic["Authorization"].FirstOrDefault();
await base.OnDisconnectedAsync(exception);
}
}
}
.NET Core(HeartbeatHub.cs)心跳
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebAPI
{
public class HeartbeatHub : Hub<IHeartbeat>
{
public async Task Send(PushMessage data)
{
string aa = "12";
await Clients.All.RecvAsync(data);
}
public async Task SendToUser(PushMessage data)
{
await Clients.Client("").SendToUser(data);
}
public async Task Login(PushMessage data)
{
string aa = "12";
await Clients.All.ReceiveLogin(data);
}
public async Task Logoff(PushMessage data)
{
string aa = "12";
await Clients.All.ReceiveLogoff(data);
}
public async Task SendHeartbeat(PushMessage data)
{
string aa = "12";
await Clients.All.ReceiveHeartbeat(data);
}
public override async Task OnConnectedAsync()
{
//Console.WriteLine("游客[{0}]进入了聊天室", this.Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
//Console.WriteLine("游客[{0}]离开了聊天室", this.Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
}
public interface IHeartbeat
{
Task SendToUser(PushMessage data);
Task RecvAsync(object arg);
Task ReceiveLogin(object arg);
/// <summary>
///
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
Task ReceiveLogoff(object arg);
/// <summary>
/// 接收客户端心跳包(前端响应方法名称)
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
Task ReceiveHeartbeat(object arg);
/// <summary>
/// 给所有客户端发送一个心跳包(前端响应方法名称)
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
Task HeartbeatAsync(int data);
}
}
.NET Core(Startup.cs)
namespace WebAPI
{
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string AllowSpecificOrigins = "AllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{
#region 跨域访问
var urls = Configuration["AppSettings:Cores"].Split(',');
services.AddCors(options =>
{
options.AddPolicy(AllowSpecificOrigins, builder =>
{
builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader();
});
});
#endregion
#region SignalR
services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
//客户端发保持连接请求到服务端最长间隔,默认30秒,改成4分钟,网页需跟着设置connection.keepAliveIntervalInMilliseconds = 12e4;即2分钟
//hubOptions.ClientTimeoutInterval = TimeSpan.FromMinutes(4);
//服务端发保持连接请求到客户端间隔,默认15秒,改成2分钟,网页需跟着设置connection.serverTimeoutInMilliseconds = 24e4;即4分钟
//hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(1);
//hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(15);
//hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
}).AddJsonProtocol(options =>
{
//不更改属性名称的大小写
//options.PayloadSerializerOptions.PropertyNamingPolicy = null;
});
services.AddSingleton<PushHub>();
services.AddSingleton<HeartbeatHub>();
#endregion
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(AllowSpecificOrigins);
//app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<PushHub>("/pushHub", options => { });
endpoints.MapHub<HeartbeatHub>("/heartbeatHub", options => { });//心跳包
});
app.Use(async (context, next) =>
{
var accessToken = context.Request.Query["access_token"];
if (context.Request.Query.TryGetValue("Bearer", out var token))
context.Request.Headers.Add("Authorization", $"Bearer {token}");
await next();
});
}
}
}
*、群聊,群发,组,groupName
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using System.Threading.Tasks;
namespace NurseCall.WebAPI.Hubs
{
public class GroupHub : Hub
{
public GroupHub()
{ }
/// <summary>
/// 添加组,根据客户端的ConnectionId,添加到组名为 groupName 的组中去
/// </summary>
/// <param name="groupName"></param>
/// <returns></returns>
public async Task AddToGroupAsync(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
/// <summary>
/// 添加组,根据客户端的ConnectionId,添加到组名为 groupName 的组中去
/// </summary>
/// <returns></returns>
public async Task Join(string groupName) => await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
/// <summary>
/// 从 groupName 的组中,去除对应的客户端的ConnectionId
/// </summary>
/// <param name="groupName"></param>
/// <returns></returns>
public async Task RemoveFromGroupAsync(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
/// <summary>
/// 从 groupName 的组中,去除对应的客户端的ConnectionId
/// </summary>
/// <returns></returns>
public async Task Remove(string groupName) => await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
/// <summary>
/// 在 groupName 的组中,所有的客户端可以接收到服务端发送的消息
/// </summary>
/// <param name="groupName"></param>
/// <param name="message"></param>
/// <returns></returns>
public async Task SendToGroupAsync(string groupName, PushMessage data)
{
await Clients.Group(groupName).SendAsync(groupName, JsonConvert.SerializeObject(data));
}
/// <summary>
/// 在 groupName 的组中,所有的客户端可以接收到服务端发送的消息
/// </summary>
/// <returns></returns>
public async Task Message() => await Clients.Groups("testGroup").SendAsync("groupMessages", "属于 testGroup 才能接收到数据");
}
}
*、
*、
*、
*、