This is part two of my notes for my session about websockets that I’ll be presenting in two weeks at the Orlando Code Camp.
So… the server… having server that replies back whatever you send to it is fascinating but not very useful. I’ll extend the server from part one to allow the clients that are connected to it to talk to each other.
The first thing the server must do when receives a message is to parse it into a command and a payload:
public class Server : WebSocketHandler { public override void OnOpen() { Say("hello from server"); } public override void OnMessage(string message) { try { if (message == string.Empty) throw new Exception("empty command"); string command; ParseCommand(ref message, out command); // handle known commands... throw new Exception("unrecognized command"); } catch (Exception x) { Say("ERROR " + x.Message); } } public void Say(string message) { JavaScriptSerializer serializer = new JavaScriptSerializer(); base.Send(serializer.Serialize(message)); } void ParseCommand(ref string message, out string command) { int pos = message.IndexOf(' '); command = message.Substring(0, pos); message = message.Remove(0, pos); } }
The first thing a client must do after establishing connection is to introduce itself (“Hi, I’m John”) before he is able to send any commands. To do that the client will send a message “LOGIN John”. The first word of the message is the command (LOGIN) and the payload is the user name. When the server receives the message it will see the command LOGIN and will reply with “hello John”. No other commands will be allowed until the client has logged in.
if (command == "LOGIN") { UserName = message; Say("hello " + UserName); return; } if (UserName == string.Empty) throw new Exception("unauthorized"); // other commands...
Now on the server we have a bunch of connections and we have the ability to assign a UserName to each connection. If we want want one connection to be able to send a message to another we need to build a little bit of plumbing in the form of a static class that keep a collection of all open connections:
public static class Connections { static List<Server> connections = new List<Server>(); public static void Add(Server server) { lock (connections) { connections.Add(server); } } public static void Remove(Server server) { lock (connections) { connections.Remove(server); } } public static Server Find(string userName) { lock (connections) { return connections .Where(s => s.UserName == userName) .FirstOrDefault(); } } }
We’ll have to change the HTTP handler to add the connection to that collection when a new connection is established:
public class WS : IHttpHandler { public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { if (context.IsWebSocketRequest) { var server = new Server(); Connections.Add(server); context.AcceptWebSocketRequest(server); } } }
And also when a connection is closed it needs to be removed from the collection:
public class Server : WebSocketHandler { public string UserName { get; private set; } // ... public override void OnClose() { Connections.Remove(this); base.OnClose(); } }
Now we are in business…
if (command.StartsWith("@")) { string recipient = command.Remove(0, 1); var conn = Connections.Find(recipient); if (conn == null) throw new Exception("user " + recipient + " is not online"); conn.Say(UserName + ": " + message); return; }
To test that we can open 2 browser windows, in the first one will login as John and in the second one we’ll login as Mary. Then John can send Mary a message “@Mary How are you doing?”