Tuesday Nov 19, 2013

Optimized WebSocket broadcast

Broadcast scenario is one of the most common use cases for WebSocket server-side code, so we are going to evaluate usability of current version of WebSocket API for Java to do that and suggest some improvements.

Please note that this post is experimental by its nature. You can use Tyrus features mentioned in this article, but anything can change any time, without warning.

When speaking about broadcast, let's define what that actually is. When message is broadcasted,  it means that it is sent to all connected clients. Easy right? Common WebSocket sample application is chat (Tyrus is not an exception, see ./samples/chat) which does exactly that - when client sends a message to server endpoint, message gets re-sent to all clients so they can see it.

If we ignore authentication and authorisation, we can shrink server-side implementation to following code:

@OnMessage
public void onMessage(Session s, String m) throws IOException {
  for (Session session : s.getOpenSessions()) {
    session.getBasicRemote().sendText(m);
   }
}

Which works well and provides expected functionality. Underlying code must process the message for every connected client, so the DataFrame which will be sent on the is constructed n-times (where n is number of connected clients). Everything now depends on processing time required for creating single data frame. That operation is not expensive per say, but just the fact it needs to be invoked that many times creates a bottle neck from it. Another important fact is that the WebSocket connection has no state, so once created data frame can be sent to as many clients as you want. So in another words, we don't really need to do create data frame multiple times, especially when we know that the message is the same for all connected clients.

WebSocket API does not allow consumers to access messages on data frame level and also does not provide way how to send already constructed data frame. That might come in some next version of the specification... so what can we do now?

If you are using Tyrus (1.3 or newer), you can try an optimized version of the same use-case:

@OnMessage
public void onMessage(Session s, String m) {
  ((TyrusSession) s).broadcast(m);
}

This way, data frame will be constructed only once which will save server-side resources and additionally clients will receive broadcasted message in shorter period. "broadcast" method returns Map<Session, Future<?>> which can be used for getting the info about which message was already send and which wasn't. Version with callback is not yet available, might be added later (if you'd want to have this feature, please send us a note to users@tyrus.java.net).

I don't have any exact measurements to confirm performance gain when using Tyrus broadcast, but seems like it may be significant, especially for higher count of connected clients.

(note to JDK8 users: the first scenario can also be improved by using fork/join framework. It was intentionally ignored in this article, since Tyrus need to stick with Java SE 7 for now)

If you have any questions, don't hesitate and ask at users@tyrus.java.net.

And, as always, list of related links:

Monday Nov 04, 2013

Asynchronous connectToServer

Users of JSR-356 – Java API for WebSocket are probably familiar with WebSocketContainer#connectToServer method. This article will be about its usage and improvement which was introduce in recent Tyrus release.

WebSocketContainer#connectToServer does what is says, it connects to WebSocketServerEndpoint deployed on some compliant container. It has two or three parameters (depends on which representation of client endpoint are you providing) and returns aSession. Returned Session represents WebSocket connection and you are instantly able to send messages, register MessageHandlers, etc.

An issue might appear when you are trying to create responsive user interface and use this method – its execution blocks until Session is created which usually means some container needs to be started, DNS queried, connection created (it’s even more complicated when there is some proxy on the way), etc., so nothing which might be really considered as responsive. Trivial and correct solution is to do this in another thread and monitor the result, but.. why should users do that? :-) Tyrus now provides async* versions of all connectToServer methods, which performs only simple (=fast) check in the same thread and then fires a new one and performs all other tasks there. Return type of these methods is Future<Session>.

List of added methods:

As you can see, all connectToServer variants have its async* alternative. All these methods do throw DeploymentException, same as synchronous variants, but some of these errors cannot be thrown as a result of the first method call, so you might get it as the cause ofExecutionException thrown when Future<Session>.get() is called.

Please let us know if you find these newly added methods useful or if you would like to change something (signature, functionality, …) – you can send us a comment to users@tyrus.java.net or ping me personally.

Related links:

About

Pavel Bucek

Search

Categories
Archives
« November 2013 »
SunMonTueWedThuFriSat
     
1
2
3
5
6
7
8
9
10
11
12
13
14
15
16
17
18
20
21
22
23
24
25
26
27
28
29
30
       
Today