Pull to refresh

WebSocket & ASP.NET

Reading time 8 min
Views 13K
html5В этом топике я хочу рассказать, как организовать WebSocket соединение между браузером, поддерживающим WebSocket и ASP.NET приложением.
В статье описано, как организовать подключение и отослать сообщение подключенному клиенту. Так же, в конце статьи, есть ссылка на исходники рабочего приложения.

Статья и приложение просто пример того, как это все работает, и как можно связать ASP.NET и WebSockets, и вряд ли претендует на руководство по внедрению в текущем виде, однако хорошо демонстрирует основы и может быть доработано.


Зачем нужны WebSockets и в чем их плюсы думаю рассказывать не требуется. Лично я считаю, что их очень не хватает, для того, что бы можно было делать полноценные клиентские приложения на JavaScript без уловок типа Comet (отличная статья тут) или периодических запросов.

Но конкретно меня эта технология серьезно заинтересовала при поиске решения небольшой «проблемы» работы с Comet в браузере Chrome.

При организации по технологии Comet некоторые браузеры, например Chrome, ведут себя так, что кажется будто страница постоянно грузится, тот же Chrome пишет «ожидание запроса» и курсор неизменно остается в состоянии ожидания.
Курсор в Chrome при использовании Comet

Это конечно же неприятно.
Да и просто, зачем использовать обходной путь, когда для этого есть более подходящая технология и представляется возможность ее изучить.

Итак само соединение. На клиентской стороне (JavaScript) браузер предоставляет специальный класс WebSocket.
Вот его описание отсюда
image

Используя его, мы открываем соединение с сервером и подписываемся на необходимые события, а так же отсылаем сообщения серверу после соединения.

        var sock;
        function connectToServer() {
            try {
                sock = new WebSocket("ws://localhost:8181/websock");
                sock.onopen = sockOpen;
                sock.onerror = sockError;
                sock.onclose = sockClosed;
                sock.onmessage = sockMessage;
            } catch (e) {
                log("error:" + e);
            }
        }


* This source code was highlighted with Source Code Highlighter.


Как видно из кода выше, наш сервер будет ждать соединений по адресу localhost:8181.
В этом примере, все функции подписчики просто выводят информацию в текстовое поле, и серверу мы ничего не отсылаем. После соединения это сделать достаточно просто, а в данном примере это лишь немного усложнит все.

Итак сервер. Поскольку ASP.NET приложение/веб-сайт работает только при обработке запросов, а нам необходимо постоянно ждать соединений, то мы должны запустить отдельный поток(thread). Сделаем это в Global.asax. Потоком и сервером будет заниматься наш специальный классик.
    void Application_Start(object sender, EventArgs e)
    {
        WebSocks.WebSockServer.Start();
    }

    void Application_End(object sender, EventArgs e)
    {
        WebSocks.WebSockServer.End();
    }


* This source code was highlighted with Source Code Highlighter.


В функции Start мы просто стартуем Thread, который будет ждать соединения, а в End подчищаем за собой. Все это можно будет посмотреть более подробно в исходниках, которые я дам в конце статьи.

Итак, далее код самой главной функции, но сначала я вкратце расскажу, что будет происходить, что бы было легче ориентироватся.
  • Присоединятся новый клиент (пока что мы обслуживем по одному)
  • Мы принимает клиентский Handshake
  • Отсылаем наш Handshake. После этого, соединение можно считать установленным.
  • Отсылаем строчку Hello World!, куда же без нее :)
Вот сам код.
  public static void Listen()<br>    {<br>      //Начинаем слушать<br>      Listener = new TcpListener(IPAddress.Loopback, PortNumber);<br>      Listener.Start();<br><br>      while (true)<br>      {<br>        //Присоеденился клиент<br>        using (Socket client = Listener.AcceptSocket())       <br>        using (NetworkStream stream = new NetworkStream(client))  <br>        using (StreamReader reader = new StreamReader(stream))   <br>        using (StreamWriter writer = new StreamWriter(stream))   <br>        {<br><br>          string clientHandshake = String.Empty; <br>          string currentRead = null;       <br>          string clientOrigin = "";        <br><br>          while (currentRead != "")<br>          {<br>            //Построчно читаем handshake от клиента <br>            currentRead = reader.ReadLine();<br>            clientHandshake += currentRead + Environment.NewLine;<br><br>            //Проверяем дошли ли мы до строчки с источником<br>            if (currentRead.StartsWith("Origin"))<br>            {<br>              //Считываем источник соеденения <br>              int valueStartIndex = currentRead.IndexOf(':') + 1;<br>              clientOrigin = currentRead.Substring(valueStartIndex, currentRead.Length - valueStartIndex);<br>            }<br>          }<br><br>          //Отвечаем клиенту в рамках процедуры установления соединения (Handshake)<br>          writer.WriteLine("HTTP/1.1 101 Web Socket Protocol Handshake");<br>          writer.WriteLine("Upgrade: WebSocket");<br>          writer.WriteLine("Connection: Upgrade");<br><br>          //Вот тут нам и нужен Origin который мы взяли ранее. <br>          //Мы не "хардкодим значение", потому что, если запускать <br>          //из под студии, то порт может быть разным.<br>          writer.WriteLine(String.Format("WebSocket-Origin: {0}", clientOrigin));<br>          writer.WriteLine("WebSocket-Location: ws://localhost:8181/websock");<br>          writer.WriteLine("");<br>          writer.Flush();<br><br>          //Даем клиенту/браузеру время сообразить.<br>          Thread.Sleep(100);<br>          <br>          //Специальные байты которые начинают и заканчивают сообщения<br>          byte[] first = new byte[] { 0x00 };<br>          byte[] last = new byte[] { 0xFF };<br>          <br>          //Отсылаем первый байт (начинаем сообщение)          <br>          client.Send(first);<br><br>          //Ну и куда же без Hello World<br>          client.Send(Encoding.UTF8.GetBytes("Hello world!"));<br><br>          //Отсылаем последний байт (заканчиваем сообщение)<br>          client.Send(last);<br>        }<br>      }<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


А вот и обещанные исходники.
dl.dropbox.com/u/3945288/WebSocketsArticle.zip

Это простейший пример, в нем есть недостатки, например отсутствие многопоточности при обработке клиентов. А так же, его еще есть куда дорабатывать, например обеспечить доступ к сессии пользователя.

Буду рад если статья понравится и кому-нибудь поможет, постараюсь развить тему и написать еще.

UPD: В комментариях заметили, что реализация для предыдущей редакции 75, текущая 76, в которой добавили безопасность. Я постараюсь сделать версию, которая будет работать с обоими редакциями, но видимо, уже не сегодня.
Для тех кто просил расшарить наработки, я залил все сюда. По идее все должно работать но не работает :), где то косяк но мозг его уже не замечает.


UPD2: Как и обещал, вот версия которая поддерживает обе версии 75 и 76.
Tags:
Hubs:
+27
Comments 31
Comments Comments 31

Articles