如何终止 SignalR 连接?

Posted

技术标签:

【中文标题】如何终止 SignalR 连接?【英文标题】:How can I kill a SignalR connection? 【发布时间】:2020-07-04 03:33:22 【问题描述】:

我正在使用 SignalR 在网站上传输数据。但是 SignalR 应该只能在一段时间内发送数据,如果该时间段已过,则应终止连接。

如果请求仍处于挂起状态且未完成,则停止功能 $.connection.hub.stop() 将被取消。但是无论发送了多少数据,这个请求都应该被强制取消。

我如何杀死 SignalR-Connection?

【问题讨论】:

【参考方案1】:

正如您在 Microsoft Documentation about Timeout and keepalive settings 中看到的,您可以在选项中定义 DisconnectTimeout。

例子:

protected void Application_Start(object sender, EventArgs e)

    // Make long-polling connections wait a maximum of 110 seconds for a
    // response. When that time expires, trigger a timeout command and
    // make the client reconnect.
    GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);

    // Wait a maximum of 30 seconds after a transport connection is lost
    // before raising the Disconnected event to terminate the SignalR connection.
    GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30);

    // For transports other than long polling, send a keepalive packet every
    // 10 seconds. 
    // This value must be no more than 1/3 of the DisconnectTimeout value.
    GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(10);

    RouteTable.Routes.MapHubs();

编辑:由于无论如何您都想终止来自客户端的连接,因此您正在谈论 CancellationToken 行为,但不幸的是,这在 SignalR 中仍然不支持,如您所见 @987654322 @ 和here,团队想对SignalR 这样做,但仍然没有任何消息。

【讨论】:

正如我所说,来自前端站点的请求尚未完成,因此前端仍有一些数据要发送到 SignalR-Backend/Hub。所以我正在寻找一个前端解决方案,因为发送了大量的数据,如果经过一段时间,那么无论数据是否已经传输,前端都应该终止连接。你明白我在找什么吗? @Snickbrack 你想通过客户端终止连接,即使你现在正在发送数据,我是对的? @Snickbrack 不要忘记选择您问题的正确答案,这里或其他答案...【参考方案2】:

请阅读this microsoft 关于 Hub 生命周期事件的文档。您可以更改这些设置的默认值,在您的Global.asax 文件中的Application_Start 中设置它们。但是这样你就不能完全控制客户端。因此,您使用 javascript setTimeout 函数并在新用户连接时从服务器端传递时间。它可能是 GlobalHost.Configuration.DisconnectTimeout 或您想要的任何时间。我用演示项目给出了一个完整的例子。实际上我在一个非常大的票务系统中使用这个逻辑来实时持有票。(请阅读所有内联评论)

型号:

public class MyModel

    public int Id  get; set; 

    public string Name  get; set; 


    public static string Send(MyModel my)
    
        //Do Somthing           
        return $"Data Sending to my.Name...";
    
    public static string Stop(string name)
    
        //Do Somthing

        return $"ForceStop name.";
    
    public static string Delete()
    
        //Do Somthing

        return "Deleted";
    

中心:

[HubName("myHub")]
public class MyHub : Hub

    int connectionTimeOut = 10;//sec

    [HubMethodName("connect")]
    public void Connect()
      
            //apply logic if any when user connected or reload page
            //set connection Time Out as you need
        connectionTimeOut= 10;// GlobalHost.Configuration.DisconnectTimeout

       Clients.Client(Context.ConnectionId).onNewUserConnected(connectionTimeOut);
    
    [HubMethodName("startSendingServer")]
    public void StartSending(int id, string name)//pass anything you need
    
        //apply logic if any when start sending data

        var my = new MyModel
        
            Id = id,
            Name = name
        ;
        var status = MyModel.Send(my);//example

        var result = new
        
            status,
            name
        ;

        Clients.Client(Context.ConnectionId).startSendingClient(result);

    

    [HubMethodName("forceStopServer")]
    public void ForceStop(string name)//pass anything you need
    
        //apply logic if any when force stop sending data
        var status = MyModel.Stop(name);
        Clients.Client(Context.ConnectionId).forceStopClint(status);
    


    public override Task OnDisconnected(bool stopCalled)
    

        //apply logic if any when connection Disconnected

        var status = MyModel.Delete();//example
        if (stopCalled)
        
            //  status=String.Format("Client 0 explicitly closed the connection.", Context.ConnectionId)
            //your code here
        
        else
        
            // status=String.Format("Client 0 timed out .", Context.ConnectionId);
            //your code here
            //Clients.Client(Context.ConnectionId).onUserDisconnected(status);
        

        return base.OnDisconnected(stopCalled);
    



测试视图:

<div class="row">
    <div class="col-md-12">
        <h1> Status: <span id="hubStatus"></span></h1>
        <br />
        <h4> Countdown : <span id="counter"></span></h4>
        <br />

        <button id="btnHub" class="btn btn-primary btn-lg">Start Sending Data</button>
    </div>
</div>
@section scripts
    <script src="~/Scripts/app/hub.js"></script>

hub.js:

var proxyTimer = null;
var sendTimeLimit = 1;//sec
var sessionTime = sendTimeLimit * 1000;

$(function () 
    var myProxy = $.connection.myHub;
    $.connection.hub.start().done(function () 
        registerServerEvents(myProxy);
    );

    clientMethods(myProxy);
);

function registerServerEvents(proxyHub) 
    proxyHub.server.connect();
    $(document).on("click", "#btnHub", function (e) 

        $("#hubStatus").html("Sending..");
        $("#btnHub").text("Count Down Start...");

        //Logic Before start sending data.
        var id = 1;
        var name = "AzR";        
        proxyHub.server.startSendingServer(id,name);

       // $.connection.hub.disconnected(function () 
      //  setTimeout(function ()  $.connection.hub.start(); , 5000); // Restart connection after 5 seconds.
       //);

        $.connection.hub.disconnected(function () 
            $("#hubStatus").html("Disconnected");// you can restart on here.     
            $("#btnHub").text("Stat Again after reload window");

        );

    );




function clientMethods(proxyHub) 

    //proxyHub.on('onConnected', function (sendTimeLimit) 
    //    sendTimeLimit = sendTimeLimit;
    //);

    proxyHub.on('onNewUserConnected', function (serverItem) 
        sendTimeLimit = serverItem;
        sessionTime = sendTimeLimit * 1000;
    );


    proxyHub.on('startSendingClient', function (serverItem) 

        //Logic after start sending data.
        var name = serverItem.name;
        var status = serverItem.status;
        $("#hubStatus").html(status);
        $("#counter").html(sendTimeLimit);
        timeCounter();
        startTimer(proxyHub, name );
    );

    proxyHub.on('forceStopClint', function (serverItem) 


        clearClintPendingTask(serverItem);//Logic before proxy stop.
        $("#btnHub").text("Force Stop...");
        $.connection.hub.stop();
    );

    proxyHub.on('onUserDisconnected', function (serverItem) 
        //Logic after proxy Disconnected (time out).
        $("#hubStatus").html(serverItem);
        $("#btnHub").text("Stat Again after reload window");
   );


//Logic before proxy stop.
function clearClintPendingTask(status) 
    //do all you need
    $("#hubStatus").html(status); 
    stopTimer();


function startTimer(proxyHub,data) 
    stopTimer();
    proxyTimer = setTimeout(function () 
        proxyHub.server.forceStopServer(data);
    , sessionTime);


function stopTimer() 
    if (proxyTimer) 
        clearTimeout(proxyTimer);
        proxyTimer = null;
    


function timeCounter() 
    var counter = sendTimeLimit;
    var interval = setInterval(function () 
        counter--;
        $("#counter").html(counter);
        if (counter == 0) 
            //Do something
            $("#counter").html("Countdown ended!");
            // Stop the counter
            clearInterval(interval);
        
    , 1000);

(已测试)

【讨论】:

【参考方案3】:

您需要定义超时。在服务器上你可以设置 DisconnectTimeout,像这样:

GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromMinutes(30);

https://zzz.buzz/2016/05/11/setting-timeout-for-signalr-for-easier-debugging/

【讨论】:

正如我所说,来自前端站点的请求尚未完成,因此前端仍有一些数据要发送到 SignalR-Backend/Hub。所以我正在寻找前端解决方案。【参考方案4】:

更新编辑,请参阅下面的选项 3所有其他人都依赖超时,我发布了强制断开连接。

如果您正在尝试强制断开连接 - 您可以获取已连接用户的列表并在服务器端调用 ForceLogOut 函数,我在代码项目的某个地方看到了这个,希望对您有所帮助。如果您只想强制注销/杀死某些用户,只需循环并仅终止该连接即可。

服务器端


public class User

    public string Name  get; set; 
    public HashSet<string> ConnectionIds  get; set; 


public class ExtendedHub : Hub
        
   private static readonly ConcurrentDictionary<string, User> ActiveUsers  = 
      new ConcurrentDictionary<string, User>(StringComparer.InvariantCultureIgnoreCase);
    public IEnumerable<string> GetConnectedUsers()
    
        return ActiveUsers.Where(x => 

            lock (x.Value.ConnectionIds)
            
                return !x.Value.ConnectionIds.Contains
                        (Context.ConnectionId, StringComparer.InvariantCultureIgnoreCase);
            

        ).Select(x => x.Key);
               

    public void forceLogOut(string to)
    
        User receiver;
        if (ActiveUsers.TryGetValue(to, out receiver))
        
            IEnumerable<string> allReceivers;
            lock (receiver.ConnectionIds)
            
                allReceivers = receiver.ConnectionIds.Concat(receiver.ConnectionIds);      
            

            foreach (var cid in allReceivers)
            
             // ***************** log out/KILL connection for whom ever your want here
                Clients.Client(cid).Signout();
            
        
    


客户端

 // 1- Save your connection variable when you start it, and later on you can use it to stop.
var myHubProxy = $.connection.myHub 
// 2- Use it when you need to stop it, IF NOT YOU WILL GET AN ERROR
myHubProxy.client.stopClient = function() 
    $.connection.hub.stop();
;

// With a button for testing
$('#SomeButtonKillSignalr').click(function () 
            $.connection.hub.stop();                
        );

使用选项 3 更新:根据请求...其他解决方案依赖于超时,但您也可以直接强制通过自行处理连接 p>

我打开 SignalR 代码,在里面你可以看到 DisposeAndRemoveAsync 客户端连接的实际终止。

1- 您可以通过您的连接修改或致电DisposeAndRemoveAsync

2- 然后拨打RemoveConnection(connection.ConnectionId);

public async Task DisposeAndRemoveAsync(HttpConnectionContext connection)
        
            try
            
                // this will force it
                await connection.DisposeAsync();
            
            catch (IOException ex)
            
                _logger.ConnectionReset(connection.ConnectionId, ex);
            
            catch (WebSocketException ex) when (ex.InnerException is IOException)
            
                _logger.ConnectionReset(connection.ConnectionId, ex);
            
            catch (Exception ex)
            
                _logger.FailedDispose(connection.ConnectionId, ex);
            
            finally
            
                // Remove it from the list after disposal so that's it's easy to see
                // connections that might be in a hung state via the connections list
                RemoveConnection(connection.ConnectionId);
            
        

注意,完成后请自行清理。

【讨论】:

正如我所说,$.connection.hub.stop()-Function 会引发错误,因为请求尚未完全发送到后端。因此,我正在寻找一种解决方案,即使存在正在运行的请求,也会终止当前活动的连接。

以上是关于如何终止 SignalR 连接?的主要内容,如果未能解决你的问题,请参考以下文章

如何从angularJs连接signalR

如何确定与 SignalR 客户端的服务器断开连接?

SignalR:服务器如何建立与客户端的连接?

使用一个连接发送并发 SignalR 消息

SignalR:检测客户端上的连接状态

SignalR Client 开始连接时如何设置用户?