防止WebSocket中第一个收到的消息的理论丢失 [英] Prevent theoretical loss of the first received message in WebSocket

查看:197
本文介绍了防止WebSocket中第一个收到的消息的理论丢失的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

服务器端的代码在连接打开后立即发送消息 (它向客户端发送初始配置/问候语)。

Code on server-side sends a message immediately after connection is opened (it sends initial configuration/greetings to a client).

以下代码在客户端:

var sock = new WebSocket(url);
sock.addEventListener('error', processError);
sock.addEventListener('close', finish);
sock.addEventListener('message', processMessage);

我担心会丢失来自服务器的第一个配置/问候相关消息。从理论上讲,在 消息设置事件处理程序之前,没有什么能阻止它被接收。

I worry about losing this first configuration/greetings-related message from server. Theoretically nothing prevents it from being received before message event handler is set.

On另一方面,实际上我从来没有想过。和AFAIK JavaScript WebSocket API 没有对策反对这个理论问题:WebSocket 构造函数既不允许 message 要设置的事件处理程序,也不允许在挂起状态下创建WebSocket。

On the other hand, practically it never occurred to me. And AFAIK JavaScript WebSocket API doesn't have countermeasures against this theoretical issue: the WebSocket constructor neither allows message event handler to be set, nor allows WebSocket to be created in suspended state.

所以:


  1. 要么我错过了某些东西,即使在理论上也无法用上述代码丢失消息。

  2. 或者它是JavaScript WebSocket API设计中的错误。

  3. 或者每个人都很高兴因为消息丢失几乎不可能。

  4. 或者这样行为(在连接时从服务器发送消息)有些被认为是不好的做法,所以没有人会在理论上纠正它的可能性。

  1. Either I am missing something, and loss of message with the above code is impossible even theoretically.
  2. Or it is bug in JavaScript WebSocket API design.
  3. Or everyone is just happy because message loss is practically impossible.
  4. Or such behavior (sending message from server on connection) is somewhy considered bad practice, so no one bothers about possibility to implement it theoretically correct.

PS:这些简单但理论上的问题是否更适合 Stack Overflow 程序员@ Stack Exchange

推荐答案

Don不用担心。

您的代码在单线程事件循环中运行。

Your code is running within a single threaded event loop.

此行: var sock = new WebSocket(url); 根本不会启动websocket连接。 规范说它必须执行实际连接只有在返回Web套接字后,与处理事件循环的线程并行运行代码:

This line: var sock = new WebSocket(url); doesn't initiate a websocket connection at all. The spec says that it must perform the actual connection only after returning the web socket, in parallel with the thread handling the event loop your code is running on:



  1. 返回一个新的WebSocket对象,但继续这些步骤[并行] [2]。


仅此一项是不够的,但该套接字的所有后续 WebSocket 事件都安排在相同的单线程事件中正在运行代码的循环。以下是规范关于接收消息的内容:

That alone wouldn't be sufficient, but all subsequent WebSocket events for that socket are scheduled inside the same single-threaded event loop that is running your code. Here's what the spec says about receiving a message:


当收到带有类型的WebSocket 消息时 >键入和数据数据,用户代理必须排队任务以执行以下步骤

When a WebSocket message has been received with type type and data data, the user agent must queue a task to follow these steps

该任务在同一个事件循环中排队。这意味着在您创建 WebSocket 的任务运行完成之前,处理消息的任务不能。因此,在事件循环处理任何与连接相关的消息之前,您的代码将完成运行。

That task is queued on the same event loop. That means that the task to process the message cannot be run until the task where you created your WebSocket has run to completion. So your code will finish running before the event loop will process any connection related messages.

即使您在使用多个线程的浏览器中运行代码,特定的代码将在单线程事件循环上运行,每个事件循环都是独立的。

Even if you're running your code in a browser that uses many threads, the specific code will run on a single threaded event loop and each event loop will be independent.

不同的事件循环可以通过将任务推送到彼此的任务队列来进行通信。但是这些任务将在接收任务的单线程事件循环中执行,保持代码线程安全。

Different event loops can and do communicate by pushing tasks into each other's task-queues. But these tasks will be executed within the single-threaded event-loop that received the task, keeping your code thread-safe.

任务处理此事件将是由单线程事件循环处理,找到适当的事件处理程序并调用其回调...但只有在任务处理完毕后才会发生。

The task "handle this event" will be handled by the single threaded event loop finding the appropriate event handler and calling its callback... but this will only happen once the task is already being handled.

更清晰:

我没有声称每个事件循环实际上都处理IO - 但IO调度程序将发送您的代码事件,这些事件将在单个线程内顺序运行(有点,他们确实有使用不同任务队列的优先级管理)。

I'm not claiming that each event-loop actually handles the IO - but the IO scheduler will send your code events and these events will run sequentially within a single thread (sort of, they do have priority management that uses different "task queues").

应该注意的是,Websocket API不是为DOM的函数 addEventListener 而设计的。

It should be noted that the Websocket API wasn't designed for the DOM's function addEventListener.

,Websocket API遵循HTML4范例,其中事件回调是对象属性(而不是EventListener集合) )。即:

Instead, the Websocket API follows the HTML4 paradigm, where event callbacks are object properties (rather than the EventListener collection). i.e.:

// altered DOM API:
sock.addEventListener('message', processMessage);
// original WebSocket API:
sock.onmessage = processMessage;

两个API在我测试的所有浏览器上都能正常工作(包括安全传递第一条消息)。方法的差异可能由HTML4兼容层处理。

Both APIs work correctly on all the browsers I tested (including safe delivery of first message). The difference in approaches is probably handled by the HTML4 compatibility layer.

但是关于事件调度的规范是不同的,所以使用 addEventListener

However the specification regarding event scheduling is different, so the use of addEventListener should probably be avoided.

关于青铜人的回答有关失败的消息回复...

Regarding Bronze Man's answer concerning failed message responses...

我不能即使我使用小型Ruby应用程序和一个小型Javascript客户端编写了测试,也可以重现声明的问题。

I couldn't reproduce the claimed issue, even though I wrote a test using a small Ruby application and a small Javascript Client.

Ruby应用程序启动带有欢迎消息的Websocket echo服务器(我正在使用 plezi.io )。

The Ruby application starts up a Websocket echo server with a welcome message (I'm using plezi.io).

Javascript客户端包含一个忙等待循环,导致Javascript线程挂起(阻塞)指定的时间(在我的测试中为2秒)。

The Javascript client contains a busy-wait loop that causes the Javascript thread to hang (block) for the specified amount of time (2 seconds in my tests).

onmessage 设置了回调只有在块被释放后(2秒后) - 所以来自服务器的欢迎消息将在定义回调之前到达浏览器。

The onmessage callback is set only after the block is released (after 2 seconds) - so the welcome message from the server will arrive at the browser before the callback is defined.

这允许我们测试如果任何特定浏览器上的欢迎消息丢失(这将是浏览器中的错误)。

This allows us to test if the welcome message is lost on any specific browser (which would be a bug in the browser).

测试是可靠的,因为服务器是已知数量并将发送一旦升级完成,就会向socket发送消息(我在C中编写了Iodine服务器后端以及plezi.io框架,因为我的深度,我选择了它们了解他们的内部行为)。

The test is reliable since the server is a known quantity and will send the message to the socket as soon as the upgrade is complete (I wrote the Iodine server backend in C as well as the plezi.io framework and I chose them because of my deep knowledge of their internal behavior).

Ruby应用程序:

# run from terminal using `irb`, after `gem install plezi`
require 'plezi'
class WebsocketEcho
    def index
       "Use Websockets"
    end
    def on_message data
       # simple echo
       write data
    end
    def on_open
       # write a welcome message
       # will ths message be lost?
       write "Welcome to the WebSocket echo server."
       puts "New Websocket connection opened, welcome message was sent."
    end
end
# adds mixins to the class and creates route
Plezi.route("/", WebsocketEcho)

# running the server from the terminal
Iodine.threads = 1
Iodine::Rack.app = Plezi.app
Iodine.start

Javascript客户端:

The Javascript Client:

function Client(milli) {
    this.ws = new WebSocket("ws" + window.document.location.href.slice(4, -1));
    this.ws.client = this;
    this.onopen = function (e) { console.log("Websocket opened", e); }
    this.ws.onopen = function (e) { e.target.client.onopen(e); }
    this.onclose = function (e) { console.log("Websocket closed", e); /* reconnect? */ }
    this.ws.onclose = function (e) { e.target.client.onclose(e); }
    if(milli) { // busy wait, blocking the thread.
        var start = new Date();
        var now = null;
        do {
            now = new Date();
        } while(now - start < milli);
    }
    this.onmessage = function (e) { console.log(e.data); }
    // // DOM API alternative for testing:
    // this.ws.addEventListener('message', function (e) { e.target.client.onmessage(e); });
    // // WebSocket API for testing:
    this.ws.onmessage = function (e) { e.target.client.onmessage(e); }    
}
// a 2 second window
cl = new Client(2000);

我机器上的结果(MacOS):

Results on my machine (MacOS):


  • 只有在新客户端创建完成后(在线程完成处理代码之后,如Ruby应用程序的延迟输出所示),Safari 11.01才会启动Websocket连接。一旦建立连接,该消息显然已到达。

  • Safari 11.01 initiates the Websocket connection only after the new client was creation is complete (after the thread is done processing the code, as indicated by the Ruby application's delayed output). The message obviously arrived once the connection was made.

Chrome 62.0立即启动Websocket连接。一旦2秒窗口结束,消息就会到达。即使在 onmessage 处理程序设置之前到达,消息也没有丢失。

Chrome 62.0 initiates the Websocket connection immediately. The message arrives once the 2 second window ends. Message wasn't lost even though it arrived before the onmessage handler was set.

FireFox 56.0的行为与Chrome相同,立即启动Websocket连接。一旦2秒窗口结束,消息就会到达。消息没有丢失。

FireFox 56.0 behaves the same as Chrome, initiating the Websocket connection immediately. The message arrives once the 2 second window ends. Message wasn't lost.

如果有人可以在Windows和Linux上测试,那会很棒......但是我不认为浏览器会出现事件调度的实现问题。我相信规格可以信任。

If someone could test on Windows and Linux, that would be great... but I don't think the browsers will have implementation issues with the event scheduling. I believe the specifications can be trusted.

这篇关于防止WebSocket中第一个收到的消息的理论丢失的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆