20

我用 SignalR 制作了一个简单的应用程序进行测试。当页面加载时,它调用服务器上的一个函数,然后该函数调用一个在屏幕上打印消息的客户端函数。我这样做是为了检查客户端和服务器功能是否正常工作以及 SignalR 通信是否正常。

我的问题是,如果我在两个不同的选项卡上打开同一个页面(在 Chrome 中完成),第一页加载正常,但第二页不调用服务器的功能 - 只有当我关闭第一页时。

据我了解,它们可能是与浏览器相关的连接限制,不允许 SignalR 连接一次以上(实际上是两个,一个用于接收,一个用于发送)

更新:我找到了我们打开的其他选项卡,但现在我已经检查过了,它只允许4 个选项卡/页面与连接处于活动状态。如果我尝试将同一页面放在新选项卡上,则不会发送任何数据,当我关闭其他选项卡之一时,新选项卡会立即发送数据。

我想知道是否有任何解决方案,因为如果用户决定在两个或更多选项卡上打开同一页面,我希望此连接可用。

我不相信它与 IIS 有任何关系,因为据我所知它可以接受数千个连接。

4

4 回答 4

21

这个问题最好由未来的通道消息规范解决,该规范迄今为止尚未被任何浏览器实现,但我设法通过限制Alex Ford 描述的连接数量并localStorage用作选项卡之间的消息总线来解决它.

storage事件允许您在选项卡之间传播数据,同时保持单个 SignalR 连接打开(从而防止连接饱和)。调用将在所有其他选项卡(不是调用者)中localStorage.setItem('sharedKey', sharedData)引发事件:storage

$(window).bind('storage', function (e) {
    var sharedData = localStorage.getItem('sharedKey');
    if (sharedData !== null)
        console.log(
            'A tab called localStorage.setItem("sharedData",'+sharedData+')'
        );
});

您可以测试if ($.connection.hub.state === 1)以确定给定选项卡是否应通过 localStorage(由 Alex 提供)通知其他选项卡,以防止重复localStorage.setItem调用。

Facebook 通过在多个子域上提供持久连接来克服浏览器的限制,但这会使部署和测试复杂化。

注意事项

旧连接:在 Alex 的解决方案中,您需要小心Disconnect()不要被调用(例如异常),并且您需要HubConnections用旧的集线器连接填充您的存储桶(或存储库)。如果会话 ID 没有更改(可能发生),这可能会阻止新客户端建立 SignalR 连接,即使没有处于活动状态。或者,为新连接添加时间戳并设置一个滑动过期时间以最大限度地减少潜在影响。

锁定: localStorage可能会受到竞争条件的影响,因为它没有实现此处描述的任何锁定。

为了支持不同类型的事件,您应该在 JSON 消息中编码一个eventType并在storage事件上对其进行测试。

后备

如果无法建立 SignalR 连接,我会每 45 秒轮询一次服务器以检索通知计数。

如果不想使用localStorage,可以使用cookies,但不是那么干净。

于 2012-10-23T18:35:07.393 回答
4

我创建了IWC-SignalR实用程序,它允许为同一应用程序的所有窗口(选项卡)建立单个 SignalR 连接。

这个怎么运作

其中一个窗口成为连接所有者(随机选择)并持有真正的 SignalR 连接。如果连接所有者关闭或崩溃,另一个窗口将成为连接所有者 - 这会自动发生。窗口间通信是通过窗口间通信库(基于localStorage)完成的。该库提供了在窗口之间以及在并行进程(锁、共享数据、事件总线......)之间进行通信的功能。希望它对某人有用。

于 2015-02-02T22:28:19.010 回答
3

为了扩展@FreshCode 的答案,这就是我实现他的想法的方式。

我需要在选项卡之间传递两个不同的操作。我可以设置通知或删除通知。这些通知由浏览器接收并存储为将在指定时间触发的超时。第一个选项卡有 SignalR 连接,而其他所有选项卡都没有。我必须克服的一个问题是,无论我打算执行哪个操作(设置/删除),“存储”事件都会触发。

我最终做的是传递一个自定义 JSON 对象,其中包含我想要执行的操作的属性:

$(window).bind('storage', function () {
    var updateInfo = JSON.parse(localStorage.getItem('updateInfo'));
    if (updateInfo.action == 'removeNotification')
        removeNotification(updateInfo.notificationId);
    else if (updateInfo.action == 'setNotification')
        setNotification(updateInfo.notification);
});

这样,每次我在本地存储中设置一个项目时,我只需指定需要发生的操作,并且只有该操作会在其他选项卡上发生。例如,如果我更新一个通知,它是客户端收到通知时这两种操作的组合。它删除通知并使用更新的值设置通知。因此,localStorage.setItem正在拨打两个电话。

function removeNotification(id) {
    // Check if signalR is connected. If so, I am the tab that will update
    // the other tabs.
    if ($.connection.hub.state === 1) {
        var updateInfo = {
            action: 'removeNotification',
            notificationId: id
        };
        localStorage.setItem('updateInfo', JSON.stringify(updateInfo));
    }
    // brevity brevity
}

同样,setNotification函数。

function setNotification(notification) {
    if ($.connection.hub.state === 1) {
        var updateInfo = {
            action: 'setNotification',
            notification: notification
        };
        localStorage.setItem('updateInfo', JSON.stringify(updateInfo));
    }
    // brevity brevity
}
于 2012-10-24T08:52:42.637 回答
2

确保 SignalR 可以使用并且正在使用 WebSocket,并且它不会使用您的应用程序允许使用的有限数量的连接之一。

SignalR kan 使用不同的传输协议,这里描述了 SignalR 如何决定使用哪个:

HTML 5 传输

这些传输依赖于对 HTML 5 的支持。如果客户端浏览器不支持 HTML 5 标准,则将使用较旧的传输。

  • WebSocket(如果服务器和浏览器都表明它们可以支持 Websocket)。WebSocket 是唯一一种在客户端和服务器之间建立真正持久的双向连接的传输方式。但是,WebSocket 也有最严格的要求;它仅在最新版本的 Microsoft Internet Explorer、Google Chrome 和 Mozilla Firefox 中完全支持,并且仅在 Opera 和 Safari 等其他浏览器中部分实现。
  • Server Sent Events,也称为 EventSource (如果浏览器支持 Server Sent Events,基本上是除了 Internet Explorer 之外的所有浏览器。)

传输选择过程 下面的列表显示了 SignalR 用来决定使用哪个传输的步骤。

  1. 如果浏览器是 Internet Explorer 8 或更早版本,则使用长轮询。

  2. 如果配置了JSONP(即启动连接时jsonp参数设置为true),则使用Long Polling。

  3. 如果正在建立跨域连接(即,如果 SignalR 端点与托管页面不在同一个域中),则如果满足以下条件,则将使用 WebSocket:

    • 客户端支持 CORS(跨域资源共享)。有关哪些客户端支持 CORS 的详细信息,请参阅 caniuse.com 上的 CORS。

    • 客户端支持WebSocket

    • 服务器支持WebSocket

      如果这些标准中的任何一个不符合,将使用长轮询。有关跨域连接的更多信息,请参阅如何建立跨域连接。

  4. 如果没有配置 JSONP 且连接不是跨域的,则在客户端和服务器都支持的情况下使用 WebSocket。

  5. 如果客户端或服务器不支持 WebSocket,则使用服务器发送事件(如果可用)。

  6. 如果服务器发送事件不可用,则尝试使用 Forever Frame。

  7. 如果 Forever Frame 失败,则使用长轮询。

使用开发人员工具,检查在您的页面上进行的网络调用。您应该能够看到如下内容:

.../connect?transport=webSockets&clientProtocol=1.5&connectionToken=...

如果transport不是webSockets(例如serverSentEventslongPolling),您可能需要对客户端和服务器进行故障排除,以了解为什么握手不会导致使用 WebSockets。(就我而言,我错过了为 IIS 安装WebSocket 协议窗口功能。

于 2019-12-16T13:21:26.693 回答