问题
I am using SignalR to transfer data on a website. But SignalR should only be able to send data for a period of time and if the time period has passed the connection should be killed.
The Stop-Function $.connection.hub.stop()
is cancelled if a request is still pending and is not completed. But this request should be forced to cancel no matter how much data has been sent.
How can I kill a SignalR-Connection?
回答1:
As you can see in this Microsoft Documentation about Timeout and keepalive settings you can define the DisconnectTimeout in the options.
Example:
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();
}
Edit: Since you want to kill the connection from the client no matter what, you are talking about a CancellationToken
behavior but unfortunately this is still not supported in SignalR as you can see here and here, the team wants to do that to SignalR
but still there is no news about it.
回答2:
Please read this microsoft document about Hub lifetime event. You can change the default values for these settings, set them in Application_Start
in your Global.asax
file. But this way you can't full control client side. So you use javascript setTimeout
function and pass the time form server end when a new user connect.it's may be GlobalHost.Configuration.DisconnectTimeout
or any time you want. I give a full example with demo project. Actually i use this logic in a very large ticketing system for real-time holding ticket.(please read all inline comment)
Model:
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";
}
}
Hub:
[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);
}
}
TestView:
<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);
}
(Tested)
回答3:
You need to define a timeout. On the server you can set DisconnectTimeout, like this:
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromMinutes(30);
https://zzz.buzz/2016/05/11/setting-timeout-for-signalr-for-easier-debugging/
回答4:
Updated Edit, please see Option 3 below. All the others are relying on timeout, I posted a forced disconnect.
If you are trying a Force Disconnect -- you can get the list of the Connected Users and call the ForceLogOut
Function on the server side, I saw this somewhere on code project, I hope it helps. If you only want to forceLogout/kill some of the users, just loop through and kill that connection only.
Server Side
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();
}
}
}
}
Client Side
// 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();
});
Updated with Option 3: based on request... the other solutions rely on time out, but you can also force it directly by disposing the connection yourself
I opened the SignalR code up, and inside you can see DisposeAndRemoveAsync
the actual termination of a client connection.
1- You can modify or call DisposeAndRemoveAsync
with your connection.
2- Then call 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);
}
}
Caution, do any clean up yourself when this is done.
来源:https://stackoverflow.com/questions/60817489/how-can-i-kill-a-signalr-connection