从浏览器发送WebSocket ping/pong帧

176

我一直在阅读关于WebSockets中ping/pong消息以保持连接的文章,但我不确定它们是什么。它是一个独立的帧类型吗?(在chrome的javascript WebSocket对象上我没有看到任何与ping-pong相关的方法)。还是只是一种设计模式(例如,我发送“ping”或任何其他字符串到服务器并让它响应)。Ping-pong是否与续帧有关?

我之所以问这个问题是因为我正在使用一个在Mongrel2后面运行的Python框架,所以我想知道是否有一种方法可以向Mongrel2发送特定的ping/pong消息,以告诉它保持连接而无需我的Python应用程序担心它。类似于HTTP方法的分离,我猜。我想一个专用的ping/pong消息帧可能比字符串“ping”更简单(对服务器和网络负载更小),尽管这可能并不重要。

编辑:我刚刚查看了RFC 6455,看起来Ping和Pong肯定是具有自己操作码的控制帧类型。那么我该如何从Chrome的javascript中发送Ping帧呢?


只需从服务器进行ping操作。每个人都知道非标准端口的网络问题,因此他们开始以定期的短间隔进行ping操作。我想你可以对一个编写不良的服务器进行ping操作,但使用它们进行任何敏感操作可能不是太明智。 - user1382306
https://github.com/websockets/ws/issues/977 - firstpostcommenter
1
@user1382306 从服务器先ping会使用移动设备的电池,消耗非常快。从客户端ping可以节省设备电量。 - bronze man
2
@user1382306 并不是所有人都遇到了这个问题!非标准端口的网络问题是什么? - HappyDog
4个回答

158

没有Javascript API可以发送ping帧或接收pong帧。这要么由您的浏览器支持,要么不支持。也没有API来启用、配置或检测浏览器是否支持和使用ping/pong帧。曾经有关于创建Javascript ping/pong API的讨论。未来可能会出现ping的可配置性/可检测性,但是Javascript不太可能直接发送和接收ping/pong帧。

然而,如果您控制客户端和服务器代码,那么您可以在更高的级别上轻松添加ping/pong支持。如果您还没有消息类型头/元数据,那么您将需要一些这样的东西,但这很简单。除非您计划每秒发送数百次ping或同时拥有数千个客户端,否则自己完成的开销将非常小。


2
能否以编程方式创建一个包含“ping”帧的二进制帧? - Nahum Bazes
2
这就是 ws WebSocket 库的实现方式:https://github.com/websockets/ws/blob/8a7016dc2fe4d9d63c428c67588d7c1f33a72e5c/lib/sender.js#L209 - Daniel W.

11
更新: 虽然这个答案已经有些年头了,但它仍然在谷歌的前几个搜索结果中。 在握手之后的任何时候,客户端或服务器都可以选择向另一方发送ping

Ping只应该从服务器发送到客户端,并且浏览器应该尽快自动回答Pong操作码。因此,您不必担心更高层次的问题。

虽然并非所有浏览器都支持标准格式,它们可能在实现某些机制方面存在差异,甚至可能意味着没有Pong响应功能。但我个人正在使用Ping / Pong,并且从未见过不实现此类操作码和低级客户端自动响应的客户端。


108
您在哪里读到ping只能从服务器发送到客户端?RFC说:“一旦建立连接并在连接关闭之前,端点可以随时发送Ping帧。” - xryl669
10
如果您希望客户端能够自动检测到已断开连接的状态(当未收到RST / FIN数据包但网络已经断开连接时),则需要让客户端也发起ping数据包。请注意,这样做不会改变原意。 - pschwamb
21
当TCP未接收到RST/FIN数据包时,TCP将不会通知连接的断开。TCP只有在发送方尝试在已断开的连接上发送数据时才能检测到连接中断。在许多应用程序中,Ping被用作心跳信号,这是有效的使用方式。如果连接已经死亡,它将一直保持死亡状态,直到客户端尝试发送数据。因此,在浏览器上使用WebSockets时,必须使用自定义Ping(或心跳)消息来检测客户端是否断开连接。服务器可能正在尝试发送Ping并检测到客户端已断开连接,但客户端未被告知。客户端Ping也是可以的。 - DDS
22
在RFC 6455中,WebSocket协议明确指出:“注意:Ping帧可以用作保持连接或验证远程端点是否仍然响应的手段。” - DDS
7
当用户代理频繁刷新时,仅服务器ping可能是一种推荐的做法。但现在许多网站被设计为单页应用程序(SPA)。在这种环境下,客户端应用程序定期ping服务器以确保连接没有丢失可能非常重要。 - Zephyr was a Friend of Mine
显示剩余16条评论

7

JS中的可能解决方案

如果WebSocket服务器在几分钟后断开ws链接,并且服务器和客户端之间没有发送任何消息,则:

  1. 客户端主动发送自定义ping消息,使用keepAlive函数来保持连接活跃。

  2. 服务器会忽略自定义ping消息并响应自定义pong消息。

let timerId = 0; 

function keepAlive(timeout = 20000) { 
    if (webSocket.readyState == webSocket.OPEN) {  
        webSocket.send('');  
    }  
    timerId = setTimeout(keepAlive, timeout);  
}

function cancelKeepAlive() {  
    if (timerId) {  
        clearTimeout(timerId);  
    }  
}


3
这段代码发送空帧,而不是ping或pong帧。 - Remember Monica
1
@RememberMonica 请先阅读我的描述,我知道发生了什么。 - xgqfrms
这为我提供了一个保持连接的函数,它在我的情况下通知服务器更新websocket连接上的读取截止时间值(顺便说一句,我在我的情况下使用golang服务器)。虽然我不得不通过模拟发送pong字符串来使它更有意义。来自坦桑尼亚的感谢! - Dilunga the Great

3

不行。

我尝试实现一个ping。但似乎没有办法强制浏览器WebSocket客户端设置除0x1(字符串)和0x2(二进制)之外的任何操作码。

const socket = new WebSocket(url);
ping() {
    if (socket?.readyState !== WebSocket.OPEN) {
      return;
    }
    const buffer = new ArrayBuffer(7);
    const dataView = new DataView(buffer);
    dataView.setInt8(0, 0x89);
    dataView.setInt8(1, 0x00);
    dataView.setInt8(2, 0x48);
    dataView.setInt8(3, 0x65);
    dataView.setInt8(4, 0x6c);
    dataView.setInt8(5, 0x6c);
    dataView.setInt8(6, 0x6f);
    socket.send(buffer);
  }
socket.addEventListener('open', ping);

这段代码应该发送一个带有"hello"消息体的最终ping帧(操作码为0x09,结束标志为0x8),但浏览器却发送了一个带有二进制数据(操作码为0x82)的最终帧,其消息体为"�hello",其中�代表0x89的数据。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接