javascript 服务器发送事件和浏览器限制
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18584525/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Server sent events and browser limits
提问by Sunyatasattva
I have a web application that listens for Server Sent Events. While I was working and testing with multiple windows open, things were not working and I banged my head for several times looking in the wrong direction: eventually, I realized that the problem was concurrent connections.
我有一个侦听服务器发送事件的 Web 应用程序。当我在打开多个窗口的情况下工作和测试时,事情并没有奏效,我几次朝着错误的方向撞头:最终,我意识到问题出在并发连接上。
However I was testing a very limited number and even if I am running the test on Apache (I know, I should use node).
但是,我测试的数量非常有限,即使我在 Apache 上运行测试(我知道,我应该使用节点)。
I then, switched browser and noticed something really interesting: apparently Chrome limits Server Sent Events connections to 4-5, while Opera doesn't. Firefox, on the other hand, after 4-5 simultaneous connections, refuses to load anyother page.
然后,我切换浏览器并注意到一些非常有趣的事情:显然 Chrome 将服务器发送的事件连接限制为 4-5,而 Opera 则没有。另一方面,Firefox 在 4-5 个同时连接后拒绝加载任何其他页面。
What is the reason behind this? Does the limit only apply to SSE connections from the same source, or would it be the same if I were to test open them from a different domain? Is there any chance that I am misusing SSE and this is actually blocking the browsers, or this is a known behaviour? Is there any way around it?
这背后的原因是什么?该限制是否仅适用于来自同一来源的 SSE 连接,或者如果我要测试从不同域打开它们是否相同?我是否有可能滥用 SSE 而这实际上阻止了浏览器,或者这是一种已知行为?有什么办法可以解决吗?
回答by Christian Landgren
The way this works in all browsers are that each domain gets a limited amount of connections and the limits are global for your whole application. That means if you have one connection open for realtime communication you have one less for loading images, css and other pages. On top of that you don't get new connections for new tabs or windows, all of them needs to share the same amount of connections. This is very frustrating but there are good reasons for limiting the connections. A few years back, this limit was 2 in all browsers (based on the rules in (http://www.ietf.org/rfc/rfc2616.txt) HTTP1.1 spec) but now most browsers use 4-10 connections in general. Mobile browsers on the other hand still needs to limit the amount of connections for battery saving purposes.
这在所有浏览器中的工作方式是每个域获得有限数量的连接,并且限制对于您的整个应用程序是全局的。这意味着如果您为实时通信打开了一个连接,那么您将少一个用于加载图像、CSS 和其他页面的连接。最重要的是,您不会为新选项卡或窗口获得新连接,它们都需要共享相同数量的连接。这非常令人沮丧,但有充分的理由限制连接。几年前,这个限制在所有浏览器中是 2 个(基于 ( http://www.ietf.org/rfc/rfc2616.txt) HTTP1.1 规范中的规则)但现在大多数浏览器使用 4-10 个连接一般的。另一方面,移动浏览器仍需要限制连接数量以节省电池电量。
These tricks are available:
这些技巧可用:
- Use more host names. By assigning ex. www1.domain.com, www2.domain.com you get new connections for each host name. This trick works in all browsers. Don't forget to change the cookie domain to include the whole domain (domain.com, not www.domain.com)
- Use web sockets. Web sockets are not limited by these restrictions and more importantly they are not competing with the rest of your websites content.
Reuse the same connection when you open new tabs/windows. If you have gathered all realtime communication logic to an object call Hub you can recall that object on all opened windows like this:
window.hub = window.opener ? window.opener.hub || new Hub()
- or use flash - not quite the best advice these days but it might still be an option if websockets aren't an option.
- Remember to add a few seconds of time between each SSE request to let queued requests to be cleared before starting a new one. Also add a little more waiting time for each second the user is inactive, that way you can concentrate your server resources on those users that are active. Also add a random number of delay to avoid the Thundering Herd Problem
- 使用更多主机名。通过分配前。www1.domain.com、www2.domain.com 您会为每个主机名获得新的连接。这个技巧适用于所有浏览器。不要忘记更改 cookie 域以包含整个域(domain.com,而不是 www.domain.com)
- 使用网络套接字。Web 套接字不受这些限制的限制,更重要的是,它们不会与您网站的其他内容竞争。
打开新选项卡/窗口时重复使用相同的连接。如果您已将所有实时通信逻辑收集到对象调用集线器,您可以在所有打开的窗口中调用该对象,如下所示:
window.hub = window.opener ? window.opener.hub || new Hub()
- 或使用 flash - 现在不是最好的建议,但如果 websockets 不是一个选项,它可能仍然是一个选项。
- 请记住在每个 SSE 请求之间添加几秒钟的时间,以便在开始新的请求之前清除排队的请求。还为用户处于非活动状态的每一秒增加一点等待时间,这样您就可以将服务器资源集中在那些处于活动状态的用户上。还添加了一个随机数的延迟,以避免雷鸣羊群问题
Another thing to remember when using a multithreaded and blocking language such as Java or C# you risk using resources in your long polling request that are needed for the rest of your application. For example in C# each request locks the Session object which means that the whole application is unresponsive during the time a SSE request is active.
在使用多线程和阻塞语言(例如 Java 或 C#)时要记住的另一件事是,您可能会在长轮询请求中使用其他应用程序所需的资源。例如,在 C# 中,每个请求都会锁定 Session 对象,这意味着整个应用程序在 SSE 请求处于活动状态期间没有响应。
NodeJs is great for these things for many reasons as you have already figured out and if you were using NodeJS you would have used socket.io or engine.io that takes care of all these problems for you by using websockets, flashsockets and XHR-polling and also because it is non blocking and single threaded which means it will consume very little resources on the server when it is waiting for things to send. A C# application consumes one thread per waiting request which takes at least 2MB of memory just for the thread.
NodeJs 非常适合这些事情,原因有很多,你已经知道了,如果你使用 NodeJS,你会使用 socket.io 或 engine.io,它们通过使用 websockets、flashsockets 和 XHR-polling 为你解决所有这些问题并且因为它是非阻塞和单线程的,这意味着它在等待发送的东西时会消耗服务器上的很少资源。AC# 应用程序为每个等待请求消耗一个线程,该线程至少需要 2MB 的内存。
回答by NerdSoup
One way to get around this issue is to shut down the connections on all the hidden tabs, and reconnect when the user visits a hidden tab.
解决此问题的一种方法是关闭所有隐藏选项卡上的连接,并在用户访问隐藏选项卡时重新连接。
I'm working with an application that uniquely identifies users which allowed me to implement this simple work-around:
我正在使用一个唯一标识用户的应用程序,它允许我实现这个简单的解决方法:
- When users connect to sse, store their identifier, along with a timestamp of when their tab loaded. If you are not currently identifying users in your app, consider using sessions & cookies.
When a new tab opens and connects to sse, in your server-side code, send a message to all other connections associated with that identifier (that do not have the current timestamp) telling the front-end to close down the EventSource. The front-end handler would look something like this:
myEventSourceObject.addEventListener('close', () => { myEventSourceObject.close(); myEventSourceObject = null; });
Use the javascript page visibility api to check to see if an old tab is visible again, and re-connect that tab to the sse if it is.
document.addEventListener('visibilitychange', () => { if (!document.hidden && myEventSourceObject === null) { // reconnect your eventsource here } });
If you set up your server code like step 2 describes, on re-connect, the server-side code will remove all the other connections to the sse. Hence, you can click between your tabs and the EventSource for each tab will only be connected when you are viewing the page.
- 当用户连接到 sse 时,存储他们的标识符以及他们的选项卡加载时的时间戳。如果您当前未在您的应用中识别用户,请考虑使用会话和 cookie。
当一个新选项卡打开并连接到 sse 时,在您的服务器端代码中,向与该标识符关联的所有其他连接(没有当前时间戳)发送一条消息,告诉前端关闭 EventSource。前端处理程序看起来像这样:
myEventSourceObject.addEventListener('close', () => { myEventSourceObject.close(); myEventSourceObject = null; });
使用 javascript 页面可见性 api 检查旧选项卡是否再次可见,如果是,则将该选项卡重新连接到 sse。
document.addEventListener('visibilitychange', () => { if (!document.hidden && myEventSourceObject === null) { // reconnect your eventsource here } });
如果您像步骤 2 描述的那样设置服务器代码,则在重新连接时,服务器端代码将删除与 sse 的所有其他连接。因此,您可以在选项卡之间单击,并且每个选项卡的 EventSource 只会在您查看页面时连接。
Note that the page visibility api isn't available on some legacy browsers: https://caniuse.com/#feat=pagevisibility
请注意,页面可见性 api 在某些旧浏览器上不可用:https: //caniuse.com/#feat=pagevisibility
回答by avetisk
You are right about the number of simultaneous connections.
您对同时连接的数量是正确的。
You can check this list for max values: http://www.browserscope.org/?category=network
您可以查看此列表以获取最大值:http://www.browserscope.org/?category= network
And unfortunately, I never found any work around, except multiplexing and/or using different hostnames.
不幸的是,除了多路复用和/或使用不同的主机名之外,我从未找到任何解决方法。