防止WebSocket中第一个收到的消息的理论丢失

Sas*_*sha 6 javascript websocket

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

以下代码在客户端:

var sock = new WebSocket(url);
sock.addEventListener('error', processError);
sock.addEventListener('close', finish);
sock.addEventListener('message', processMessage);
Run Code Online (Sandbox Code Playgroud)

我担心从服务器丢失这个第一个配置/问候相关的消息.理论上没有什么能阻止 message设置事件处理程序之前接收它.

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

所以:

  1. 要么我错过了某些东西,即使在理论上也不可能用上述代码丢失消息.
  2. 或者它是JavaScript WebSocket API设计中的错误.
  3. 或者每个人都很高兴,因为消息丢失实际上是不可能的.
  4. 或者这样的行为(在连接时从服务器发送消息)被认为是不好的做法,所以没有人讨论在理论上正确实现它的可能性.

PS:这样简单但理论上的问题更适合Stack OverflowProgrammers @ Stack Exchange吗?

Mys*_*yst 5

别担心.

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

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

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

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

收到带有类型类型和数据数据的WebSocket 消息时,用户代理必须对任务进行排队以执行这些步骤

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

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

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

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

更清楚:

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

编辑:客户端代码问题

应该注意的是,Websocket API并不是为DOM的功能而设计的addEventListener.

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

// altered DOM API:
sock.addEventListener('message', processMessage);
// original WebSocket API:
sock.onmessage = processMessage;
Run Code Online (Sandbox Code Playgroud)

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

但是,关于事件调度的规范是不同的,因此addEventListener应该避免使用.

编辑2:测试理论

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

即使我使用小型Ruby应用程序和小型Javascript客户端编写了测试,但我无法重现声明的问题.

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

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

onmessage回调仅设置块被释放(2秒后)之后-所以被定义回调之前从服务器欢迎消息将在浏览器到达.

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

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

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
Run Code Online (Sandbox Code Playgroud)

Javascript客户端:

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);
Run Code Online (Sandbox Code Playgroud)

在我的机器上的结果(MacOS):

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

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

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

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