Pull to refresh

WebSockets в Windows 8 Consumer Preview

Reading time8 min
Views4.7K
Original author: Brian Raymor
В Windows 8 CP и Server Beta все клиенты и сервера Microsoft WebSocket, включая IE10, сейчас поддерживают финальную версию протокола IETF WebSocket. Кроме того, IE10 реализует предварительную рекомендацию W3C WebSockets API.

WebSockets стабильны и готовы к тому, чтобы разработчики начали создавать инновационные приложения и сервисы. Эта статья представляет собой простое введение в W3C WebSockets API и ниже расположенный протокол WebSocket. Обновленная демонстрация Flipbook использует последние версии API и протокола.

В моей предыдущей статье я описал сценарии использования WebSockets:
WebSockets позволяют Web-приложениям выполнять доставку нотификаций и обновлений в реальном времени, прямо в браузер. Разработчики столкнулись с проблемами, связанными с необходимостью обходить ограничения персоначальной модели взаимодействия HTTP вида запрос-ответ, чей дизайн не предназначен для сценариев реального времени. WebSockets позволяют браузерам открывать двухсторонний, полнодуплексный канал коммуникаций с сервисами на стороне сервера. Каждая из сторон может использовать этот канал для немедленной доставки данных другой стороне. Сегодня сайты, начиная с социальных сетей и игр, заканчивая финансовыми сайтами, могут предоставить лучшие сценарии реального времени, чем ранее, в идеале используя одну и ту же разметку в различных браузерах.

Со времени публикации той статьи в сентябре 2011 года рабочие группы достигли важного прогресса. Протокол WebSocket теперь стал стандартным протоколом, предложенным IETF. К тому же W3C WebSockets API теперь является кандидатом-рекомендацией W3C.

Введение в WebSocket API на примере Echo


Фрагменты кода, приведённые ниже, используют простой эхо-сервер, созданный с использованием пространства имён System.Web.WebSockets из ASP.NET, чтобы возвращать обратно текстовые и бинарные сообщения, которые были посланы из приложения. Приложение позволяет пользователю либо вводить текст, который требуется отправить, и выводит полученный ответ как текстовое сообщение, либо нарисовать изображение, которое может быть отправлено на сервер, и получено обратно как бинарное сообщение.
image
Более сложный пример, позволяющий экспериментировать с разницей в задержках и производительности между WebSockets и HTTP polling, можно увидеть в демонстрации Flipbook.

Подробности соединения с сервером WebSocket.


Это простое объяснение предполагает прямое соединение между приложением и сервером. Если сконфигурировано соединение через прокси, IE10 начинает процесс соединения через посылку HTTP-запроса CONNECT к прокси.

Когда создаётся объект WebSocket, между клиентом и сервером выполняется рукопожатие, формирующее соединение WebSocket.
image

IE10 начинает процесс рукопожатия с посылки HTTP-запроса на сервер:
GET /echo HTTP/1.1
Host: example.microsoft.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://microsoft.com
Sec-WebSocket-Version: 13

Давайте посмотрим на каждую часть этого запроса.

Процесс соединения начинается со стандартного запроса HTTP GET, что позволяет запросу проходить через сетевые экраны, прокси и другие промежуточные пункты:
GET /echo HTTP/1.1
Host: example.microsoft.com

HTTP-заголовок Upgrade является запросом к серверу на переключение протокола уровня приложения с HTTP на WebSocket.
Upgrade: websocket
Connection: Upgrade

Сервер трансформирует значение заголовка Sec-WebSocket-Key при ответе, чтобы продемонстрировать, что он понимает протокол WebSocket:
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Заголовок Origin формируется IE10, чтобы позволить серверу применить политику безопасности, основанную на источнике.
Origin: http://microsoft.com

Заголовок Sec-WebSocket-Version идентифицирует запрашиваемую версию протокола. Версия 13 является финальной в стандарте, предложенным IETF:
Sec-WebSocket-Version: 13

Если сервер принимает запрос на обновление протокола уровня приложения, он возвращает ответ HTTP 101 Switching Protocols:
image
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

Для демонстрации того, что он действительно понимает протокол WebSocket, сервер выполняет стандартизованное преобразование значения заголовка Sec-WebSocket-Key из клиентского запроса, и возвращает результат в заголовке Sec-WebSocket-Accept:
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

IE10 затем соотносит значение Sec-WebSocket-Key со значением Sec-WebSocket-Accept, чтобы гарантировать, что сервер действительно является сервером WebSocket, а не чем-то иным.

Клиентское рукопожатие формирует соединение HTTP поверх TCP между IE10 и сервером. После того, как сервер возвращает ответ 101, протокол уровня приложения переключается с HTTP на WebSockets, который использует ранее установленное соединение TCP.

HTTP полностью исчезает в картине взаимодействия после данного переключения. Сообщения теперь могут быть посланы или приняты обеими сторонами соединения в любое время, с использованием легковесного протокола WebSocket.
image

Програмирование подключения к серверу WebSocket


Протокол WebSocket определяет две новые схемы URI, которые очень похожи на схемы HTTP.

  • «ws:» "//" host [ ":" port ] path [ "?" query ] похожа на схему “http:”. Её порт по умолчанию, — 80. Она используется для незащищённых (незашифрованных) соединений;
  • «wss:» "//" host [ ":" port ] path [ "?" query ] похожа на схему “https:”. Её порт по умолчанию, — 443. Она используется для защищённых соединений, пробрасываемых через слой TLS.


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

Следующий фрагмент кода устанавливает соединение WebSocket:
var host = "ws://example.microsoft.com";
var socket = new WebSocket(host);


ReadyState – Готовность… Установка… Поехали…

Атрибут WebSocket.readyState предоставляет состояние соединения: CONNECTING, OPEN, CLOSING, или CLOSED. Когда WebSocket только создан, readyState устанавливается в CONNECTING. Когда соединение установлено успешно, readyState устанавливается в OPEN. Если соединение не удалось установить, readyState устанавливается в CLOSED.

Подписка на события открытия соединения

Чтобы получать уведомления о том, что соединение было создано, приложение должно подписаться на события open.
socket.onopen = function (openEvent) {
  document.getElementById("serverStatus").innerHTML = 'Web Socket State::' + 'OPEN';
};


Подробности за сценой: отсылка и приём сообщений


После успешного рукопожатия приложение и сервер могут обмениваться сообщениями WebSocket. Сообщение формируется как последовательность из одного или нескольких фрагментов сообщения (иначе говоря, «фреймов» данных).

Каждый фрейм включает в себя такую инфорацию, как:
  • Размер фрейма;
  • Тип сообщения (бинарное или текстовое), только в первом фрейме сообщения;
  • Флаг (FIN), индицирующий, что это последний фрейм сообщения.

image
IE10 объединяет фреймы в целое сообщение перед тем, как передать его скрипту.

Программирование посылки и отправки сообщений


send API позволяет посылать сообщения к серверу как текст в кодировке UTF-8, равно как и ArrayBuffers или Blobs.

Например, этот кусок кода берёт текст, введённый пользователем, и посылает его на сервер как текстовое сообщение (UTF-8), которое затем возвращается обратно. Этот код проверяет, что Websocket.readyState находится в состоянии OPEN:
function sendTextMessage() {
  if (socket.readyState !== WebSocket.OPEN) {
    return;
  }
  var e = document.getElementById("textmessage");
  socket.send(e.value);
}

Этот код получает изображение, отрисованное пользователем на холсте, и отправляет его на сервер, как бинарное сообщение:
function sendBinaryMessage() {
  if (socket.readyState != WebSocket.OPEN) {
    return;
  }
  var sourceCanvas = document.getElementById('source');
  // msToBlob returns a blob object from a canvas image or drawing
  socket.send(sourceCanvas.msToBlob());
  // ...
}


Подписка на события сообщений


Чтобы получать сообщения, приложение должно подписаться на события сообщений. Обработчик события получает MessageEvent, который содержит данные в MessageEvent.data. Данные могут быть получены как текст или как бинарные сообщения.

Когда получено бинарное сообщение, атрибут WebSocket.binaryType управляет тем, в каком виде возвращаются данные, — в виде Blob или в виде ArrayBuffer. Атрибут может быть установлен либо в «blob», либо в «arraybuffer».

Примеры ниже используют значение по умолчанию, равное «blob».

Этот кусок кода принимает изображение или текст с Websocket-сервера. Если данные бинарные, он получает изображение и отрисовывает его на целевом холсте. Иначе он получает текст в кодировке UTF-8 и показывает его в текстовом поле.
socket.onmessage = function (messageEvent) {
  if (messageEvent.data instanceof Blob) {
    var destinationCanvas = document.getElementById('destination');
    var destinationContext = destinationCanvas.getContext('2d');
    var image = new Image();
    image.onload = function () {
      destinationContext.clearRect(0, 0, destinationCanvas.width, destinationCanvas.height);
      destinationContext.drawImage(image, 0, 0);
    }
    image.src = URL.createObjectURL(messageEvent.data);
  } else {
    document.getElementById("textresponse").value = messageEvent.data;
  }
};


Подробнее о закрытии WebSocket-соединения


Подобно приветственному рукопожатию, есть также и прощальное рукопожатие. Любая из сторон (приложение или сервер) может инициировать этот процесс.

Специальный тип фрейма (закрывающий фрейм) посылается другой стороне. Закрывающий фрейм может содержать опциональный код статуса и причину закрытия. Протокол определяет несколько соответствующих значений для кода статуса. Отправитель закрывающего фрейма обязан более не посылать никаких данных после закрывающего фрейма.

Когда другая сторона получает закрывающий фрейм, она отвечает своим собственным закрывающим фреймом. Она может оправить предварительно несколько сообщений до закрывающего фрейма.
image

Программирование закрытия WebSocket и подписка на события закрытия


Приложение инициирует прощальное рукопожатие на открытом соединение с помощью close API:
socket.close(1000, "normal close");


Для получения уведомлений о закрытии соединения приложение должно подписаться на события закрытия соединений.
socket.onclose = function (closeEvent) {
  document.getElementById("serverStatus").innerHTML = 'Web Socket State::' + 'CLOSED';
};


close API принимает два опциональных параметра: код статуса из списка определённых в протоколе, и описание. Код статуса должен быть либо равен 1000, либо находиться в диапазоне от 3000 до 4999. Когда выполняется закрытие соединения, атрибут readyState устанавливается в CLOSING. После того, как IE10 получает ответ с закрывающим фреймом от сервера, атрибут readyState устанавливается в CLOSED и генерируется событие close.

Использование Fiddler для просмотра трафика WebSockets


Fiddler — популярный HTTP-прокси для отладки. В последних его версия появилась некоторая поддержка протокола WebSocket. Вы можете изучать обмен заголовками в рукопожатии WebSocket:
image

Все сообщения WebSocket также логгируются. Ниже на скриншоте вы можете наблюдать, как приложение «spiral» отсылает текстовое сообщение на сервер, который отправляет его обратно:
image

Заключение


Если вы хотите больше узнать об WebSockets, Вы можете просмотреть эти сессии с конференции Microsoft //Build/, проходвшей в сентябре 2011 г.:

Если вам незнакомы технологии Microsoft для создания сервисов WebSocket, эти статьи могут стать хорошим руководством:


Я надеюсь, что вы начнете разработку на базе WebSockets уже сегодня, и поделитесь с нами своими впечатлениями.

—Brian Raymor, Senior Program Manager, Windows Networking
Tags:
Hubs:
Total votes 18: ↑11 and ↓7+4
Comments8

Articles