Jetty Websockets - 在处理不可靠的连接时正确发送异步消息
Posted
技术标签:
【中文标题】Jetty Websockets - 在处理不可靠的连接时正确发送异步消息【英文标题】:Jetty Websockets - Correctly sending async messages when handling unreliable connections 【发布时间】:2016-01-19 09:13:45 【问题描述】:我正在使用 Jetty 9.3.5,我想知道在发送 websocket 消息时处理不可靠连接的正确方法是什么,特别是:我注意到 websocket 连接没有正常关闭的情况,即使客户端侧向下,在服务器上触发 onClose() 需要很长时间(例如,用户关闭笔记本电脑盖并将其置于待机状态 - 可能需要 1-2 小时才能在服务器上收到关闭事件服务器端)。
因此,由于客户端仍处于注册状态,服务器会不断发送开始构建的消息。这在发送大量消息时会成为一个问题。
我已经测试过发送字节消息:
Session.getRemote().sendBytes(ByteBuffer, WriteCallback)
Session.getRemote().sendBytesByFuture(ByteBuffer);
为了在一侧模拟连接中断(即用户将笔记本电脑置于待机状态),在 Linux 上,我为 eth0 接口分配了一个 IP 地址,开始发送消息,然后将其关闭:
ifconfig eth0 192.168.1.1
ifconfig eth0 up
--- start sending messages (simple incremented numbers) and connect using Chrome browser and print them ---
ifconfig eth0 down
这样:Jetty 还在发送消息,Chrome 客户端没有收到,onClose 或 onError 没有在服务器端触发
我关于 Jetty 的问题是:
有没有办法清除队列中未传递的消息? 我试过了,但没有运气:
Session.getRemote().flush();
可以设置最大排队消息数吗? 我试过了:
WebSocketServletFactory.getPolicy().setMaxBinaryMessageBufferSize(1)
我可以检测客户端是否没有收到消息吗? (或者如果连接处于异常状态,比如说) 我试过了:
session.getRemote().sendBytes(bb, new WriteCallback()
@Override
public void writeSuccess()
//print success
@Override
public void writeFailed(Throwable arg0)
//print fail
);
但是即使没有收到消息,这也会打印成功。
我也试过用,但没找到解决办法:
factory.getPolicy().setIdleTimeout(...);
factory.getPolicy().setAsyncWriteTimeout(3000);
sendPing()
提前致谢!
【问题讨论】:
【参考方案1】:不幸的是,作为消息传递协议的 WebSocket 协议并不是真正为这种级别的消息之间的细微差别而设计的。
必须先完成第一条消息,然后才能考虑发送下一条消息。因此,如果您正在处理一条消息,则无法安全地取消该消息。
充其量,可以存在一个 API 来截断带有 CONTINUATION / 空负载 / fin=true 的消息。
但即使这样,远程端点也不知道您取消了消息,它只会看到部分消息。
最好使用操作系统级别的事件(如 android 的连接意图)或通过定期 websocket PING(将自身插入到传出 websocket 帧的行前)来处理检测连接问题。
但是,即使使用 PING,如果您的传出 websocket 帧正在进行中,则在该 websocket 帧完成发送之前,即使 PING 也无法发送。
RemoteEndpoint.flush()
将尝试刷新任何待处理的消息(和帧),而不是清除待处理的消息(或帧)。
至于检测客户端是否收到消息,您需要在自己的层中实现某种消息 ACK 来验证,协议没有这样的概念。 (一些建立在 websocket 之上的 libs/apis 已经在该层实现了消息 ACK。cometd message ack extension 是一个真实世界的例子)
您试图解决什么样的情况?
也许使用RemoteEndpoint.sendPartialString(String, boolean)
或RemoteEndpoint.sendPartialBytes(ByteBuffer, boolean)
发送整个消息的较小帧可能对您有用。但是,对方可能没有可以读取这些部分帧的 API(例如:浏览器中的 javascript)。
【讨论】:
我认为我正在寻找的是更准确地监控连接状态,我认为最好的解决方案是发送自定义“ping / pong”消息,但从客户端发送到服务器。服务器定期检查所有连接的客户端,如果没有收到“Ping”,它会断开它/它们。在客户端,如果没有收到“Pong”,则连接终止,并且可以开始使用退避重试(或其他东西,取决于需要)。这样,服务端和客户端都会知道连接是否有问题。 如果 WebSocket 帧正在被写入或读取,但尚未完成,则无法发送或读取 PING 或 PONG。请记住,每个 RFC-6455 base framing 的单个 WebSocket 帧的大小最多可达 18,446,744,073,709,551,615 字节(64 位无符号值的最大值)。 是的,但我指的是使用由客户端发送到服务器的自定义消息,以及为每一端(客户端和服务器)使用自定义超时 cu 的计时器来检查对方是否有发送了回复/回复。如果超时到期,相应的一方(客户端或服务器)将关闭套接字。 请记住,当您使用 .close() 时,大多数 websocket API 层并没有关闭套接字,您只是要求启动 Websocket 关闭握手,并且必须等到握手完成控制权返回到本地端点(或发生超时)之前的两个端点,此时底层 tcp/ip 套接字关闭。很少有 websocket API 允许苛刻和立即关闭底层网络层。 好的,但在这种情况下不会强制关闭 / session/disconnect() 帮助吗?如果不是,那么 session.close() 和 session.disconnect() 有什么区别?以上是关于Jetty Websockets - 在处理不可靠的连接时正确发送异步消息的主要内容,如果未能解决你的问题,请参考以下文章
在 jetty 中部署 webapps 和 websockets
使用 JSR-356(websockets)的 Jetty 8 配置
jetty 9,websockets 和关闭服务器端的 websocket 连接
Jetty 中的 JSR-356 javax websockets(嵌入式和非嵌入式)
是否可以在没有 Jetty 的 Eclipse 中使用 Java WebSockets?
在 WebSocketServlet (Jetty WebSockets) 中访问在 HttpServlet 中设置的会话属性