WebSocket — протокол двунаправленной связи между веб-браузером и сервером в режиме реального времени.
Написать некоторое подобие WebSocket сервера не очень сложно.
Конечно, существует некоторое число реализаций, в том числе и на C#, но, обычно, самостоятельное создание велосипедов тоже доставляет некоторое удовольствие.
Протокол состоит из двух частей — установления соединения (hanshake) и передачи данных.
Клиент посылает на сервер запрос следующего вида:
А сервер отвечает так:
Формат первой строки запроса и ответа совпадает с форматами строки запроса (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 байт получаются следующим образом:
Когда сервер и клиент послали handshake запросы, и проверка пройдена, то начинается этап обмена данными.
Для передачи строк достаточно передать 0x00 + строку + 0xFF
User — класс, описывающий пользователя чата. После handshake все управление соединением передается ему.
Имеет метод SendMessage для отправки сообщения пользователю, и метод Stop для завершения действий.
Чтение выполняется в отдельном потоке recieveThread.
Чтение и запись — просто поиск\запись в поток конкатенации 0x00, сообщения, 0xff.
Server — класс, обрабатывающий входящие соединения (handshake-запросы) и взаимодействие с пользователями.
Обработка handshake запроса производится следующим образом:
Обработка ключа в соответствии с протоколом:
Сначала обрабатываются поля запроса и с помощью функции BuildKey получаются первые две части ключа — key1 и key2; а также — оставшиеся 8 байт копируются из буфера data в массив lastKey и объединяются:
Теперь — осталось только сосчитать MD5 хеш:
Архив со всеми исходниками.
Написать простой WebSocket-сервер несложно, однако использованию WebSocket в реальных проектах препятствует поддержка различных версий различными браузерами (и неподдержка большинством браузеров (пример из топика работает в Google Chrome последних версий).
Написать некоторое подобие 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 последних версий).