Tags:
create new tag
, view all tags

How to: Create a Chat Application with a WebSocket

2016-04-05 - 07:56:55 by PeterThoeny in Development
WebSockets are a relatively recent addition to the World Wide Web. Sockets are well known in Unix and Linux for many years, used to create endpoints for communication between processes that look like files. The WebSocket specification takes that to the web, defining an API to establish a full-duplex communication channel between a web browser and a server over a single TCP connection. Once established, the connection is persistent, which allows for fast communication between a browser and a server.

This blog explains how WebSockets work, and we create a simple web chat application as an example. The server side part is done in Perl, the browser side is done in JavaScript and JQuery. This blog is not TWiki specific, but has been created and tested in a TWiki environment.

In order to establish the full-duplex communication channel, the WebSocket protocol requires a handshake (carried out over http:// or https://) to switch over to the WebSocket protocol. This handshake effectively upgrades the communication protocol to ws:// (or wss:// for SSL protected channels). This complexity is nicely hidden in black boxes, which allows us to understand and use WebSockets relatively easily.

Let's first look at the server side. WebSocket libraries have been written in many languages. Here is the complete code of a web chat application written in Perl:

#!/usr/bin/perl -w

use Net::WebSocket::Server;

my $port = 8082;
print "Starting WebSocket chat server on port $port, press Ctr-C to disconnect\n";
Net::WebSocket::Server->new(
    listen => $port,
    on_connect => sub {
        my ($serv, $conn) = @_;
        $conn->on(
            utf8 => sub {
                my ($conn, $msg) = @_;
                print "got: $msg\n";
                $_->send_utf8($msg) for( $serv->connections() );
            },
        );
    },
)->start;

First install the Net::WebSocket::Server library, which can be done via CPAN. Then we create a script with above content. It creates a Net::WebSocket::Server object with new(), then starts it with start(). The structure we pass to new() specifies how to initialize the WebSocket:

  • listen defines the port number to listen to.
  • on_connect defines the function that is called when a connection is established successfully. It has two parameters:
    • $serv - server object
    • $conn - single connection object
  • The connection object method $conn->on() defines the action to take when a message is received from that connection.
  • When an utf8 message arrives, we print it to the console, and broadcast the message back to all currently established connections.

So that's all there is to a WebSocket based chat server! We could enhance the script to support authentication and the run under SSL. For details consult CPAN:Net::WebSocket::Server.

Now on to the user agent, aka web browser. The browser side piece of the chat application is a bit more complex due to the UI and error handling we must provide, but the actual WebSocket communication part is very short. Example minimal WebSocket client:

<script>
    var ws = new WebSocket("ws://chat.example.com:8082/");
    ws.onopen = function() {
        // Web Socket is connected, send data using send()
        ws.send( 'Hello server' );
    };
    ws.onmessage = function( event ) {
        var message = event.data;
        alert( 'Message received: ' + message );
    };
    ws.onclose = function() {
        // websocket is closed.
        alert( 'Connection is closed... ');
    };
</script>

This code is pretty much self-explanatory. Now on to the chat application. We first look at the HTML, then the CSS, and finally at the JavaScript code. The HTML is short:

<div id="chatContainer">
<div id="chatLog"></div>
<div id="chatInput">
<p>Web chat - enter nickname & message, and hit the enter key:</p>
<input id="chatUser" type="text" placeholder="nickname" />:
<input id="chatText" type="text" placeholder="message" />
<button id="chatButton">Disconnect</button>
</div>
</div>

Explanation:

  • The outer div tag is the chat container; it has two elements:
  • A div with chatLog ID, used as the chat log.
  • A div with chatInput ID, used for input. It has two input fields; one for the nickname of the user, and one for the chat text. It also contains a button to connect to and disconnect from the server.

Now on to the style sheet, seen to the right. We define the dimensions of the chat container and the chat log. We limit the height of the chat log, and add a vertical scrollbar if needed. That is basically it if we ignore the styles that make the chat box look pretty.

<style>
#chatContainer {
  width: 800px;
  padding: 10px;
  border: 5px solid #aaa;
  background-color: #f8f8f8;
}
#chatLog {
  max-height: 300px;
  overflow-y: auto;
  padding: 5px;
  border: 1px solid gray;
  background-color: #fff;
}
#chatLog p {
  margin: 0;
}

#chatUser {
  width: 80px;
}
#chatText {
  width: 600px;
}
.event {
  color: #999;
}
.warning {
  font-weight: bold;
  color: #ccc;
}
</style>

The last browser-side piece is the JavaScript code. It contains the guts of the chat application. We have several chat-related functions, and a few JQuery actions to handle the user interface:

<script>
var webSocket;

function connectWsChat(){
  try {
    var host = 'ws://chat.example.com:8082/';
    webSocket = new WebSocket( host );
    logWsChatMessage( '<p class="event">Socket status: ' + 
        webSocket.readyState + '</p>' );
    webSocket.onopen = function() {
      logWsChatMessage( '<p class="event">Socket status: ' +
          webSocket.readyState + ' (open)</p>' );
      $( '#chatButton' ).text( 'Disconnect' );
    }
    webSocket.onmessage = function( msg ) {
      logWsChatMessage( '<p class="message">' + msg.data + '</p>' );
    }
    webSocket.onclose = function() {
      logWsChatMessage( '<p class="event">Socket status: ' +
          webSocket.readyState + ' (closed)</p>' );
      $( '#chatButton' ).text( 'Connect' );
    }
  } catch( exception ) {
    logWsChatMessage( '<p>Error ' + exception + '</p>' );
  }
}

function isConnectedWsChat() {
  if( webSocket && webSocket.readyState==1 ) {
    $( '#chatButton' ).text( 'Disconnect' );
    return 1;
  } else {
    $( '#chatButton' ).text( 'Connect' );
    return 0;
  }
}

function sendWsChat() {
  if( isConnectedWsChat() ) {
    var chatUser = $( '#chatUser' ).val();
    var chatText = $( '#chatText' ).val();
    if( chatUser=='' || chatText=='' ){
      logWsChatMessage(
          '<p class="warning">Please enter a nickname and message</p>' );
      return;
    }
    try{
      webSocket.send( chatUser + ': ' + chatText );
      //logWsChatMessage( '<p class="event">Sent: ' + chatText + '</p>' )
    } catch( exception ){
      logWsChatMessage( '<p class="warning"> Error: ' + exception + '</p>' );
    }
    $( '#chatText' ).val( '' );
  }
}

function logWsChatMessage(msg) {
  var chatLog = $( '#chatLog' );
  chatLog.append( '<p>' + msg + '</p>' );
  chatLog.scrollTop( chatLog.prop( 'scrollHeight' ) );
}

$(document).ready( function() {

  if( !( 'WebSocket' in window ) ) {
    $( '#chatInput').fadeOut( 'fast' );
    $( '<p>Oh no, you need a browser that supports WebSockets.</p>' )
        .appendTo( '#chatContainer' );
  } else {
    connectWsChat();
  }

  $( '#chatText' ).keypress( function( event ) {
    if( event.keyCode == '13' ) {
      sendWsChat();
    }
  });

  $( '#chatButton' ).click( function() {
    if( webSocket && webSocket.readyState==1 ) {
      webSocket.close();
      $( this ).text( 'Connect' );
    } else {
      //webSocket.open();
      connectWsChat();
      $( this ).text( 'Disconnect' );
    }
  });

  $( window ).unload( function() {
    if( isConnectedWsChat() ) {
      webSocket.close();
    }
  });

});
</script>

Explanation:

  • We declare a global webSocket variable, it will contain the WebSocket object once the connection is established.

  • function connectWsChat() - establish the connection to the chat server
    • First we create a WebSocket object, pointing it to ws://chat.example.com:8082 (replace that with the domain and port number used by your chat server)
    • The onopen defines the function called when the connection is opened. We put a connection status message into the chat log, and set the button text to "Disconnect".
    • The onmessage defines the function called when a message is received from the server. We put that message into the chat log.
    • The onclose defines the function called when the connection is closed. We put a connection status message into the chat log, and set the button text to "Connect"

  • function isConnectedWsChat() - check and return the connection status, and update the button text
    • If the connection is open we set the button text to "Disconnect", and return 1.
    • Else we set the button text to "Connect", and return 0.

  • function sendWsChat() - send a message to the chat server
    • We get the nickname and the chat text from the input fields.
    • If text is not empty and if the connection is open we send the message to the server with the WebSocket's send() method.

  • function logWsChatMessage(msg) add a message to the chat log
    • We append the message to the chat div tag, and scroll to the bottom, so that the latest message is always visible.

Using the JQuery ready state, we handle the user interface:

  • If the browser does not support WebSocket we add an error message to the chat container.
  • Else we try to connect to the chat server. The result is shown in the chat log; status 1 is shown on success. An error message is shown if the connection can't be established, such as because the chat server is down, or if there is a firewall issue (on server, or company firewall).

  • $( '#chatText' ) - we detect a newline (key code '13') in the input field to submit the chat text.

  • $( '#chatButton' ) - we use the click event to either connect to or disconnect from the server, depending on the connection state. We also update the button text accordingly.

  • $( window ).unload() - we disconnect from the chat server on window unload, such as when the user closes the browser tab

Overall pretty simple, I was surprised how easy this was. I hope you enjoyed reading this and that you could learn something new. Let me know in the comments below what you think.

Comments

.

Edit | Attach | Watch | Print version | History: r2 < r1 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r2 - 2016-04-05 - PeterThoeny
 

Twitter Delicious Facebook Digg Google Bookmarks E-mail LinkedIn Reddit StumbleUpon    
  • Help
  • Learn about TWiki  
  • Download TWiki
This site is powered by the TWiki collaboration platform Powered by Perl Hosted by OICcam.com Ideas, requests, problems regarding TWiki? Send feedback. Ask community in the support forum.
Copyright © 1999-2017 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.