.net core signalR 服务端断开连接

{ }
{ }
*:first-child { }
*:last-child { }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
h6:first-child { }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
{ }
:first-child { }
:last-child { }
{ }
:first-child { }
:last-child { }
{ }
{ }
code { }
{ }
{ }
{ }
{ }
:first-child { }
:last-child { }
{ }
{ }
{ }
{ }
{ }
{ }

{ color: rgba(255, 255, 255, 1); padding: 5px; background-color: rgba(43, 102, 149, 1); border: 1px solid rgba(255, 255, 255, 1); border-radius: 5px; font-size: 24px; margin-top: 15px; margin-bottom: 15px }

environment

.net core 3.1

前言

项目要求弄一个即时通讯

由于.net 已经集成了websocket通讯中间件-signalR,并且运作的效率还可以,为减少开发周期便使用了signalR作为websocket连接管理中间件。

既然是通讯,那就要解决最基本的连接问题。

如何连?以什么作为凭证?

既然是用户与用户之间的通信,那边应该用用户标识作为凭证进行连接,无标识的连接(游客)将毫无意义。

然后一般情况下(参考qq/微信),通讯时是不允许有一个用户多个通讯连接的。既然如此,那便要考虑一个用户二次连接的问题:

在这里,我们选择了第二次登录,将之前登录的用户强制退出。

退出的方式有两种:

  • 客户端自己断开
  • 服务端强制客户断开

随意一点的(自己玩的那种)就客户端自己断开就足够了,但如果是正式的项目的话还是要强制断开,保证操作的完整性。

好,既然要强制断开,那便是服务端移除连接.

先说结果:

一、在会话中断开

会话中是指服务端已获取到连接

使用场景:指客户发送一个[关闭指令],然后服务端自动关闭连接

Microsoft.AspNetCore.SignalR.Hub中有一个public HubCallerContext Context { get; set; }表示调用方的上下文。

然后在Microsoft.AspNetCore.SignalR.HubCallerContext中提供了一个方法:

//
// Summary:
// Aborts the connection.
public abstract void Abort();// --> 使中止,中断连接

故只需要在对应的方法块中使用Context.Abort();便可断开连接

二、在会话外断开

会话外是指服务端还未获取到连接

使用场景:用户在小米登录了账号然后又在华为登录了账号,此时小米的账号应该被强制下线。

根据场景触发事件是华为登录了账号,此时你不清楚小米的连接

于是我们要使用一个集合保留设备-》连接的映射关系:

ConcurrentDictionary<string, HubCallerContext> _connections // 此处key为连接凭证,value为此连接对应的上下文

注:HubCallerContext是一个单例对象,即一个连接中的所有HubCallerContext都是一样的,故此方法可行

然后在连接开启时即Hub.OnConnectedAsync时维护此集合,若是存在一个用户对应多个连接,你还需要维护一个用户->连接凭证的集合

然后在华为连接触发OnConnectedAsync时,检查此集合是否已存在此凭证,若存在则取出对应上下文-HubCallerContext调用Abort进行强制退出

三、源码分析

ps:若是你只是想知道服务端怎么强制断开连接的话,下面就不用看了

由于百度、Google都没搜到需要的结果,只好自己来了...

强制断开即是服务端移除连接

首先,想要释放便得知道连接保存在哪

自己写过websocket的应该都知道,当连接建立后,服务端需要将连接进行保存避免自动释放,那么signalR既然是封装了websocket,那么必然也存在类似的操作

贴一下signalR service注册部分: Microsoft.Extensions.DependencyInjection.SignalRDependencyInjectionExtensions

services.TryAddSingleton<SignalRMarkerService>();
services.TryAddSingleton<SignalRCoreMarkerService>();

services.TryAddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));

services.TryAddSingleton(typeof(IHubProtocolResolver), typeof(DefaultHubProtocolResolver));

services.TryAddSingleton(typeof(IHubContext<>), typeof(HubContext<>));

services.TryAddSingleton(typeof(IHubContext<, >), typeof(HubContext<, >));

services.TryAddSingleton(typeof(HubConnectionHandler<>), typeof(HubConnectionHandler<>));

services.TryAddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));

services.TryAddSingleton(typeof(HubDispatcher<>), typeof(DefaultHubDispatcher<>));

services.TryAddScoped(typeof(IHubActivator<>), typeof(DefaultHubActivator<>));

services.AddAuthorization();

SignalRServerBuilder signalRServerBuilder = new SignalRServerBuilder(services);

signalRServerBuilder.AddJsonProtocol();

先看app使用hub的地方:

app.UseEndpoints(endpoints =>
{
endpoints.MapHub<XxxHub>("/xxxHub");
});

navigation->Microsoft.AspNetCore.Builder.HubEndpointRouteBuilderExtensions.HubEndpointRouteBuilderExtensions

public static class HubEndpointRouteBuilderExtensions
{
public static HubEndpointConventionBuilder MapHub<THub>(this IEndpointRouteBuilder endpoints, string pattern) where THub : Hub
{
return endpoints.MapHub<THub>(pattern, null);
}
public static HubEndpointConventionBuilder MapHub&lt;THub&gt;(this IEndpointRouteBuilder endpoints, string pattern, Action&lt;HttpConnectionDispatcherOptions&gt; configureOptions) where THub : Hub
{
if (endpoints.ServiceProvider.GetService&lt;SignalRMarkerService&gt;() == null)
{
throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.");
}
HttpConnectionDispatcherOptions httpConnectionDispatcherOptions = new HttpConnectionDispatcherOptions();
configureOptions?.Invoke(httpConnectionDispatcherOptions);
ConnectionEndpointRouteBuilder connectionEndpointRouteBuilder = endpoints.MapConnections(pattern, httpConnectionDispatcherOptions, delegate(IConnectionBuilder b)
{
b.UseHub&lt;THub&gt;();
});
object[] attributes = typeof(THub).GetCustomAttributes(inherit: true);
connectionEndpointRouteBuilder.Add(delegate(EndpointBuilder e)
{
object[] array = attributes;
foreach (object item in array)
{
e.Metadata.Add(item);
}
e.Metadata.Add(new HubMetadata(typeof(THub)));
});
return new HubEndpointConventionBuilder(connectionEndpointRouteBuilder);
}

}

key code : b.UseHub<THub>();

navigation -> Microsoft.AspNetCore.SignalR.SignalRConnectionBuilderExtensions.SignalRConnectionBuilderExtensions

public static class SignalRConnectionBuilderExtensions
{
public static IConnectionBuilder UseHub<THub>(this IConnectionBuilder connectionBuilder) where THub : Hub
{
if (connectionBuilder.ApplicationServices.GetService(typeof(SignalRCoreMarkerService)) == null)
{
throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.");
}
return connectionBuilder.UseConnectionHandler<HubConnectionHandler<THub>>();
}
}

navigation -> Microsoft.AspNetCore.Connections.ConnectionBuilderExtensions.ConnectionBuilderExtensions

public static IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler
{
TConnectionHandler handler = ActivatorUtilities.GetServiceOrCreateInstance<TConnectionHandler>(connectionBuilder.ApplicationServices);
return connectionBuilder.Run((ConnectionContext connection) => handler.OnConnectedAsync(connection));
}

OnConnectedAsync!!!,见名思以当连接打开时触发,这个应该就是关键点了

navigation -> Microsoft.AspNetCore.Connections.ConnectionHandler

public override async Task OnConnectedAsync(ConnectionContext connection)
{
IList<string> list = _hubOptions.SupportedProtocols ?? _globalHubOptions.SupportedProtocols;
if (list == null || list.Count == 0)// 未配置连接协议
{
throw new InvalidOperationException("There are no supported protocols");
}
// 超时时间
TimeSpan timeout = _hubOptions.HandshakeTimeout ?? _globalHubOptions.HandshakeTimeout ?? HubOptionsSetup.DefaultHandshakeTimeout;
// 连接上下文配置
HubConnectionContextOptions contextOptions = new HubConnectionContextOptions
{
KeepAliveInterval = (_hubOptions.KeepAliveInterval ?? _globalHubOptions.KeepAliveInterval ?? HubOptionsSetup.DefaultKeepAliveInterval),
ClientTimeoutInterval = (_hubOptions.ClientTimeoutInterval ?? _globalHubOptions.ClientTimeoutInterval ?? HubOptionsSetup.DefaultClientTimeoutInterval),
StreamBufferCapacity = (_hubOptions.StreamBufferCapacity ?? _globalHubOptions.StreamBufferCapacity ?? 10),
MaximumReceiveMessageSize = _maximumMessageSize
};
Log.ConnectedStarting(_logger);
// **** 构建连接对象
HubConnectionContext connectionContext = new HubConnectionContext(connection, contextOptions, _loggerFactory);
IReadOnlyList&lt;string&gt; supportedProtocols = (list as IReadOnlyList&lt;string&gt;) ?? list.ToList(); // 然后进行握手连接
if (await connectionContext.HandshakeAsync(timeout, supportedProtocols, _protocolResolver, _userIdProvider, _enableDetailedErrors))
{ // 握手成功
try
{
await _lifetimeManager.OnConnectedAsync(connectionContext);
await RunHubAsync(connectionContext);
}
finally
{
Log.ConnectedEnding(_logger);
await _lifetimeManager.OnDisconnectedAsync(connectionContext);
}
}

}

主要看握手成功之后的内容:

try
{
await _lifetimeManager.OnConnectedAsync(connectionContext);
await RunHubAsync(connectionContext);
}
finally
{
Log.ConnectedEnding(_logger);
await _lifetimeManager.OnDisconnectedAsync(connectionContext);
}

首先可以看到在finally中调用了OnDisconnectedAsync,见名思以我觉得它应该就是我们要找的释放连接,查看定义:

private readonly HubLifetimeManager<THub> _lifetimeManager;

而且通过之前的注册来看此成员是一个单例,感觉非常符合,继续查看定义: Microsoft.AspNetCore.SignalR.HubLifetimeManager

public abstract class HubLifetimeManager<THub> where THub : Hub
{
public abstract Task OnConnectedAsync(HubConnectionContext connection);
public abstract Task OnDisconnectedAsync(HubConnectionContext connection);

public abstract Task SendAllAsync(string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendAllExceptAsync(string methodName, object[] args, IReadOnlyList&lt;string&gt; excludedConnectionIds, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendConnectionAsync(string connectionId, string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendConnectionsAsync(IReadOnlyList&lt;string&gt; connectionIds, string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendGroupAsync(string groupName, string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendGroupsAsync(IReadOnlyList&lt;string&gt; groupNames, string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendGroupExceptAsync(string groupName, string methodName, object[] args, IReadOnlyList&lt;string&gt; excludedConnectionIds, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendUserAsync(string userId, string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task SendUsersAsync(IReadOnlyList&lt;string&gt; userIds, string methodName, object[] args, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default(CancellationToken));

public abstract Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default(CancellationToken));

}

到此有点小懵逼了???,这里的方法都是返回Task,但我释放连接需要HubConnectionContext,岂不是无解???

虽然很像但是不是就很郁闷,既然_lifetimeManager做不了,就只能去看:

await RunHubAsync(connectionContext);
private async Task RunHubAsync(HubConnectionContext connection)

{

try

{

await _dispatcher.OnConnectedAsync(connection);

}

catch (Exception exception)

{

Log.ErrorDispatchingHubEvent(_logger, "OnConnectedAsync", exception);

await SendCloseAsync(connection, exception, allowReconnect: false);

return;

}

try

{

await DispatchMessagesAsync(connection);

}

catch (OperationCanceledException)

{

}

catch (Exception exception2)

{

Log.ErrorProcessingRequest(_logger, exception2);

await HubOnDisconnectedAsync(connection, exception2);

return;

}

await HubOnDisconnectedAsync(connection, null);

}

一个一个来,先看await _dispatcher.OnConnectedAsync(connection);

navigation -> Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher

public override async Task OnConnectedAsync(HubConnectionContext connection)
{
IServiceScope scope = null;
try
{
// 通过 service 拿到了THub
scope = _serviceScopeFactory.CreateScope();
IHubActivator<THub> hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
THub hub = hubActivator.Create();
try
{
// 然后通过hub 和 连接进行初始化
InitializeHub(hub, connection);
await hub.OnConnectedAsync();
}
finally
{
hubActivator.Release(hub);
}
}
finally
{
await scope.DisposeAsync();
}
}
private void InitializeHub(THub hub, HubConnectionContext connection)

{

hub.Clients = new HubCallerClients(_hubContext.Clients, connection.ConnectionId); // 只用到了ConnectionId显然不是

hub.Context = connection.HubCallerContext; // 这个就有点可疑了

hub.Groups = _hubContext.Groups;// 只用到了分组应该也不是

}

查看HubConnectionContext的构造方法看看HubCallerContext是如何被构造的:

public HubConnectionContext(ConnectionContext connectionContext, HubConnectionContextOptions contextOptions, ILoggerFactory loggerFactory)
{
...
HubCallerContext = new DefaultHubCallerContext(this);
...
}

navigation -> Microsoft.AspNetCore.SignalR.Internal.DefaultHubCallerContext

internal class DefaultHubCallerContext : HubCallerContext
{
private readonly HubConnectionContext _connection;
public override string ConnectionId =&gt; _connection.ConnectionId;

public override string UserIdentifier =&gt; _connection.UserIdentifier;

public override ClaimsPrincipal User =&gt; _connection.User;

public override IDictionary&lt;object, object&gt; Items =&gt; _connection.Items;

public override IFeatureCollection Features =&gt; _connection.Features;

public override CancellationToken ConnectionAborted =&gt; _connection.ConnectionAborted;

public DefaultHubCallerContext(HubConnectionContext connection)
{
_connection = connection;
} public override void Abort()
{
// ************************
_connection.Abort();
}

}

Abort -> 使中止 推测是中止连接,而且通过源码可知调的是HubConnectionContext.Abort.

Hub中的定义:

public HubCallerContext Context
{
get
{
CheckDisposed();
return _context;
}
set
{
CheckDisposed();
_context = value;
}
}

通过测试结果可知,这个便是服务器中断连接的方法了

[Over~]

.net core signalR 服务端强制中断用户连接的更多相关文章

  1. 记录一次SignalR服务端实现过程

    前言:最近手上一个项目需要后端实时推送数据到前端,第一个想到的就是微软的SignalR,由于之前都是平时没事写的Demo,没有用到实际项目中,这次恰好用到了,因此记录下来整个实现过程(网上也有很多类似 ...

  2. SignalR入门之多平台SignalR服务端

    之前创建SignalR服务端是基于Web应用程序而言的.那么能不能把SignalR服务端做成控制台应用程序.Winform或windows服务呢? 答案是肯定的. 之前尽管看起来好像是IIS和ASP. ...

  3. 创建自托管的SignalR服务端

    微软官方例子地址:http://www.asp.net/signalr/overview/deployment/tutorial-signalr-self-host 1.说明: SignalR服务端可 ...

  4. asp.net core webapi 服务端配置跨域

    在前后端分离开发中服务端仅仅只为前端提供api接口,并且前后端往往单独部署,此时就会出现浏览器跨域问题.asp.net core提供了简单优雅的解决方案. 在startup文件的Configure添加 ...

  5. HMS Core分析服务助您掌握用户分层密码,实现整体收益提升

    随着市场愈发成熟,开发者从平衡收益和风险的角度开始逐步探索混合变现的优势,内购+广告就是目前市场上混合变现的主要方式之一. 对于混合变现模式,您是否有这样的困惑: 如何判断哪些用户更愿意看广告.哪些用 ...

  6. RedHat下安装Telnet服务端及客户端远程连接配置

    Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式.它为用户提供了在本地计算机上完成远程主机工作的能力. 配置之前请确保网络连通,如防火墙影响连接,请先关 ...

  7. signalr服务端-基础搭建

    signalr 支持 iis托管.winform.windowsservices.wpf 托管 这里我采用winfrom托管 首先画一个这样的窗体 在服务项目通过项目管理包安装signalr类库 安装 ...

  8. .net core api服务端跨域配置

    第1步:添加包引用(.net core 2.2 已自带此包,可跳过此步骤) Install-Package Microsoft.AspNetCore.Cors 第2步:在Startup.cs文件的Co ...

  9. WPF创建SignalR服务端(转)

    在网上看到了一个帖子,比较详细,博主写的很好. 地址:http://blog.csdn.net/lordwish/article/details/51786200

随机推荐

  1. IE浏览器查看星号密码

    用CHROME打开保存密码的网页,F12,右击"密码框"检查,编辑属性:password改为passw(只要不是password即可)即可显示密码

  2. 如何让Spring Boot 的配置动起来?

    前言 对于微服务而言配置本地化是个很大的鸡肋,不可能每次需要改个配置都要重新把服务重新启动一遍,因此最终的解决方案都是将配置外部化,托管在一个平台上达到不用重启服务即可一次修改多处生效的目的. 但是对 ...

  3. YARN学习总结之环境搭建

    Yarn环境搭建(基于hadoop-2.6.0-cdh5.7.0 伪分布) 1)配置文件 etc/hadoop/mapred-site.xml: <configuration> <p ...

  4. SpringBoot时代背景

    微服务 James Lewis Martin Fowler 2014年提出微服务完整概念,https://martinfowler.com/microservices/ In short, the m ...

  5. 深入刨析tomcat 之---第8篇 how tomcat works 第11章 11.9应用程序,自定义Filter,及注册

    writed by 张艳涛, 标签:全网独一份, 自定义一个Filter 起因:在学习深入刨析tomcat的学习中,第11章,说了调用过滤链的原理,但没有给出实例来,自己经过分析,给出来了一个Filt ...

  6. @ControllerAdvice全局异常处理不起作用原因及解决办法

    这段时间使用springboot搭建基础框架,作为springboot新手,各种问题都有. 当把前端框架搭建进来时,针对所有controller层的请求,所发生的异常,需要有一个统一的异常处理,然后返 ...

  7. 快速设置 JAVA_HOME

    快速设置 JAVA_HOME %SystemRoot%\System32\rundll32.exe sysdm.cpl,EditEnvironmentVariables

  8. SQL SERVER Date列和Time列合并成一列处理报表数据

    问题原由: intouch项目中,利用intouch脚本来存储数据时,存入的时间格式为:date,time分开存储.在报表需求中,有需要利用查询两个时间段之间的数据. 问题解决: 1.直接写脚本(写出 ...

  9. ifix中嵌入3d模型初探(一)

    在ifix项目中插入3d模型,是当前工控上位机的一个发展趋势,故而我也来尝尝鲜.利用现有条件,初步打算完成一个工厂俯视3d全景. 基本思路:利用webbrowser+3dmax+three.js来嵌入 ...

  10. 利用 cgroup 的 cpuset 控制器限制进程的 CPU 使用

    最近在做一些性能测试的事情,首要前提是控制住 CPU 的使用量.最直观的方法无疑是安装 Docker,在每个配置了参数的容器里运行基准程序. 对于计算密集型任务,在只限制 CPU 的需求下,直接用 L ...