SignalR:客户端和服务器都 "reconnect" 但推送未到达客户端
SignalR: Client and server both "reconnect" but pushes don't reach client
我在 Azure 上有一个 C# SignalR 客户端 (2.2) 和 ASP.NET MVC SignalR 服务器。当在服务器端创建一个新的 "Entity" 时,它会使用以下内容向客户端推送一个简单的通知:
public static class EntityHubHelper
{
private static readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<EntityHub>();
public static void EntityCreated(IdentityUser user, Entity entity)
{
_hubContext.Clients.User(user.UserName).EntityCreated(entity);
}
}
[Authorize]
public class EntityHub : Hub
{
// Just tracing overrides for OnConnected/OnReconnected/OnDisconnected
}
客户端或服务器偶尔会重新连接,这是预料之中的,但我看到了两个都重新连接(例如重新启动网络服务器),但随后客户端停止获取数据的情况。
这似乎是在 1-2 天没有数据被推送之后发生的,然后最后一次推送被错过了。
我们的客户追踪:
15/08/02 03:57:23 DEBUG SignalR: StateChanged: Connected -> Reconnecting
15/08/02 03:57:28 DEBUG SignalR: Error: System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
15/08/02 03:57:31 DEBUG SignalR: Error: System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
15/08/02 03:57:47 DEBUG SignalR: StateChanged: Reconnecting -> Connected
15/08/02 03:57:47 INFO SignalR OnReconnected
我们的服务器跟踪:
8/2/2015 3:57:57 AM [SignalR][OnReconnected] Email=correspondinguser@example.com, ConnectionId=ff4e472b-184c-49d4-a662-8b0e26da43e2
我使用服务器默认的保持活动和超时(10 秒和 30 秒)并且它通常使用 websockets(在 Azure 上启用,标准所以没有限制)。
我有两个问题:
(1) 在 websocket 的情况下,客户端如何发现服务器已经重启(在这种情况下,它会失去对所述客户端存在的记忆)?服务器的 10s/30s 设置是否在初始连接期间被下推,并且客户端在 30s 后决定服务器消失?
(2) 这种情况如何调试?有什么方法可以证明客户端实际上仍在接收 keepalive,所以我知道我在其他地方遇到了一些灾难性问题?
经过各种测试和修复后,当从用户映射到连接 ID 时,问题似乎出在 IUserIdProvider 中。使用 SignalR 消息添加客户端发起的保活显示客户端和服务器确实已经重新连接,并且连接保持健康,但是从服务器推送到客户端的消息在 1-2 天后进入黑洞,可能与网站 publishing/appdomain 刷新参与。
我将 IUserIdProvider 替换为 SQL Azure (various options explained here) using this user presence sample recommended by @davidfowl in this post,并根据我现有的 user/auth 方案对其进行了定制。但是,它需要一个PresenceMonitor.cs 中的一些额外更改以提高可靠性:
- 我不得不将
periodsBeforeConsideringZombie
从 3 增加到 6,因为它在 30 秒时删除了 "zombie" 连接,而它们直到 50 秒左右才会断开连接。这意味着连接有时会在 30-50 秒范围内的某个地方重新连接,并且不会在数据库中进行跟踪。
- 我必须修复在数据库中找不到的心跳跟踪连接的处理。
样本在UserPresence.Check()
中有如下代码:
// Update the client's last activity
if (connection != null)
{
connection.LastActivity = DateTimeOffset.UtcNow;
}
else
{
// We have a connection that isn't tracked in our DB!
// This should *NEVER* happen
// Debugger.Launch();
}
然而,即使 periodsBeforeConsideringZombie
在 6 . 这是因为集线器的 OnConnected 事件有时可能会有点慢,所以如果你的 10 秒计时器处理程序是 "lucky".
,你会在心跳列表中看到一个新连接
我在 UserPresence
中使用此代码来为连接提供两个计时器滴答,或者在 10 秒到 20 秒之间,具体取决于计时器 "luck",以触发 OnConnected。如果它仍然没有被数据库跟踪,我会断开它,以便客户端再次连接(处理 OnClosed)并且不是消息的黑洞(因为我为用户循环数据库连接以推送消息)。
private HashSet<string> notInDbReadyToDisconnect = new HashSet<string>();
private void Check()
{
HashSet<string> notInDbReadyToDisconnectNew = new HashSet<string>();
...
else
{
// REMOVED: // We have a connection that isn't tracked in our DB!
// REMOVED: // This should *NEVER* happen
// REMOVED: // Debugger.Launch();
string format;
if (notInDbReadyToDisconnect.Contains(trackedConnection.ConnectionId))
{
trackedConnection.Disconnect();
format = "[SignalR][PresenceMonitor] Disconnecting active connection not tracked in DB (#2), ConnectionId={0}";
}
else
{
notInDbReadyToDisconnectNew.Add(trackedConnection.ConnectionId);
format = "[SignalR][PresenceMonitor] Found active connection not tracked in DB (#1), ConnectionId={0}";
}
}
...
notInDbReadyToDisconnect = notInDbReadyToDisconnectNew;
...
}
它为单个服务器完成工作,但 HashSet 可能需要移动到数据库以处理横向扩展。
经过这一切,一切都非常可靠,我的服务器推送代码仍然非常简单:
public static class EntityHubHelper
{
private static readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<EntityHub>();
public static void EntityCreated(User user, Entity entity)
{
List<string> connectionIds = user.PushConnections.Select(c => c.ConnectionId).ToList();
_hubContext.Clients.Clients(connectionIds).EntityCreated(entity);
}
}
我在 Azure 上有一个 C# SignalR 客户端 (2.2) 和 ASP.NET MVC SignalR 服务器。当在服务器端创建一个新的 "Entity" 时,它会使用以下内容向客户端推送一个简单的通知:
public static class EntityHubHelper
{
private static readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<EntityHub>();
public static void EntityCreated(IdentityUser user, Entity entity)
{
_hubContext.Clients.User(user.UserName).EntityCreated(entity);
}
}
[Authorize]
public class EntityHub : Hub
{
// Just tracing overrides for OnConnected/OnReconnected/OnDisconnected
}
客户端或服务器偶尔会重新连接,这是预料之中的,但我看到了两个都重新连接(例如重新启动网络服务器),但随后客户端停止获取数据的情况。
这似乎是在 1-2 天没有数据被推送之后发生的,然后最后一次推送被错过了。
我们的客户追踪:
15/08/02 03:57:23 DEBUG SignalR: StateChanged: Connected -> Reconnecting
15/08/02 03:57:28 DEBUG SignalR: Error: System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
15/08/02 03:57:31 DEBUG SignalR: Error: System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server ---> System.Net.WebException: The remote server returned an error: (500) Internal Server Error.
15/08/02 03:57:47 DEBUG SignalR: StateChanged: Reconnecting -> Connected
15/08/02 03:57:47 INFO SignalR OnReconnected
我们的服务器跟踪:
8/2/2015 3:57:57 AM [SignalR][OnReconnected] Email=correspondinguser@example.com, ConnectionId=ff4e472b-184c-49d4-a662-8b0e26da43e2
我使用服务器默认的保持活动和超时(10 秒和 30 秒)并且它通常使用 websockets(在 Azure 上启用,标准所以没有限制)。
我有两个问题:
(1) 在 websocket 的情况下,客户端如何发现服务器已经重启(在这种情况下,它会失去对所述客户端存在的记忆)?服务器的 10s/30s 设置是否在初始连接期间被下推,并且客户端在 30s 后决定服务器消失?
(2) 这种情况如何调试?有什么方法可以证明客户端实际上仍在接收 keepalive,所以我知道我在其他地方遇到了一些灾难性问题?
经过各种测试和修复后,当从用户映射到连接 ID 时,问题似乎出在 IUserIdProvider 中。使用 SignalR 消息添加客户端发起的保活显示客户端和服务器确实已经重新连接,并且连接保持健康,但是从服务器推送到客户端的消息在 1-2 天后进入黑洞,可能与网站 publishing/appdomain 刷新参与。
我将 IUserIdProvider 替换为 SQL Azure (various options explained here) using this user presence sample recommended by @davidfowl in this post,并根据我现有的 user/auth 方案对其进行了定制。但是,它需要一个PresenceMonitor.cs 中的一些额外更改以提高可靠性:
- 我不得不将
periodsBeforeConsideringZombie
从 3 增加到 6,因为它在 30 秒时删除了 "zombie" 连接,而它们直到 50 秒左右才会断开连接。这意味着连接有时会在 30-50 秒范围内的某个地方重新连接,并且不会在数据库中进行跟踪。 - 我必须修复在数据库中找不到的心跳跟踪连接的处理。
样本在UserPresence.Check()
中有如下代码:
// Update the client's last activity
if (connection != null)
{
connection.LastActivity = DateTimeOffset.UtcNow;
}
else
{
// We have a connection that isn't tracked in our DB!
// This should *NEVER* happen
// Debugger.Launch();
}
然而,即使 periodsBeforeConsideringZombie
在 6 . 这是因为集线器的 OnConnected 事件有时可能会有点慢,所以如果你的 10 秒计时器处理程序是 "lucky".
我在 UserPresence
中使用此代码来为连接提供两个计时器滴答,或者在 10 秒到 20 秒之间,具体取决于计时器 "luck",以触发 OnConnected。如果它仍然没有被数据库跟踪,我会断开它,以便客户端再次连接(处理 OnClosed)并且不是消息的黑洞(因为我为用户循环数据库连接以推送消息)。
private HashSet<string> notInDbReadyToDisconnect = new HashSet<string>();
private void Check()
{
HashSet<string> notInDbReadyToDisconnectNew = new HashSet<string>();
...
else
{
// REMOVED: // We have a connection that isn't tracked in our DB!
// REMOVED: // This should *NEVER* happen
// REMOVED: // Debugger.Launch();
string format;
if (notInDbReadyToDisconnect.Contains(trackedConnection.ConnectionId))
{
trackedConnection.Disconnect();
format = "[SignalR][PresenceMonitor] Disconnecting active connection not tracked in DB (#2), ConnectionId={0}";
}
else
{
notInDbReadyToDisconnectNew.Add(trackedConnection.ConnectionId);
format = "[SignalR][PresenceMonitor] Found active connection not tracked in DB (#1), ConnectionId={0}";
}
}
...
notInDbReadyToDisconnect = notInDbReadyToDisconnectNew;
...
}
它为单个服务器完成工作,但 HashSet 可能需要移动到数据库以处理横向扩展。
经过这一切,一切都非常可靠,我的服务器推送代码仍然非常简单:
public static class EntityHubHelper
{
private static readonly IHubContext _hubContext = GlobalHost.ConnectionManager.GetHubContext<EntityHub>();
public static void EntityCreated(User user, Entity entity)
{
List<string> connectionIds = user.PushConnections.Select(c => c.ConnectionId).ToList();
_hubContext.Clients.Clients(connectionIds).EntityCreated(entity);
}
}