我对iOS设备上的websockets有一个相当奇怪和具体的问题。
我正在开发一个基于浏览器的Web应用程序,它通过websockets与Web服务器通信。对于客户端,使用浏览器的本地websocket,因此不涉及库(没有socket.io等)。
在服务器端,我使用带有ws module的Node.js作为websocket服务器。
在桌面浏览器和Android设备上一切正常,但在iOS上,这种连接常常会挂起"这意味着浏览器不会响应在websocket连接上收到的消息。 websocket" onmessage"处理程序没有被解雇。
然而,在调试服务器时,我可以清楚地看到消息如何离开服务器并被发送到浏览器,但是当出现此问题时,在Safari上没有任何反应。好像消息是"卡住"在设备的某个地方,因为如果我在GUI上触发另一个事件(例如移动滑块或点击按钮),那么" onmessage"立即执行。由于iOS上的所有浏览器共享相同的后端,因此会出现在Safari,Chrome和Firefox上。
我承认这些消息可能变得非常大,即它们可以变成大约100kb长。我读到一些websocket实现有这些大小的问题,所以我尝试在应用程序级别将它们分成几个块,但到目前为止还没有成功。
也许值得一提的是服务器操作系统是Windows。
这是我的简单客户端代码(摘要):
var socket = new WebSocket("ws://myurl.com");
socket.onmessage = function(e) {
// Sometimes gets stuck before calling this handler.
// Can be resolved with triggering any event on the UI.
processData(e.data);
}
socket.onerror = function(e) {
logError(e);
}
socket.onclose = function(e) {
cleanUp();
}
服务器端看起来像这样:
var webServer = require("http")
.createServer()
.listen(80, "0.0.0.0", function () {
console.log("Listening on port " + 80);
});
var WebSocketServer = require("ws").Server;
var wss = new WebSocketServer({server: webServer});
wss.on("connection", function(ws) {
ws.on("message", function(message) {
processMessage(message);
});
});
如上所述使用内容安全策略here(但是 它可能更像是Meteor / Cordova问题)
在应用程序级别(said here)将邮件拆分为块
发送一些虚拟确认字节或禁用Nagle的算法(建议here)
将onmessage回调包装成setTimeout()
(here)
协议WS或WSS没有区别
运行高速公路测试套件。有时它会通过所有测试,有时其中一些可能由于超时或执行时间太长而失败
有人可能会给我一些提示或有这种问题的经验吗?
更新02.02.017
另外值得一提的是,我的应用程序是通过requestAnimationFrame循环渲染的3D应用程序。
经过多天的研究,我发现requestAnimationFrame + WebSockets onmessage handler似乎存在问题。
我明天会更新我的发现。
答案 0 :(得分:3)
如果通过requestAnimationFrame
创建3D渲染循环并且同时应用程序使用Websocket的onmessage
处理程序接收数据,则似乎会出现此问题。
解决方案是将requestAnimationFrame
替换为setTimeout
的交替调用(请参阅下面的示例),使用requestAnimationFrame
模仿setTimeout
或使用setInterval
作为解决方法。
我在铬的问题跟踪器(here和there)中找到了这个错误的证据,因此我不知道这个错误与Apple有多大关系。 Webkit,但至少在那里描述的症状非常相似,解决方案/解决方案也帮助了我。
下面我引用此source的解决方案,所有信用都转到第30条注释:
var altframe = true, frametime = 0, lastframe = Date.now();
function Run() {
frametime = Date.now() - lastframe;
lastframe = Date.now();
if (!altframe)
setTimeout(Run,frametime);
// Hard-scheduled based on timing of last frame, must schedule before sim+render.
/*
SIM AND RENDER CODE
*/
if (altframe) {
window.requestAnimationFrame(Run);
// Normal timing, called after next paint.
altframe = false; // Alternate frame will be timed based on the next one.
}
else {
altframe = true; // Because we can't switch it above.
}
};
Run(); // As if we just had our setTimeout fire, altframe should be true