Как стать автором
Обновить

Простой WebSocket сервер на C#

WebSocket — протокол двунаправленной связи между веб-браузером и сервером в режиме реального времени.
Написать некоторое подобие WebSocket сервера не очень сложно.
Конечно, существует некоторое число реализаций, в том числе и на C#, но, обычно, самостоятельное создание велосипедов тоже доставляет некоторое удовольствие.

Краткое описание протокола


Протокол состоит из двух частей — установления соединения (hanshake) и передачи данных.

Handshake


Клиент посылает на сервер запрос следующего вида:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
Sec-WebSocket-Protocol: sample
Upgrade: WebSocket
Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
Origin: http://example.com

^n:ds[4U

А сервер отвечает так:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Location: ws://example.com/demo
Sec-WebSocket-Protocol: sample

8jKS'y:G*Co,Wxa-

Формат первой строки запроса и ответа совпадает с форматами строки запроса (Request-Line) и строки статуса (Status-Line) из спецификации HTTP.

После этих строк передается набор полей (fields) — строк, состоящих из двух подстрок, не содержащих CR (Carriage Return, U+000D) и LF (Line Feed, U+000A), разделенных двоеточием (U+003A) (первая подстрока не содержит двоеточие), оканчивающихся CRLF.

После последнего поля следует 10 байт, начинающихся с CRLF (часть данных для проверки ответа (challenge)).

В handshake-ответе сервера после полей следует 18 байт начинающихся с CRLF (16 байт — ответ на запрос проверки).
Эти 16 байт получаются следующим образом:
  • Для полей Sec-WebSocket-Key1 и Sec-WebSocket-Key2 цифры, содержащиеся в значении записываются одна за другой и это число делится на число пробелов в строке.
  • Далее вычисляется MD5 хеш от конкатенации этих двух чисел и последних 8 байт от запроса.
    Хеш — результат аутентификации.

Когда сервер и клиент послали handshake запросы, и проверка пройдена, то начинается этап обмена данными.

Обмен данными


Для передачи строк достаточно передать 0x00 + строку + 0xFF

Реализация


  • Для обмена данными будут использоваться классы TcpListener и TcpClient
  • Каждый пользователь будет обрабатываться в отдельном потоке
  • После подключения пользователь сначала должен указать имя
  • Сообщение отправленное одним пользователем, отсылается всем другим

Сервер


User


User — класс, описывающий пользователя чата. После handshake все управление соединением передается ему.
Имеет метод SendMessage для отправки сообщения пользователю, и метод Stop для завершения действий.
Чтение выполняется в отдельном потоке recieveThread.
Чтение и запись — просто поиск\запись в поток конкатенации 0x00, сообщения, 0xff.

Сервер


Server — класс, обрабатывающий входящие соединения (handshake-запросы) и взаимодействие с пользователями.
Обработка handshake запроса производится следующим образом:

Обработка ключа


Обработка ключа в соответствии с протоколом:
static byte[] BuildKey(string data)
{
    int spaceCount = 0;
    string partialKey = "";
    char[] keyChars = data.ToCharArray();
    foreach (char c in keyChars)
    {
        if (char.IsDigit(c))
            partialKey += c;
        if (char.IsWhiteSpace(c))
            spaceCount++;
    }
    byte[] answ = BitConverter.GetBytes((int)(Int64.Parse(partialKey) / spaceCount));
    if (BitConverter.IsLittleEndian)
        Array.Reverse(answ);
    return answ;
}

handshake — ответ


Сначала обрабатываются поля запроса и с помощью функции BuildKey получаются первые две части ключа — key1 и key2; а также — оставшиеся 8 байт копируются из буфера data в массив lastKey и объединяются:
byte[] all = new byte[16];
Array.Copy(key1, all, 4);
Array.Copy(key2, 0, all, 4, 4);
Array.Copy(lastKey, 0, all, 8, 8);

Теперь — осталось только сосчитать MD5 хеш:
byte[] handshakeAnswer = MD5.Create().ComputeHash(all);

Архив со всеми исходниками.

Вывод


Написать простой WebSocket-сервер несложно, однако использованию WebSocket в реальных проектах препятствует поддержка различных версий различными браузерами (и неподдержка большинством браузеров (пример из топика работает в Google Chrome последних версий).
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.