Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Streaming Data over WebSockets

4.89/5 (8 votes)
28 Oct 2013CPOL3 min read 46.6K   1.2K  
Implementing a simple message delivery system in C#

Introduction

There is an application server with many web browser connected to it, we want to read a message from each one of them and send it to everybody like in a chat.

Background

On the client side, we have some Ajax to handle the websocket and jquery to handle the user interface.

On the server side, we use a very simple web application built over the NetFluid Application Server Framework.

Using the Code

The code is absolutely simple, it's composed of three files:

  • Hoster.cs: Initialize the application server and self host the web application
  • SocketManager.cs: It contains all the C# code used on the server side
  • ./UI/index.html: It contains all the user interface and JavaScript

Setting Up

Hoster.cs is the entry point of our web application. It initializes the application server, sets up the basic configuration and loads the current assembly to self host our webapp.
It's not strictly necessary to setup these values programmatically, you can also do it from the configuration.

C#
static void Main() 
{ // Setup the application server, hosting the current assembly 
Engine.Load(Assembly.GetCallingAssembly()); // Enable console log of received request 
Engine.DevMode = true; // Set all files inside "Public" 
                       // as public and downloadable by the client (styles, js..) 
Engine.AddPublicFolder("/","./Public",true); // Add an HTTP interface 
                                                                 // on our application server 
Engine.Interfaces.AddInterface("127.0.0.1", 8080); // Makes the web application runs 
Engine.Start(); //Prevent application from closing ! 
Console.ReadLine(); 
} </section /><section />

The Server Side

In SocketManager.cs, we got the core of our web application: the websocket server.
For simplicity reasons, we are going to use a line-based communication protocol, the client sends a line, the server reads a line and sends this line to all clients.

C#
// On "/channel" uri the application server will call this method
[Route("/channel")]
public void Channel()
{
	// Add the current client to the clients list
	clients.Add(Context);

	while (true)
	{
		try
		{
			// Read the message
			var msg = Context.Reader.ReadLine();

			// Send to all
			foreach (var client in clients)
			{
				client.Writer.WriteLine(msg);
				client.Writer.Flush();
			}
		}
		catch (Exception)
		{
			//Client goes timeout
			break;
		}
	}

	//The client has close the connection, so we remove it from the list
	var trash = Context;
	clients.TryTake(out trash);
}

Note: SocketManager inherits the NetFluid.FluidPage type witch is required to host NetFluid callable methods.

Adding Some Bacon

In the first version of this article, the server re-sent the string and the client appended into a table.
It was a very simple way to keep trace of data but also a very simple way to make your browser explode on high traffic volume so I decided to keep on the server the last six messages received and to send to clients the whole actual state of the server, they will replace the current HTML state with the new one instead of appending it.

C#
[Route("/channel")]
public void Channel()
{
	//Save the current client into the recipients list
	clients.Add(Context);

	while (true)
	{
		try
		{
			// Take the message from the client
			messages.Enqueue(Context.Reader.ReadLine());

			// Store just the last six messages of clients
			if (messages.Count>6)
			{
				string trashMessage;
				messages.TryDequeue(out trashMessage);
			}

			foreach (var client in clients)
			{
				//join message build the message table for clients
				client.Writer.WriteLine(string.Join("",messages));
				client.Writer.Flush();
			}
		}
		catch (Exception)
		{
			//Client goes timeout
			break;
		}
	}

	//The client has close the connection, so we remove it from the list
	var trash = Context;
	clients.TryTake(out trash);
}

The Client Side

If the server side code was simple, the client side code is stupid.
All it has to do is to take a string the user, open a web socket, send the string and if there is any incoming message, take the composite from the server and display it.

HTML

HTML
<div class="container">
	<div>
		<h1>WebSocket streaming data</h1>
		<div>
			<p>
				<!– To keep trace of who is writing what >
				<span>Nickname</span>
				<input type="text" class="form-control" 
				id="nick" placeholder="Nickname">
			</p>
			<p>
				<!– The message to be sent >
				<span>Text message</span>
				<input type="text" class="form-control" 
				id="msg" placeholder="Text message">
			</p>
			
			<!– Send the message and reset the input >
			<button id="send" type="submit" 
			class="btn btn-default">Send</button>
			
			<!– Start a brute force message spamming >
			<button id="spam" type="submit" 
			class="btn btn-default">Let's spam !</button>
		</div>
		<hr />
		<!– Incoming composite will be displayed here >
		<div id="incoming" class="row"></div>

		<!– Websocket status debugging >
		<h3>Socket status</h3>
		<div id="status"></div>
	</div>
</div>

JavaScript Binding

To setup a WebSocket, all you need to do is to instantiate a new WebSocket class (if your browser supports it).

JavaScript
// Address of our websocket server 
var wsUri = "ws://localhost:8080/channel";

function init()
{
	var sock = new WebSocket(wsUri);
	sock.onopen = function (evt) { $("#status").html("CONNECTING"); };
	sock.onclose = function (evt) { $("#status").html("CLOSED"); };
	sock.onerror = function (evt) { $("#status").html("ERROR"); };
	sock.onmessage = function (evt)
	{
		// HERE IT TAKE THE INCOMING COMPOSITE AND DISPLAY IT
		$("#incoming").html(evt.data);
	};
	$("#status").html("CONNECTED");
	return sock;
}

var websocket = init();

Client will send message if you click on Send Message button or you press enter on focused input textbox:

JavaScript
//Send the message clicking on the button
$("#send").click(function (event)
{
	//Prevent to send empty messages
	if ($("#msg").val() == "")
		return;

	//prevent submitting or other unwanted js handlers
	event.preventDefault();

	//send the message in input textbox
	sendMessage($("#msg").val());

	//reset the input textbox
	$("#msg").val("");
});

//Send the message pressing Enter
$("#msg").keypress(function( event )
{
	//Prevent to send empty messages
	if (event.which == 13 && $("#msg").val()!="")
	{
		//prevent submitting or other unwanted js handlers
		event.preventDefault();

		//send the message in input textbox
		sendMessage($("#msg").val());

		//reset the input textbox
		$("#msg").val("");
	}
});

Fancy things: For simple testing and debugging, the client encapsulates every message into a random coloured box with nick name of the sender and time stamp, "Let's spam" button will test the capacity of your web application starting a unstoppable repeated event which will send the given message every 30 milliseconds.
Style of coloured box is roughly imported from Twitter Boostrap Alerts.

JavaScript
// Random color to distinguish incoming messages
// names used are from Twitter Boostrap Alerts css class
var colors = new Array();
colors[0] = "success";
colors[1] = "info";
colors[2] = "warning";
colors[3] = "danger";

function randomColor()
{
	return colors[Math.floor(Math.random()*3)];
}

//Formats the message inside a div and sent it to the server
function sendMessage(msg)
{
	var div = $("<div class=\"col-md-4 alert alert-" + 
	randomColor() + "\"></div>");
	div.append($("<strong>" + $("#nick").val()+ 
	"</strong>"));
	div.append($("<h3>" + msg + "</h3>"));
	div.append($("<small>" + (new Date().toUTCString()) + 
	"</small>"));

	var fake = $("<div></div>");
	fake.append(div);
	var outer = fake.html();

	if (websocket.readyState == WebSocket.CLOSING || 
			websocket.readyState == WebSocket.CLOSED)
		websocket = init();

	websocket.send(outer+"\r\n");
}

//Spam messages to the server every 30 milliseconds
$("#spam").click(function (event)
{
	event.preventDefault();
	var msg = prompt("Message", "");

	if (msg != null)
	{
		setInterval(function (){sendMessage(msg);}, 30);
	}
});

Points of Interest

I am not responsible for any attack of epilepsy caused by the spam mode :D

History

  • First public version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)