Websocket 连接在 Safari 中随机挂起 iOS

Websocket connection hangs randomly in Safari iOS

关于 iOS 设备上的 websockets,我有一个相当奇怪和具体的问题。

我正在开发一个基于浏览器的网络应用程序,它通过 websockets 与网络服务器通信。对于客户端,使用浏览器的本机 websocket,因此不涉及库(没有 socket.io 等)。

在服务器端,我使用 Node.js 和 ws module 作为 websocket 服务器。

在桌面浏览器和 Android 设备上一切正常,但在 iOS 上连接很频繁 "hangs",这意味着浏览器不响应在 websocket 连接上收到的消息. websocket 的 "onmessage" 处理程序不会被触发。

但是在调试服务器时,我可以清楚地看到消息是如何离开服务器并发送到浏览器的,但是当出现这个问题时,在 Safari 上没有任何反应。消息似乎在设备中的某处 "stuck",因为如果我在 GUI 上触发另一个事件(例如移动滑块或单击按钮),那么 "onmessage" 会立即执行。由于 iOS 上的所有浏览器共享相同的后端,因此在 Safari、Chrome 和 Firefox 上都会发生这种情况。

我承认消息可以变得非常大,即它们可以长到大约 100kb。我读到一些 websocket 实现有这些量级的问题,所以我尝试在应用程序级别将它们分成几个块,但到目前为止没有成功。

也许值得一提的是服务器 OS 是 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);
    });
});

到目前为止我尝试了什么


有人可以给我一些提示或对此类问题有经验吗?

更新 02.02.017

另外值得一提的是,我的应用程序是一个通过 requestAnimationFrame 循环渲染的 3D 应用程序。

经过多日的研究,我发现 requestAnimationFrame + WebSockets onmessage 处理程序似乎存在问题。

我明天会更新我的发现。

如果通过 requestAnimationFrame 创建 3D 渲染循环并且同时应用程序正在使用 Websocket 的 onmessage 处理程序接收数据,则似乎会出现此问题。

一个解决方案是用交替调用 setTimeout 代替 requestAnimationFrame(参见下面的示例),用 setTimeout 模仿 requestAnimationFrame 或使用 setInterval作为解决方法。

我在 chromium 的问题跟踪器 (here and 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