Wednesday, April 11, 2012

NancyFx combined with WebSocket server in one executable

Wednesday, April 11, 2012 Posted by Andre Broers , , , , , 17 comments
For a project here at OrionSoftware we needed a test application that displays realtime results to a webpage. Nice moment to digg a bit deeper in the WebSockets protocol. It only works on certain browsers so you can't demo on all browsers. At least chrome works, so that is the browser I use during this sample.

SuperWebSocket


To get websockets working in c# I will use a library because the implementation details of the different protocols are quite technical and subject to change as you can see on http://superwebsocket.codeplex.com/. You can download the binaries from this location: http://superwebsocket.codeplex.com/releases/view/84246.

Start


We begin with the creation of a new console application project in VisualStudio 2010 and call it MyWebSocket.Change the profile to a Full .Net Profile in the project properties. I always create an assemblies map in the root of the solution and add all my external referenced assemblies in here that aren't included via nuget. Unfortunately the SuperWebSocket server library isn't in nuget at this time so I extract the binary assemblies from the Net40 map of SuperWebSocket in this map.

Now I create references to the libraries by using Add Reference - Browse - and selecting all the assemblies in the assemblies folder.

We also need a reference to the System.Configuration assembly.

Now we need to add the NancyFX libraries by using nuget:
PM> install-package Nancy
Successfully installed 'Nancy 0.10.0'.
Successfully added 'Nancy 0.10.0' to MyNancy.

PM> install-package Nancy.Hosting.Self
Attempting to resolve dependency 'Nancy (= 0.10.0)'.
Successfully installed 'Nancy.Hosting.Self 0.10.0'.
Successfully added 'Nancy.Hosting.Self 0.10.0' to MyNancy.

PM>

Now it is time to show the code of the servers. One for NancyFX to host our webapplication and one for our WebSocket server.

NancyFX Server


We create a standard nancy host that works with a standard module displaying the index.html as the default route.
var host = new NancyHost(new Uri("http://127.0.0.1:8080"));
host.Start();  // start hosting

Console.WriteLine("Nancy Started at http://127.0.0.1:8080");
while (true) { Thread.Sleep(1000); }
host.Stop();  // stop hosting

And our module MainModule.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nancy;
using System.Dynamic;

namespace MyWebSocket
{
    public class MainModule : NancyModule
    {
        public MainModule()
        {
            Get["/"] = x =>
            {
                dynamic Model = new ExpandoObject();
                return View["index", Model];
            };
        }
    }
}

Now we create a new folder in our project and call it Views and add a new index.html view in it. Remember to set the CopyAlways property to this file. For now we leave the file as is. We will edit this file in the client section of this sample.

SuperWebSocket Server


Now create our second server which will respond to our websockets call from the client.
var socketServer = new WebSocketServer();
socketServer.Setup(new RootConfig(),
    new ServerConfig
    {
        Name = "SuperWebSocket",
        Ip = "Any",
        Port = 8181,
        Mode = SocketMode.Async,
    }, SocketServerFactory.Instance);

socketServer.NewMessageReceived += new SessionEventHandler<WebSocketSession, string>(socketServer_NewMessageReceived);
socketServer.NewSessionConnected += new SessionEventHandler<WebSocketSession>(socketServer_NewSessionConnected);
socketServer.SessionClosed += new SessionEventHandler<WebSocketSession, CloseReason>(socketServer_SessionClosed);
socketServer.Start();
while (true) { Thread.Sleep(1000); }
socketServer.Stop();

This will create a socketserver which reacts to three events. When a new session connection comes in it will send a message to all clients. When a session disconnects it will inform the other clients. When a message comes in it will send this message to all clients.
        private static List<WebSocketSession> m_Sessions = new List<WebSocketSession>();
        private static object m_SessionSyncRoot = new object();

        static void socketServer_NewMessageReceived(WebSocketSession session, string e)
        {
            SendToAll(session.IdentityKey + ": " + e);
        }

        static void socketServer_NewSessionConnected(WebSocketSession session)
        {
            lock (m_SessionSyncRoot)
                m_Sessions.Add(session);

            SendToAll("System: " + session.IdentityKey + " connected");
        }

        static void socketServer_SessionClosed(WebSocketSession session, CloseReason reason)
        {
            lock (m_SessionSyncRoot)
                m_Sessions.Remove(session);

            if (reason == CloseReason.ServerShutdown)
                return;

            SendToAll("System: " + session.IdentityKey + " disconnected");
        }

        static void SendToAll(string message)
        {
            lock (m_SessionSyncRoot)
            {
                foreach (var s in m_Sessions)
                {
                    s.SendResponseAsync(message);
                }
            }
        }

This will keep a list of all sessions m_Sessions. We need a lock on this object because it will be read and written to by multiple threads.

Complete the server


To run both servers I choose to create two tasks with both servers as a delegate. A keypress will end the program.

Here is the complete source:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperWebSocket;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketBase;
using SuperSocket.SocketEngine;
using Nancy.Hosting.Self;
using System.Threading;
using System.Threading.Tasks;

namespace MyWebSocket
{
    class Program
    {
        private static List<WebSocketSession> m_Sessions = new List<WebSocketSession>();
        private static object m_SessionSyncRoot = new object();

        static void Main(string[] args)
        {
            var tokenSource = new CancellationTokenSource();
            CancellationToken ct = tokenSource.Token;

            // Create a task and supply a user delegate by using a lambda expression.
            var taskA = new Task(() =>
            {
                var host = new NancyHost(new Uri("http://127.0.0.1:8080"));
                host.Start();  // start hosting

                Console.WriteLine("Nancy Started at http://127.0.0.1:8080");
                while (!ct.IsCancellationRequested) { Thread.Sleep(1000); }
                host.Stop();  // stop hosting
            }, tokenSource.Token);

            var taskB = new Task(() =>
            {
                var socketServer = new WebSocketServer();
                socketServer.Setup(new RootConfig(),
                    new ServerConfig
                    {
                        Name = "SuperWebSocket",
                        Ip = "Any",
                        Port = 8181,
                        Mode = SocketMode.Async,
                    }, SocketServerFactory.Instance);

                socketServer.NewMessageReceived += new SessionEventHandler<WebSocketSession, string>(socketServer_NewMessageReceived);
                socketServer.NewSessionConnected += new SessionEventHandler<WebSocketSession>(socketServer_NewSessionConnected);
                socketServer.SessionClosed += new SessionEventHandler<WebSocketSession, CloseReason>(socketServer_SessionClosed);
                socketServer.Start();
                while (!ct.IsCancellationRequested) { Thread.Sleep(1000); }
                socketServer.Stop();
            }, tokenSource.Token);

            // Start the task.
            taskA.Start();
            taskB.Start();
            Console.ReadKey();
            tokenSource.Cancel();
        }

        static void socketServer_NewMessageReceived(WebSocketSession session, string e)
        {
            SendToAll(session.IdentityKey + ": " + e);
        }

        static void socketServer_NewSessionConnected(WebSocketSession session)
        {
            lock (m_SessionSyncRoot)
                m_Sessions.Add(session);

            SendToAll("System: " + session.IdentityKey + " connected");
        }

        static void socketServer_SessionClosed(WebSocketSession session, CloseReason reason)
        {
            lock (m_SessionSyncRoot)
                m_Sessions.Remove(session);

            if (reason == CloseReason.ServerShutdown)
                return;

            SendToAll("System: " + session.IdentityKey + " disconnected");
        }

        static void SendToAll(string message)
        {
            lock (m_SessionSyncRoot)
            {
                foreach (var s in m_Sessions)
                {
                    s.SendResponseAsync(message);
                }
            }
        }

    }
}

Client Side


The client side is our created index.html. It's all arranged from javascript.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>MyWebSocket</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#button").click(function() { socket.send($("#message").val()); }).attr("disabled", "disabled");
            var socket = new WebSocket('ws://localhost:8181/sample');
            socket.onopen = function () {
                $("#messages").append("handshake successfully established. May send data now...<br/>");
                $("#button").removeAttr("disabled");
            };
            socket.onclose = function () {
                $("#messages").append("connection closed<br/>");
                $("#button").attr("disabled", "disabled");
            };
            // when data is comming from the server, this metod is called
            socket.onmessage = function (evt) {
                $("#messages").append("# " + evt.data + "<br/>");
            };
        });
    </script>
</head>
<body>
    <label for="message">Message: </label>
    <input id="message" type="text" />
    <button id="button" type="button">Send</button>
    <div id="messages"></div>
</body>
</html>

Here we have a simple chat client. It has an input where you can type a message and it will be distributed to all other clients when you click the button. The button will only be active when the connection to our websocket server is established.

In the jquery document ready callback we create a new WebSocket object with the url matching the server. We create three callbacks on this object. And our chat application is complete.

Run the Chat Application


When we run the application from VisualStudio we can browse with two Chrome windows to our NancyFX url: http://127.0.0.1:8080  and we can chat.




17 comments:

  1. I am wondering why the address of the websocket server is localhost:8181/sample? Where does the "/sample" come from?

    ReplyDelete
  2. Good question. I think you can leave it away. I think it can be used as a command to the websocketserver. Like the routing in Nancy.
    I don't have the source at hand so I can't try it out.

    ReplyDelete
  3. It's awesome to go to see this site and reading the views of all colleagues regarding this article, while I am also keen of getting experience.

    Also visit my web site: resistor Resistance

    ReplyDelete
  4. Its lіke you leаrn mу thoughts!
    You appear tο grаsρ so much about thіs, like you wrote thе booκ in іt or something.
    I think thаt уou juѕt cаn dο with some peгcеnt to foгсe thе message home a little
    bit, but instеаd of that, that is greаt blog.
    A fantastic read. I'll definitely be back.

    My web page; cermet

    ReplyDelete
  5. I thіnκ this is one of thе mοst ѵіtal info for me.
    Аnԁ i am glad readіng уour article.
    Βut wanna remark on few geneгal thingѕ, The website stylе is wonderful, the articleѕ is reаllу gгеat :
    D. Gοoԁ job, сheеrs

    Fеel frее to surf to mу webpage wirewound resistor

    ReplyDelete
  6. Hmm it seems likе youг ωebsіte ate my first commеnt
    (it ωаѕ suρer long) so I gueѕs I'll just sum it up what I wrote and say, I'm thоroughly enjoying yοur blοg.
    I аs well am an asрiгing blog writer but I'm still new to the whole thing. Do you have any tips for beginner blog writers? I'd genuinely appreciatе it.


    Feel fгee to surf to my web-ѕite;
    Resistor Resistance

    ReplyDelete
  7. I'm extremely impressed with your writing skills as well as with the layout on your weblog. Is this a paid theme or did you modify it yourself? Either way keep up the nice quality writing, it is rare to see a great blog like this one these days.

    Also visit my web-site :: resistor resistance (http://robotc.mx/)

    ReplyDelete
  8. I rarely leave responses, hοweνer I browsed
    a fеw of the rеѕponses on this ρage "NancyFx combined with WebSocket server in one executable".
    I dο have 2 queѕtions for you if yоu do not mind.
    Coulԁ it be only me оr ԁo a fеw of thе commentѕ
    come асrosѕ like thеy aгe coming from brain dead people?
    :-P And, іf you are writіng
    at other placeѕ, І would like to follow eνеrything new you hаve to post.
    Woulԁ you make a liѕt of all of youг social pages likе yοuг twitter feeԁ, Facebook pаge οr linkedin рrofile?


    Feеl free to visit my webpаge resistors 

    ReplyDelete
  9. Woω that ωas odd. I just wrote an reallу long comment but аfter I clicκed ѕubmit mу comment dіdn't show up. Grrrr... well I'm not
    writing аll thаt over again. Anyhоw, just wanted to say exсellent blog!


    My blοg ... Power Rating

    ReplyDelete
  10. đồng tâm
    game mu
    cho thuê nhà trọ
    cho thuê phòng trọ
    nhac san cuc manh
    số điện thoại tư vấn pháp luật miễn phí
    văn phòng luật
    tổng đài tư vấn pháp luật
    dịch vụ thành lập công ty trọn gói
    lý thuyết trò chơi trong kinh tế học
    đức phật và nàng audio
    hồ sơ mật dinh độc lập audio
    đừng hoang tưởng về biển lớn ebook
    chiến thắng trò chơi cuộc sống ebook
    bước nhảy lượng tử
    ngồi khóc trên cây audio
    truy tìm ký ức audio
    mặt dày tâm đen audio
    thế giới như tôi thấy ebook

    phần không chịu được ánh mắt sắc lang của Lưu Phong. Hôm nay là lần đầu tiên nàng mặc nội khố này, mặc dù khe hở giữa hai mông bị quần lót thít chặt vào hơi khó chịu, nhưng để có thể tạo được sự hấp dẫn với Lưu Phong, chút khó chịu này có là gì đâu.
    Không thể phủ nhận, kỳ vọng hấp dẫn Lưu Phong của Ân Tố Tố đã đạt được, bởi vì trước mắt kích thích đã làm cho bên trong nội khố của Lưu Phong không chịu được, hùng khởi một cách dữ tợn.
    " Lão công, để thiếp giúp chàng cởi......" Mặc dù chưa nói xong, nhưng là hai tay của nàng đã tự giải phóng nhục bổng của Lưu Phong khỏi nội khố bó sát người. Người Lưu Phong hoàn toàn trần truồng, hắn cảm giác được phía dưới

    nhục bổng của hắn nhanh chóng không kiêng kỵ bại lộ dưới con mắt của nữ nhân.
    Ân Tố Tố rất thẹn thùng, rất muốn nhắm mắt lại, nhưng cái đầu hồng hồng, bóng lưỡng của nhục bổng kia lại hấp dẫn ánh mắt của nàng. Nàng thẹn thùng, đỏ mặt lớn mật nhìn nhục bổng nọ, nó chính là nguyên nhân gây cho nàng sự vui sướng. Cái… vật này thật to lớn, lòng Ân Tố Tố như nai con đập loạn lên.

    Hơi e ngại nhưng lại là thứ nàng chờ mong.
    Ân Tố Tố đột nhiên lớn mật dùng miệng ngậm lấy cây nhục bổng kia, tự mình dùng miệng ôn nhuyễn bao vây nó.

    Thân thể Lưu Phong run lên, rên rỉ một tiếng thoải mái.

    Nhất thời, hắn nhìn bốn phía một lượt, không để cho động tác kích thích này bị quay lén.
    Vỗ vỗ đầu một chút, hắn cười thầm chính mình hồ đồ, thời đại này làm gì có camera mà quay lén? Càng không thể có camera công nghệ cao để quay trong điều kiện bây giờ được.

    ReplyDelete
  11. So does SuperWebSockets support binary transport? Can't seem to find any examples.

    Also, you used Nancy, but would Katana work too?

    ReplyDelete