Saturday, March 12, 2011

Keeping WebSockets alive

The problem with using stateful connections on an imperfect place as the internet is that connections might drop. The server or an intermediary can drop the connection due to an idle timeout. Even a temporary problem at the server or a local network hiccup might kill your connection.

If you aren't prepared to handle these scenarios, you will not be able to fully rely on WebSockets.

A simple solution

The simplest solution is checking every few seconds whether the WebSocket is still opened. This might suffice in a good amount of scenarios, but a lot of other scenarios require more stable connectivity.

$(document).ready(function () {        
    setInterval(openWebSocket, 5000);
}
 
function openWebSocket(){
    if (websocket.readyState === undefined || websocket.readyState > 1) {
        ...
    }
}

Keepalives

As mentioned before, the server or an intermediate might drop the connection when the connection becomes idle. To prevent this, you could make your server send keepalive messages at predefined intervals.

I implemented this in the HTML5 Labs WCF Server.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
                 ConcurrencyMode = ConcurrencyMode.Multiple)]
public class CadSubscriptionService : WebSocketsService {
    private static KeepAliveBroadcaster _keepAliveBroadcaster;
 
    public CadSubscriptionService() {           
        if (_keepAliveBroadcaster == null) {
            _keepAliveBroadcaster = new KeepAliveBroadcaster();
        }
    }
 
    public override void OnOpen() {
        _keepAliveBroadcaster.AddService(this);
        base.OnOpen();
    }
 
    protected override void OnClose(object sender, EventArgs e) {
        _keepAliveBroadcaster.RemoveService(this);
        base.OnClose(sender, e);
    }     
}

The KeepAliveBroadcaster class maintains a list of connected sessions. Using a Timer it sends a message to each client every 15 seconds. This should stop the connection from dropping due to an idle timeout.

public class KeepAliveBroadcaster {
    private List<WebSocketsService> _services;
    private Timer _timer;
 
    public KeepAliveBroadcaster() {
        _timer = new Timer((o) => {
            if (_services == null) {
                return;
            }
            lock (_services) {
                if (_services.Count > 0) {
                    foreach (var service in _services) {
                        service.Send("staying alive!");
                    }
                }
            }
        }, null, 100, 15000);              
    }
 
    public void AddService(WebSocketsService service) {
        if (_services == null) {
            _services = new List<WebSocketsService>();
        }
        lock (_services) {               
            _services.Add(service);
        }
    }
 
    public void RemoveService(WebSocketsService service) {
        lock (_services) {
            _services.Remove(service);
        }
    }
}

Reopening ASAP

Something you could also implement is reopening the connection as soon as it closes.

Retry opening the connection when the onclose event fires. Think about limiting the number of retries, or you might end up with an infinite loop.

websocket.onclose = function (event) {            
    $('#status').html('Socket closed');    
    
    retryOpeningWebSocket();
};
 
function retryOpeningWebSocket(){
    if (retries < 2) {            
        setTimeout(openWebSocket, 1000);            
        retries++;
    }
}

Some afterthought

Depending on what type of connectivity you require, these solutions might not suffice. If you can't afford to lose a single message, you might want to think about implementing queues at the client and server to overcome a gap of connection loss. Maybe you even want to implement some sort of acknowledge messaging. Something for a future post maybe!

Start experimenting yourself?

These links might get you started:

9 comments:

  1. The protocol has a keep-alive mechanism built in - ping and pong frames.

    ReplyDelete
  2. Hi Paul

    Thanks for your feedback.

    This was released a day after this post. It still doesn't cover all scenarios though. You still need to think about reopening when something goes wrong.

    ReplyDelete
  3. The WebSocket protocol may have ping and pong frames but the W3C WebSocket API doesn't expose them:

    http://dev.w3.org/html5/websockets/#ping-and-pong-frames

    ReplyDelete
  4. Jef,

    I'm trying to implement the simple solution but I'm not sure what goes in place of the "..."

    I'm not a programmer ... just someone who pokes and dabbles until it works.

    Thanks
    John

    ReplyDelete
    Replies
    1. You would reinitialize your socket there: websocket = new WebSocketDraft(url).

      Delete
  5. websocket.readyState remains in state(1) even for network disconnections. So this can not be relied as a web socket keepalive mechanism.

    ReplyDelete
  6. I would like to know how to keep the connection persistent when we reload the same page ?

    ReplyDelete
    Replies
    1. Reloading the page will dispose the socket. What is the problem you're having?

      Delete