关于iOS设备上的网络套接字,我有一个相当奇怪和具体的问题。
我正在开发一个基于浏览器的web应用程序,它通过websockets与web服务器通信。对于客户端,使用浏览器的本地websocket,因此不涉及库(没有socket.io等)。
在服务器端,我使用Node.js和ws模块作为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);
});
});
>
玩弄这里提到的内容安全策略(但这可能更多是Meteor/Cordova问题)
在应用程序级别将消息拆分为块(这里说)
发送一些伪确认字节或禁用Nagle算法(如这里所建议的)
将onmessage回调包装到< code>setTimeout()(此处)
协议WS或WSS没有区别
运行Autobahn测试套件。有时它通过了所有测试,有时有些测试可能由于超时或执行时间过长而失败
有人可以给我一些提示或有这类问题的经验吗?
更新02.02.017
另外值得一提的是,我的应用程序是一个通过requestAnimationFrame循环渲染的3D应用程序。
经过几天的研究,我发现消息处理程序上的requestAnimationFrame WebSockets似乎存在问题。
我明天会更新我的发现。
如果通过requestAnimationFrame
创建3D渲染循环,同时应用程序使用Websocket的onmessage
处理程序接收数据,则似乎会出现此问题。
一种解决方案是将< code > requestAnimationFrame 替换为< code>setTimeout的交替调用(参见下面的示例),用< code>setTimeout模仿< code > requestAnimationFrame ,或者使用< code>setInterval作为解决方法。
我在chromium的问题跟踪器(这里和那里)中找到了这种 bug 的证据,所以我不知道这种 bug 与苹果的Webkit有多大关系,但至少那里描述的症状非常相似,解决方案/变通办法也帮助了我。
下面我引用了这个来源的解决方案,所有的功劳都在这里的第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