Windows 8 operative system will provide the full support for WebSockets (see also WebSockets.org). Simplified, It is a web technology which provides full-duplex communication over TCP initiated by preceding HTTP request. There are many innovative and amazing scenarios which can be easily achieved by using this technology. Fro example it is possible to implement chat-scenarios form browser to browser in the real time. previously, such scenarios by using various polling algorithms. As described in this post Microsoft has implemented WebSockets on different places in Windows 8 and .NET 4.5 Stack. This post describes how Web Socket can be hosted in an ASP.NET application and how it can be consumed by browser as a client.
Following are topics covered by this post:
1. What do you need to get started?
2. How to initialize Web Socket protocol
3. How to implement Web Socket Handler
4. How to initialize custom Web Socket Handlers
5. How to receive and send messages
6. How to shut down custom Web Socket Handler
7. How to implement JavaScript client
What do you need to get started?
Sample shown in this post requires Windows8 (client or server), IE10, IIS8 and ASP.NET 4.5. Before you start you need to enable WebSocket Protocol at the machine. To do that, go to windows features, expand World Wide Web Services | Application development Features and activate WebSocket Protocol. | |
How to initialize WebSocket protocol?
Upcoming version of System.Net, which is generally used by ASP.NET contains on the HttpContext the new property “IsWebSocketRequest” (since ver. 4.5). This property is internally mapped to property System.Net.HttpListener.IsWebSocketRequest. Wherever there is an access within ASP.NET application to HttpContext instance, you can check if the request is of WebSocket -type:
if(context.IsWebSocketRequest){ // Do something } |
This single line of code is very simple one, but there are few more or less undocumented facts behind the scene, which sometimes might be useful. When the snippet shown above is executing the IIS is not executing WebSocket-stream as you might expect. This is why IsWebSocketRequest-name is a bit wired. WebSocket is TCP based network stream which is different from common HTTP request which can be cached by HttpContext. This sounds wired, but it is not. Before WebSocket communication is started it has to be initiated. Because WebSocket protocol is an upgrade to HTTP, the spec requires using of HTTP to initiate WebSocket protocol.
Exactly this HTTP request is defined as WebSocketRequest. After successful completion of this request, WebSocket full-duplex is established.
WebSocketRequest is the commong HttpRequest with a little sugar. Here is an example of the real WebSocketRequest. When such request is sent by client the property IsWebSocketRequest will return true.
Cache-Control: no-cache Connection: Upgrade Upgrade: Websocket Host: daenetwebsocketconsumer User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) Origin: localhost Sec-WebSocket-Key: cDdQwtBTScmm1pzAJWvC/g== Sec-WebSocket-Version: 8 |
Implementation of this property will Internally do following check in pseudo-code:
IF Headers.Exists(“Upgrade”) AND HEADERS[“UPGRADE”].ToLower() == “websocket” THEN RETURN true
Another two properties Sec-WebSocket-Key and Sec-WebSocket-Version are also part of the request , but they will not be checked by this property. They will be internally handled to ensure the proper and compatible protocol communication.
The first one holds unique client context, so different clients can be distinguished. It represents some kind of session key.
The second one defines the WebSocket implementation version. The current working draft provides a number of different versions in the specification. For example at the day of writing of this post the last specified version is called HyBi 17. The Sec-WebSocket-Version would have a value 13 for HyBi 17. In the example above I used the Internet Explorer 10 (IE10) which obviously sends the version 8. According to specification number 8 is related to HyBi 10, which is currently supported by IE10 on Windows 8 preview.
To setup WebSocket-Duplex in ASP.NET application create one for example MySocketInitializer.ASHX (IHTTPHandler) file and put following code in there:
public void ProcessRequest(HttpContext context) { if(context.IsWebSocketRequest) { context.AcceptWebSocketRequest(new YourWebSocketHandlerImplementation()); } }
|
The implementation of method
IHttpHandler.ProcessRequest will invoke a (new) method
AcceptWebSocketRequest by giving the instance of your WebSocket implementation (
YourWebSocketHandlerImplementation ). This line of code starts up the Web Socket protocol.
How to implement Web Socket Handler?
The web socket handler is in ASP.NET stack the concrete implementation of abstract class Microsoft.Web.WebSockets.WebSocketHanlder. Note that there is also an implementation of the same functionality in System.ServiceModel.WebSockets.WebSocketService. The second one is the implementation of the same functionality in WCF. In this post I will focus on the handler implementation only.
Following snippet shows the declaration of the handler class.
namespace Microsoft.Web.WebSockets { public class WebSocketHandler { public WebSocketHandler(); public Exception Error { get; set; } public int MaxIncomingMessageSize { get; set; } public AspNetWebSocketContext WebSocketContext { get; set; } public void Close(); public virtual void OnClose(); public virtual void OnError(); public virtual void OnMessage(byte[] message); public virtual void OnMessage(string message); public virtual void OnOpen(); [EditorBrowsable(EditorBrowsableState.Never)] public Task ProcessWebSocketRequestAsync(AspNetWebSocketContext webSocketContext); public void Send(byte[] message); public void Send(string message); } } |
As you see there are few virtual methods which you should/can implement.
OnOpen
Method OnOpen should be used to perform any kind of initialization. This can be for example initialization of variables which will hold running sessions.
OnClose
In opposite, the method OnClose should be used clear the state of the handler when closed. Note that when OnClose is called, the handler is still running. That means, you can send messages within this methods to any connected client. For example this message can be something like: “bye – I’m going offline”.
OnMessage
This method should be overwritten, because this is the place where to catch messages sent from WebSocket clients.
Send
The method send is solely used to send the message to dedicated client. Note that architecture of Web Sockets in ASP.NET provides one WebScoketHandler instance per client. For example, if you implement the browser-multiplayer-game every player will get its own WebSocketHander instance.
How to initialize the custom Web Socket Handler?
When implementing the handler, there are few important tasks to do:
1. Pass arguments in constructor.
2. Add the WebSocketHandler instance to the list of connected instances
3..Notify clients
It is mostly required to grab same data from HTTP request and pass it as argument to the WebSocket constructor. For example in one game this could be the name of the user: In ASHX handler (IHTTPHandler) we would initialize the WebSocketHandler as follows:
public void ProcessRequest(HttpContext context) { if(context.IsWebSocketRequest) { context.AcceptWebSocketRequest( new YourWebSocketHandlerImplementation(context.Cookies[“UserName”))); } } |
This is the place where you can decouple HTTP from WebSocket protocol. Here you can grab everything you need from request and pass it to WebSocketHandler.
As next we need to persist the client WebSocket session. Following snippet shows the full implementation of the WebSocketHandler:
public class YourWebSocketHandlerImplementation : WebSocketHandler { private static WebSocketCollection m_Sessions = new WebSocketCollection (); public string m_Player; public YourWebSocketHandlerImplementation (string argument) { m_Player = argument; } public override void OnOpen() { m_Sessions.Add(this); m_Sessions.Broadcast(“New player joined the game”)); this.Send(“Welcome new player”); } } |
As already mentioned we can design the constructor as we want. This is the place where we receive all required parameters from IHTTPHandler. In the method
OnOpen we cover three tasks. First we persist the instance of the handler to list of type
WebSocketCollection. You do not have to do it this way, but if you wants to broadcast messages to all connected clients, this class provides a helpful method
Broadcast(). This method automatically enumerates all WebSocketHandler instances in the list and sends the message to all of them. With following line of code we notify all already connected players that new player has joined the game:
m_Sessions.Broadcast(“New player joined the game”));
If we want to send the message to dedicated client you can do it as follows:
this.Send(“Welcome new player”);
How to receive and Send messages
Till now we have seen how to start up and how to initialize the custom WebSocketHandler. Now we are ready to receive messages from connected clients. To do that you have to override the method OnMessage.
public override void OnMessage(string message) { m_Sessions.Broadcast(“User” + m_Player + “ sent following message: “ + message)); m_Sessions.Send(“You sent: “ + message); } |
In the first line we broadcast the received message to all connected users. In the second line we send echo of the message to the sender. This all is fine, but in the real world, you will have to deal with more complex messages. In fact you will always have to implement some kind sub protocol on top of Web Socket.
The easiest way to do that is to use JSON.
In my example I want to give the client the ability to send message to some specific user or to broadcast the message to all users. To do this I invented a simple protocol which can be modeled as follows:
enum MessageType { Send, Broadcast, Join, Leave }
class Message { public MessageType Type { get; set; } public string Value { get; set; } public string UserName { get; set; } } |
The class MessageType defines different types of messages. By this attribute I’m able to style the message by proprietary type.Depending on that value handler and client can perform some action. The message itself contains the UserName property which defines the name of the player in the game. The MessageType defines the type of message and Value is the message itself.
When the message is received by method OnMessage we can deserialize it as follows:
public override void OnMessage(string message) { var ser = new JavaScriptSerializer(); var msg = m_Ser.Deserialize<Message>(message); switch (msg.Type) { case MessageType.Broadcast: m_Sessions.Broadcast(m_Ser.Serialize(new { Type = msg.Type, UserName = m_Nick, Value = msg.Value })); break; case MessageType.Send: var channel = m_Sessions.FirstOrDefault(n => ((YourWebSocketHandlerImplementation)n).m_Nick == msg.UserName); if (channel != null) channel.Send(m_Ser.Serialize(new { Type = msg.Type, UserName = msg.UserName, Value = msg.Value })); break; default: return; } } } |
In this example the JavaScriptSerializer is used to materialize the typed instance message send over WebScoket protocol. If the message is of type Broadcast, it is sent to all connected clients. If it is of type Send, then we grab the appropriate WebSocket instance from the list of all sessions and use it to send the message to this dedicated user.
How to shut down the Web Socket?
If required you can perform any destructing operation in the method OnClose. There is nothing what you have to implement in this method. Depending of your application you can implement anything here. Following example shows how to notify all other connected users that one user is going down.
public override void OnClose() { m_Sessions.Broadcast(m_Ser.Serialize(new { type = MessageType.Leave, from = m_Nick, value = m_Nick + " has left the server." }));
m_Sessions.Remove(this); } |
Note that method close as any other method in the Web Socket Handler is tight to one session. While executing OnClose the socket is still fully functional. That means you can use it to send messages. In example above we broadcast one message to all connected users and finally remove the socket instance from the list of active sessions.
How to implement JavaScript client?
To this point this article briefly described how to implement the custom WebSocketHandler and how to build the custom messaging (protocol) on top of WebSocket protocol. This topic describes how to implement JavaScript client which can communicate with your custom socket implementation.
In general the WebSocket API should be supported by browsers with HTML5 support. The browser which I used in this sample is IE10, which supports HyBi 10 (or Ver. 8) od WebSocket spec.
Open Web Socket connection
To open the WebSocket connection create the instance of WebSocket API:
var connection = new WebSocket(“ws://websockethost:80/daenet.websocketsample/MySocketInitializer.ashx”);
The argument in constructor is the implementation of IHTTPHandler interface, which is responsible to register the custom implementation of the WebSocket.
After executing of the line above, the underlying implementation of WebSocket protocol in the browser will send the WebSocket compatible request (as described above) to url :“ws://websockethost:80/daenet.websocketsample/MySocketInitializer.ashx”:
The figure below shows simplified diagram of sequence initiated by creating WebSocket instance:
As you see when the WebSocket instance i s created browser will implicitelly send WebSocketRequest to ASHX file and ASP.NET will execute method ProcessRequest (see above “How to initialize WebSocket protocol”). This method will install the Web Socket Handler which will handle all messages.
Note that after this sequence one permanent TCP connection is open between client and web server.
Receive Message
Now we can hook the handler which will be invoked each time some message is received:
connection.onmessage = function (message) {
var data = window.JSON.parse(message.data);
};
Optionally if required the received message can be deserialized in a type by calling JSON.parse(message).
The figure below shows the sequence initiated when the method WebSocketHanlder.Send() is invoked at the server side. Note that the message send from server is transferred through open TCP connection.
Send Message
Following snippet shows how to send the message:
connection.send(“hello”);
And this example shows how to send the message formatted as JSON.
connection.send(window.JSON.stringify({ type: 1, Value: “Hello”, UserName = “damir” }));
The diagram below shows the sequence initiated when the send function is invoked.
Finally, when the socket needs to be closed following code has to be executed:
connection.close();
When the function close() is invoked in Javascript, the call is propagated to server side and your implementation of the WebSocketHandler.OnClose() is invoked (see “how to shut down the web socket”).
Putting all together
Following picture shows very simple application in engineer design which demonstrate using of web sockets. The application demonstrates using of all important WebSocket functions. It implement some kind of chat application. Following picture shows all supported scenarios.
Following HTML/Javascript code implements all described at this picture. The application requires the WebSocket Handlerwhich we described in this article.
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <title>WebSocket Sample </title> <script src="Scripts/jquery-1.6.3.min.js"></script> <script src="Scripts/jquery.cookie.js" type="text/javascript"></script> <script src="Scripts/json2.min.js" type="text/javascript"></script> <script> var username; $(function () { "use strict"; username = prompt("Please enter your username:"); $.cookie('username', username); $("#userName").val(username);
// Machine ‘websockethost’ must support WebSocket protocol. For example Windows 8 with IIS8 var host = "ws://websockethost:80/daenet.websocketsample/MySocketInitializer.ashx"; $("#join").click(function () { var connection = new WebSocket(host); connection.onmessage = function (message) { var data = window.JSON.parse(message.data); $("<li/>").html("[" + data.UserName + "]: " + data.Value).appendTo($("#messages")); }; $("#broadcast").click(function () { connection.send(window.JSON.stringify({ type: 1, Value: $("#msg").val() })); }); $("#send").click(function () { connection.send(window.JSON.stringify({ type: 0, Value: $("#msg").val(), UserName: $("#nick").val() })); }); $("#close").click(function () { connection.close(); }); }); }); </script> </head> <body> <h2>Daenet WebSocket Sample</h2> <form> <h2 id="username" style="font-family: 'Segoe UI'">user: [<input type="text" id="userName" />] <input type="button" id="join" value="Join session" /></h2> <label for="msg">Message to be sent:</label> <input type="text" id="msg" size="50" /> <br /> <input type="button" id="broadcast" value="Broadcast message to all users" /> <br/> <label for="nick">User to send the message:</label> <input type="text" id="nick" value="user?" /> <input type="button" id="send" value="Send message to user" /> <br /> <input type="button" id="close" value="Close session" /> <br /> </form> <ul id="messages"> </ul> </body> </html>
|
Posted
Jan 29 2012, 06:55 PM
by
Damir Dobric