Jetty Websockets - Correctly sending async message

2019-04-16 04:39发布

问题:

I'm using Jetty 9.3.5 and I would like to know what is the proper way to handle unreliable connections when sending websocket messages, specifically: I noticed cases when a websocket connection does not close normally so, even though the client side is down, it takes a lot of time until onClose() is triggered on the server (for ex. a user closes the laptop lid and puts it in standby - it can take 1-2 hours until the close event is received on the server side).

Thus, because the client is still registered, the server keeps sending messages that begin to build up. This becomes an issue when sending a large number of messages.

I've tested sending byte messages with:

Session.getRemote().sendBytes(ByteBuffer, WriteCallback)
Session.getRemote().sendBytesByFuture(ByteBuffer);

To simulate the connection down on one side (ie. user puts laptop in standby), on Linux, I assigned an IP address to eth0 interface, started sending the messages and then brought it down:

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

This way: the messages were still being sent by Jetty, the Chrome client did not receive them, the onCllose or onError was not triggered on server-side

My questions regarding Jetty are:

  1. Is there a way to clear queued messages that were not delivered? I've tried, but with no luck:

    Session.getRemote().flush();

  2. Can a max number of queued messages be set? I've tried:

    WebSocketServletFactory.getPolicy().setMaxBinaryMessageBufferSize(1)

  3. Can I detect if the client does not receive the message? (or if the connection is in abnormal state let's say) I've tried:

    session.getRemote().sendBytes(bb, new WriteCallback() {
    
                    @Override
                    public void writeSuccess() {
                        //print success                     }
    
                    @Override
                    public void writeFailed(Throwable arg0) {
                        //print fail
                    }
                });
    

But this prints success even though the messages are not received.

I also tried to use, but couldn't find a solution:

factory.getPolicy().setIdleTimeout(...);
factory.getPolicy().setAsyncWriteTimeout(3000);
sendPing()

Thanks in advance!

回答1:

Unfortunately, the WebSocket protocol, being a message passing protocol isn't really designed for this level of nuance between messages.

The first message MUST complete before you can even think of sending the next message. So if you have a message in process, then there is no way to safely cancel that message.

At best, an API could exist to truncate that message with a CONTINUATION / empty payload / fin=true.

But even then the remote endpoint wouldn't know that you canceled the message, it would just see a partial message.

Detecting connectivity issues is best handled with either OS level events (like Android's Connectivity intents), or via periodic websocket PING (which inserts itself in front of the line for outgoing websocket frames.

However, even with PING, if your outgoing websocket frame is in-progress, even the PING cannot be sent until that websocket frame is done sending.

RemoteEndpoint.flush() will attempt to flush any pending messages (and frames), not clear out pending messages (or frames).

As for detecting if client got the message, you'll need to implement some sort of message ACK into your own layer to verify that, the protocol has no such concept. (Some libs/apis built on top of websocket have implemented message ACK in that layer. The cometd message ack extension comes to mind as a real world example)

What sort of situation are you attempting to solve for?

Perhaps using the RemoteEndpoint.sendPartialString(String, boolean) or RemoteEndpoint.sendPartialBytes(ByteBuffer, boolean) to send smaller frames of the whole message could be useful to you. However, the other side might not have an API that can read those partial frames (eg: Javascript in a browser).