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.
static void Main()
{
Engine.Load(Assembly.GetCallingAssembly());
Engine.DevMode = true;
Engine.AddPublicFolder("/","./Public",true);
Engine.Interfaces.AddInterface("127.0.0.1", 8080);
Engine.Start();
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.
[Route("/channel")]
public void Channel()
{
clients.Add(Context);
while (true)
{
try
{
var msg = Context.Reader.ReadLine();
foreach (var client in clients)
{
client.Writer.WriteLine(msg);
client.Writer.Flush();
}
}
catch (Exception)
{
break;
}
}
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.
[Route("/channel")]
public void Channel()
{
clients.Add(Context);
while (true)
{
try
{
messages.Enqueue(Context.Reader.ReadLine());
if (messages.Count>6)
{
string trashMessage;
messages.TryDequeue(out trashMessage);
}
foreach (var client in clients)
{
client.Writer.WriteLine(string.Join("",messages));
client.Writer.Flush();
}
}
catch (Exception)
{
break;
}
}
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
<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).
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)
{
$("#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:
$("#send").click(function (event)
{
if ($("#msg").val() == "")
return;
event.preventDefault();
sendMessage($("#msg").val());
$("#msg").val("");
});
$("#msg").keypress(function( event )
{
if (event.which == 13 && $("#msg").val()!="")
{
event.preventDefault();
sendMessage($("#msg").val());
$("#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.
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)];
}
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").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