The websocket protocol specification is approaching final and the Jetty implementation and API have been tracking the draft and is ready when the spec and browsers are available.   More over, Jetty release 7.5.0 now includes a capable websocket java client that can be used for non browser applications or load testing. It is fully asynchronous and can create thousands of connections simultaneously.

This blog uses the classic chat example to introduce a websocket server, client and load test.

The project

The websocket example has been created as a maven project with groupid com.example.  The entire project can be downloaded from here.   The pom.xml defines a dependency on org.eclipse.jetty:jetty-websocket-7.5.0.RC1 (you should update to 7.5.0 when the final release is available), which provides the websocket API and transitively the jetty implementation.  There is also a dependency on org.eclipse.jetty:jetty-servlet which provides the ability to create an embedded servlet container to run the server example.

While the project implements a Servlet, it is not in a typical webapp layout, as I wanted to provide both client and server in the same project.    Instead of a webapp, this project uses embedded jetty in a simple Main class to provide the server and the static content is served from the classpath from src/resources/com/example/docroot.

Typically developers will want to build a war file containing a webapp, but I leave it as an exercise for the reader to put the servlet and static content described here into a webapp format.

The Servlet

The Websocket connection starts with a HTTP handshake.  Thus the websocket API in jetty also initiated by the handling of a HTTP request (typically) by a Servlet.  The advantage of this approach is that it means that websocket connections are terminated in the same rich application space provided by HTTP servers, thus a websocket enabled web application can be developed in a single environment rather than by collaboration between a HTTP server and a separate websocket server.

We create the ChatServlet with an init() method that instantiates and configures a WebSocketFactory instance:

public class ChatServlet extends HttpServlet
{
  private WebSocketFactory _wsFactory;
  private final Set _members = new CopyOnWriteArraySet();
  @Override
  public void init() throws ServletException
  {
    // Create and configure WS factory
    _wsFactory=new WebSocketFactory(new WebSocketFactory.Acceptor()
    {
      public boolean checkOrigin(HttpServletRequest request, String origin)
      {
        // Allow all origins
        return true;
      }
      public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
      {
         if ("chat".equals(protocol))
           return new ChatWebSocket();
         return null;
      }
    });
    _wsFactory.setBufferSize(4096);
    _wsFactory.setMaxIdleTime(60000);
  }
  ...

The WebSocketFactory is instantiated by passing it an Acceptor instance, which in this case is an anonymous instance. The Acceptor must implement two methods: checkOrigin, which in this case accepts all; and doWebSocketConnect, which must accept a WebSocket connection by creating and returning an instance of the WebSocket interface to handle incoming messages.  In this case, an instance of the nested ChatWebSocket class is created if the protocol is “chat”.   The other WebSocketFactory fields have been initialised with hard coded buffers size and timeout, but typically these would be configurable from servlet init parameters.

The servlet handles get requests by passing them to the WebSocketFactory to be accepted or not:

  ...
  protected void doGet(HttpServletRequest request,
                       HttpServletResponse response)
    throws IOException
  {
    if (_wsFactory.acceptWebSocket(request,response))
      return;
    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       "Websocket only");
  }
  ...

All that is left for the Servlet, is the ChatWebSocket itself.   This is just a POJO that receives callbacks for events.  For this example we have implemented the WebSocket.OnTextMessage interface to restrict the call backs to only connection management and full messages:

  private class ChatWebSocket implements WebSocket.OnTextMessage
  {
    Connection _connection;
    public void onOpen(Connection connection)
    {
      _connection=connection;
      _members.add(this);
    }
    public void onClose(int closeCode, String message)
    {
      _members.remove(this);
    }
    public void onMessage(String data)
    {
      for (ChatWebSocket member : _members)
      {
        try
        {
          member._connection.sendMessage(data);
        }
        catch(IOException e)
        {
          e.printStackTrace();
        }
      }
    }
  }

The handling of the onOpen callback is to add the ChatWebSocket to the set of all members (and remembering the Connection object for subsequent sends).  The onClose handling simply removes the member from the set.   The onMessage handling iterates through all the members and sends the received message to them (and prints any resulting exceptions).

 

The Server

To run the servlet, there is a simple Main method that creates an embedded Jetty server with a ServletHandler for the chat servlet, as ResourceHandler for the static content needed by the browser client and a DefaultHandler to generate errors for all other requests:

public class Main
{
  public static void main(String[] arg) throws Exception
  {
    int port=arg.length>1?Integer.parseInt(arg[1]):8080;
    Server server = new Server(port);
    ServletHandler servletHandler = new ServletHandler();
    servletHandler.addServletWithMapping(ChatServlet.class,"/chat/*");
    ResourceHandler resourceHandler = new ResourceHandler();
    resourceHandler.setBaseResource(Resource.newClassPathResource("com/example/docroot/"));
    DefaultHandler defaultHandler = new DefaultHandler();
    HandlerList handlers = new HandlerList();
    handlers.setHandlers(new Handler[] {servletHandler,resourceHandler,defaultHandler});
    server.setHandler(handlers);
    server.start();
    server.join();
  }
}

The server can be run from an IDE or via maven using the following command line:

mvn
mvn -Pserver exec:exec

The Browser Client

The HTML for the chat room simply imports some CSS and the javascript before creating a few simple divs to contain the chat text, the join dialog and the joined dialog:

<html>
 <head>
 <title>WebSocket Chat Example</title>
 <script type='text/javascript' src="chat.js"></script>
 <link rel="stylesheet" type="text/css" href="chat.css" />
 </head>
 <body>
  <div id='chat'></div>
  <div id='input'>
   <div id='join' >
    Username:&nbsp;<input id='username' type='text'/>
    <input id='joinB' class='button' type='submit' name='join' value='Join'/>
   </div>
   <div id='joined' class='hidden'>
    Chat:&nbsp;<input id='phrase' type='text'/>
    <input id='sendB' class='button' type='submit' name='join' value='Send'/>
   </div>
  </div>
  <script type='text/javascript'>init();</script>
 </body>
</html>

The javascript create a room object with methods to handle the various operations of a chat room.  The first operation is to join the chat room, which is triggered by entering a user name.  This creates a new WebSocket object pointing to the /chat URL path on the same server the HTML was loaded from:

var room = {
  join : function(name) {
    this._username = name;
    var location = document.location.toString()
      .replace('http://', 'ws://')
      .replace('https://', 'wss://')+ "chat";
    this._ws = new WebSocket(location, "chat");
    this._ws.onopen = this.onopen;
    this._ws.onmessage = this.onmessage;
    this._ws.onclose = this.onclose;
  },
  onopen : function() {
    $('join').className = 'hidden';
    $('joined').className = '';
    $('phrase').focus();
    room.send(room._username, 'has joined!');
  },
  ...

The javascript websocket object is initialised with call backs for onopen, onclose and onmessage. The onopen callback is handled above by switching the join div to the joined div and sending a “has joined” message.

Sending is implemented by creating a string of username:message and sending that via the WebSocket instance:

  ...
  send : function(user, message) {
    user = user.replace(':', '_');
    if (this._ws)
      this._ws.send(user + ':' + message);
  },
  ...

If the chat room receives a message, the onmessage callback is called, which sanitises the message, parses out the username and appends the text to the chat div:

  ...
  onmessage : function(m) {
    if (m.data) {
      var c = m.data.indexOf(':');
      var from = m.data.substring(0, c)
        .replace('<','<')
        .replace('>','>');
      var text = m.data.substring(c + 1)
        .replace('<', '<')
        .replace('>', '>');
      var chat = $('chat');
      var spanFrom = document.createElement('span');
      spanFrom.className = 'from';
      spanFrom.innerHTML = from + ': ';
      var spanText = document.createElement('span');
      spanText.className = 'text';
      spanText.innerHTML = text;
      var lineBreak = document.createElement('br');
      chat.appendChild(spanFrom);
      chat.appendChild(spanText);
      chat.appendChild(lineBreak);
      chat.scrollTop = chat.scrollHeight - chat.clientHeight;
    }
  },
  ...

Finally, the onclose handling empties the chat div and switches back to the join div so that a new username may be entered:

  ...
  onclose : function(m) {
    this._ws = null;
    $('join').className = '';
    $('joined').className = 'hidden';
    $('username').focus();
    $('chat').innerHTML = '';
  }
};

With this simple client being served from the server, you can now point your websocket capable browsers at http://localhost:8080 and interact with the chat room. Of course this example glosses over a lot of detail and complications a real chat application would need, so I suggest you read my blog is websocket chat simpler to learn what else needs to be handled.

The Load Test Client

The jetty websocket java client is an excellent tool for both functional and load testing of a websocket based service.  It  uses the same endpoint API as the server side and for this example we create a simple implementation of the OnTextMessage interface that keeps track of the all the open connection and counts the number of messages sent and received:

public class ChatLoadClient implements WebSocket.OnTextMessage
{
  private static final AtomicLong sent = new AtomicLong(0);
  private static final AtomicLong received = new AtomicLong(0);
  private static final Set<ChatLoadClient> members = new CopyOnWriteArraySet<ChatLoadClient>();
  private final String name;
  private final Connection connection;
  public ChatLoadClient(String username,WebSocketClient client,String host, int port)
  throws Exception
  {
    name=username;
    connection=client.open(new URI("ws://"+host+":"+port+"/chat"),this).get();
  }
  public void send(String message) throws IOException
  {
    connection.sendMessage(name+":"+message);
  }
  public void onOpen(Connection connection)
  {
    members.add(this);
  }
  public void onClose(int closeCode, String message)
  {
    members.remove(this);
  }
  public void onMessage(String data)
  {
    received.incrementAndGet();
  }
  public void disconnect() throws IOException
  {
    connection.disconnect();
  }

The Websocket is initialized by calling open on the WebSocketClient instance passed to the constructor.  The WebSocketClient instance is shared by multiple connections and contains the thread pool and other common resources for the client.

This load test example comes with a main method that creates a WebSocketClient from command line options and then creates a number of ChatLoadClient instances:

public static void main(String... arg) throws Exception
{
  String host=arg.length>0?arg[0]:"localhost";
  int port=arg.length>1?Integer.parseInt(arg[1]):8080;
  int clients=arg.length>2?Integer.parseInt(arg[2]):1000;
  int mesgs=arg.length>3?Integer.parseInt(arg[3]):1000;
  WebSocketClient client = new WebSocketClient();
  client.setBufferSize(4096);
  client.setMaxIdleTime(30000);
  client.setProtocol("chat");
  client.start();
  // Create client serially
  ChatLoadClient[] chat = new ChatLoadClient[clients];
  for (int i=0;i<chat.length;i++)
    chat[i]=new ChatLoadClient("user"+i,client,host,port);
  ...

Once the connections are opened, the main method loops around picking a random client to speak in the chat room

  ...
  // Send messages
  Random random = new Random();
  for (int i=0;i<mesgs;i++)
  {
    ChatLoadClient c = chat[random.nextInt(chat.length)];
    String msg = "Hello random "+random.nextLong();
    c.send(msg);
  }
  ...

Once all the messages have been sent and all the replies have been received, the connections are closed:

  ...
  // close all connections
  for (int i=0;i<chat.length;i++)
    chat[i].disconnect();

The project is setup so that the load client can be run with the following maven command:

mvn -Pclient exec:exec

And the resulting output should look something like:

Opened 1000 of 1000 connections to localhost:8080 in 1109ms
Sent/Received 10000/10000000 messages in 15394ms: 649603msg/s
Closed 1000 connections to localhost:8080 in 45ms

Yes that is 649603 messages per second!!!!!!!!!!! This is a pretty simple easy test, but it is still scheduling 1000 local sockets plus generating and parsing all the websocket frames. Real applications on real networks are unlikely to achieve close to this level, but the indications are good for the capability of high throughput and stand by for more rigorous bench marks shortly.

 

 

 


16 Comments

Angelo · 22/08/2011 at 07:55

Hi Greg,
Many thanks for this article. WebSocketClient that I have never tested is a very cool feature! For information (yes you know already that), I have explained step by step the websocket chat sample with Embedding Jetty at http://angelozerr.wordpress.com/about/websockets_jetty/
Regards Angelo

Suvanan · 24/08/2011 at 18:12

Hi Greg,
Thanks for the article. When is Jetty 7.5.0 and cometd 2.4.0 scheduled to be GA?
– Suvanan

    gregw · 24/08/2011 at 23:56

    September 1 is the target date. RC’s are available already.

gregw · 02/09/2011 at 07:41

The client API has been updated a little since this blog:
http://webtide.intalio.com/2011/09/jetty-websocket-client-api-updated/

tornike · 21/01/2012 at 13:11

great article thanks a lot. it works as dynamic web project in eclipse as well, without embedding jetty into the app. the only thing i found was chat object missing in client javascript room. if anyone needs, adding the following after onopen function solves the error.
chat : function(text) {
if (text != null && text.length > 0)
room._send(room._username, text);
},

pc repairs · 30/01/2012 at 10:19

Load testing is the process of putting demand on a system or device and measuring its response. Load testing is performed to determine a system’s behavior under both normal and anticipated peak load conditions. It helps to identify the maximum operating capacity of an application as well as any bottlenecks and determine which element is causing degradation.

Daniel · 21/02/2012 at 23:53

Hi, I have tried following the steps in creating the server but I cannot understand what classes I am to import. Is Jetty an API that is used in the example please? Thanks,

    Patinya · 08/04/2012 at 20:59

    Hi JanThanks for the quick response. Good news .A clean nitsall and build of r91 worked perfectly (webapps are available and working) aftermvn nitsall (complains that no path to android home)mvn -Dandroid.home= nitsall./deploy.shBut I then attempted to repeat the process delete i-jetty, check it out again, same steps as above and was back at the same state as in my previous post. ie i-jetty apparently working but the webapps don’t appear.The only difference between non-working and working states was a reboot in between. So I rebooted and the problem was resolved. I am now unable to replicate the problem at all.Therefore it must have been something about my machine’s state (Ubuntu 8.04 inside VirtualBox on OSX 10.5.4).My apologies for taking up your time with the problem. If I _can_ replicate it I’ll let you know.Thanks again.Steve

Kay · 22/02/2012 at 00:35

Do you know of any way that we could possibly move the websocket out to a different thread?

    Giovanni · 08/04/2012 at 08:48

    Hi Huadong,Well, even if dynamic class idloang is supported by the jvm, there’s still a way to go to make webapplication development easy. For example, to support jsp on-the-fly compilation, the Dalvik jvm would need to support byte-code-generation, and as I understand it this is also missing.As that facility is missing, in order to support jsps, you’d need to precompile them, and precompile them for the Dalvik jvm. So you’d need to mess around with generating the java code using jspc, then run the Dalvik compiler on the generated files. So that just makes webapp dev that bit more difficult.Leaving aside jsps, and the need to compile your whole webapp with the Dalvik compiler, there’s still the issue that there’s no way of sharing libraries on the Dalvik platform. So, for example, it would be convenient if you could use the published jetty libs from the i-jetty project rather than having to import the sources into your project and build them.In the short term, if you want to do webapp development, I’d stay away from jsps, selectively import the jetty sources into your webapp project, and then write a little class with a main to start a jetty server and deploy your webapp (some examples are in the examples/embedded directory of the jetty distro).If the future, I’d like to write a jetty deployer that would be able to load an Android .apk bundle that contains a webapp from say the sdcard.regardsJan

Tito · 17/04/2012 at 08:58

Hello
i’m using jetty to make a chat application in my website.
but i’m asking, if it’s possible to create a websocket session between 2 users?
or in generally , to choose which user you want to talk with.
assuming that there is just one server…
thanks for the reply

Don McGregor · 28/06/2012 at 22:47

I can talk directly to the jetty server just fine, but how can I put the Jetty server behind an Apache front end? Ideally I’d like to put several Jetty servers behind the Apache web server and load balance them. From my reading I think the typical mod_proxy in apache doesn’t work with web sockets.

sysfix · 25/03/2013 at 05:20

The term load testing is used in different ways in the professional software testing community. Load testing generally refers to the practice of modeling the expected usage of a software program by simulating multiple users accessing the program concurrently. As such, this testing is most relevant for multi-user systems; often one built using a client/server model, such as web servers. However, other types of software systems can also be load tested. For example, a word processor or graphics editor can be forced to read an extremely large document; or a financial package can be forced to generate a report based on several years’ worth of data. The most accurate load testing simulates actual use, as opposed to testing using theoretical or analytical modeling.

Greg Wilkins: Websocket Example: Server, Client and LoadTest · 22/08/2011 at 07:23

[…] Read more here: Greg Wilkins: Websocket Example: Server, Client and LoadTest […]

Websocket Example: Server, Client and LoadTest | Eclipse | Syngu · 23/08/2011 at 05:36

[…] release 7.5.0 now includes a capable websocket java client that can be used for non browser… Read more… Categories: Eclipse     Share | Related […]

Jetty WebSocket Client API updated | Webtide Blogs · 02/09/2011 at 07:40

[…] With the release of Jetty 7.5.0 and the latest draft 13 of the WebSocket protocol, the API for the client has be re-factored a little since my last blog on WebSocket: Server, Client and Load Test. […]

Comments are closed.