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 中设置的会话属性