Опубликован: 15.05.2013 | Доступ: свободный | Студентов: 265 / 9 | Длительность: 24:25:00
Специальности: Системный архитектор
Лекция 9:

Синдикация

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >

Использование AtomPub

Обратная сторона чтения RSS-каналов, как мы уже видели, это необходимость в управлении записями в канале: в добавлении, удалении и правке записей. Это можно использовать в приложении, которое позволяет пользователю поддерживать блог, а не только читать записи из других ресурсов.

API для этого можно найти в Windows.Web.AtomPub (http://msdn.microsoft.com/library/windows/apps/windows.web.atompub.aspx), это показано в примере "AtomPub" (http://code.msdn.microsoft.com/windowsapps/AtomPub-sample-c1fcdc8e). Главный класс здесь – это AtomPubClient (http://msdn.microsoft.com/library/windows/apps/br243412.aspx), который инкапсулирует методы, необходимые для реализации протокола AtomPub. У него есть методы наподобие createResourceAsync, retrieveResourceAsync, updateResourceAsync, и deleteResourceAsync для работы с записями, где каждый ресурс идентифицируется с помощью URI и объекта SyndicationItem, при необходимости. Мультимедиа-ресурсами для записей управляют с помощью createMediaResourceAsync и похожим образом названных методов,где ресурс представляется в виде объекта IInputStream.

У объекта AtomPubClient так же имеются методы retrieveFeedAsync и setRequestHeader, которые выполняют те же функции, что и методы SyndicationClient с такими же именами, а так же есть несколько похожих свойств, наподобие serverCredential, timeout, и bypassCacheOnRetrieve. Еще один метод, retrieveServiceDocumentAsync, предоставляет документацию рабочего пространства/сервиса для канала (в форме объекта Windows.Web.AtomPub.ServiceDocument).

Снова, пример "AtomPub" ( http://code.msdn.microsoft.com/windowsapps/AtomPub-sample-c1fcdc8e). показывает различные операции: получение данных (Сценарий 1), создание (Сценарий 2), удаление (Сценарий 3) и обновление (Сценарий 4). Вот как, для начала, создаётся объект AtomPubClient (смотрите js/common.js), наличие учетных данных предполагается:

  function createClient() {
  client = new Windows.Web.AtomPub.AtomPubClient();
  client.bypassCacheOnRetrieve = true;
  var credential = new Windows.Security.Credentials.PasswordCredential();
  credential.userName = document.getElementById("userNameField").value;
  credential.password = document.getElementById("passwordField").value;
  client.serverCredential = credential;
  }
  Обновление записи (js/update.js) выглядит так, где обновление представлено вновь созданным объектом SyndicationItem:
  function getCurrentItem() {
  if (currentFeed) {
  return currentFeed.items[currentItemIndex];
  }
  return null;
  }
  var resourceUri = new Windows.Foundation.Uri( /* service address */ );
  createClient();

  var currentItem = getCurrentItem();

  if (!currentItem) {
  return;
  }

  // Обновление элемента
  var updatedItem = new Windows.Web.Syndication.SyndicationItem();
  var title = document.getElementById("titleField").value;
  updatedItem.title = new Windows.Web.Syndication.SyndicationText(title,
  Windows.Web.Syndication.SyndicationTextType.text);
  var content = document.getElementById("bodyField").value;
  updatedItem.content = new Windows.Web.Syndication.SyndicationContent(content,
  Windows.Web.Syndication.SyndicationTextType.html);

  client.updateResourceAsync(currentItem.editUri, updatedItem).done(function () {
  displayStatus("Updating item completed.");
  }, onError);

Обработка ошибок в данном случае работает с классом Window.Web.WebError (http://msdn.microsoft.com/library/windows/apps/windows.web.weberror.aspx) (смотрите js/common.js):

function onError(err) {
  displayError(err);

  // Сравнивает номер ошибки со значением WebErrorStatus, для того, чтобы иметь дело с конкретной ошибкой.
  var errorStatus = Windows.Web.WebError.getStatus(err.number);
  if (errorStatus === Windows.Web.WebErrorStatus.unauthorized) {
  displayLog("Wrong username or password!");
  }
  }

Сокеты

Сокеты – это основа сетевого транспорта. В отличие от HTTP-запросов, где клиент отправляет запрос серверу, а сервер отвечает ему, то есть – от отдельного сеанса взаимодействия, сокеты – это соединение между клиентским и серверным IP-портами, так что каждый может в любое время отправлять другому данные по этому каналу. Очевидно, мы видели подобный механизм раньше, а именно, когда использовали Windows Push Notification Service (WNS). WNS, однако, ограничен уведомления и специально создан для отправки обновлений плиток или уведомлений для приложений, которые не исполняются. Сокеты, с другой стороны, созданы для обмена данными между сервером и работающим клиентом.

Сокеты обычно используют, когда нет API более высокого уровня или других абстрактных механизмов, подходящих для конкретного сценария, когда используются пользовательские протоколы, когда нужна возможность двусторонней связи, или когда важно снизить нагрузку на сеть при каждом обмене данными. Если рассмотреть HTTP, это протокол, который построен на низкоуровневых сокетах. Отдельный HTTP-запрос обычно включает в себя заголовки и множество других данных, помимо небольшого объема полезного содержимого, поэтому это – неэффективный сетевой транспорт, если нужно передать множество небольших фрагментов информации. Лучше – подключиться напрямую к серверу и передавать данные с минимальным количеством служебной информации. VoIP – другой пример, где хорошо работают сокеты, как и широковещательные сценарии наподобие многопользовательских игр. В последних один из компьютеров игроков выполняет роль сервера в локальной подсети, он может передавать сообщения всем остальным игрокам, и наоборот, опять же, с минимальной избыточностью.

В мире сокетов обмен данными может происходить двумя путями: в виде отдельных пакетов или сообщений (напоминающих ведра с водой), или в виде непрерывного потока (как вода, текущая по трубе). Их называют сокетами датаграмм и сокетами потоков, соответственно, и то и другое поддерживается посредством API WinRT. WinRT так же поддерживает обе формы обмена данными посредством проткола WebSocket, технологии, изначально созданной для веб-браузеров и веб-серверов, которая стала весьма интересной для применения в приложениях, для общих целей. Все применимые классы находятся в API Windows.Networking.Sockets (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.aspx), как мы увидим в следующих разделов. Обратите на это внимание, так как имеется некоторое наложение между разными типами сокетов, эти разделы нужно читать по порядку, так как я не собираюсь повторяться!

Сокеты датаграмм

На языке сокетов ведро с водой называется датаграммой. Это пакет данных, отправленных с одного конца сокета на другой – даже без наличия соответствующего соединения. В соответствии со стандартом User Datagram Protocol (UDP). UDP, как я обобщаю здесь из его описания в Википедии (http://ru.wikipedia.org/wiki/UDP), это простой, не предусматривающий сохранение состояния, двунаправленный протокол, ориентированный на отдельные транзакции. Он подразумевает минимальную избыточность и отсутствие задержек повторной передачи данных, и поэтому он не может гарантировать, что датаграмма будет доставлена. Таким образом он используется тогда, когда в проверке ошибок и коррекции нет необходимости, или это выполняется самим приложением, а не на уровне сетевого интерфейса. Например, сценарий использования VoIP позволяет просто терять пакеты, если они не могут быть доставлены, вместо того, чтобы заставлять все остальные механизмы ждать задержавшегося пакета. В результате качество звука может страдать, но говорящие не начнут заикаться или разговаривать как пришельцы из другой галактики. Коротко говоря, UDP может быть ненадежным, но он минимизирует задержки передачи данных. Протоколы более высокого уровня, наподобие Real-time Transport Protocol (RTP) и Real Time Streaming Protocol (RTSP) построены на базе UDP.

Приложения для Магазина Windows работают с этим средством передачи данных, либо в роли клиента, либо как сервер, используя класс Windows.Networking.Sockets.DatagramSocket ( http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocket.aspx), объект, который нужно инициализировать с использованием оператора new для настройки соединения и ожидания поступления сообщений:

var listener = new Windows.Networking.Sockets.DatagramSocket();

С каждой стороны соединения, следующий шаг – это прослушивание события этого объекта messagereceived ( http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocket.messagereceived.aspx):

     // Событие из WinRT: помните о вызове removeEventListener при необходимости
      listener.addEventListener("messagereceived", onMessageReceived);

Когда данные прибывают, обработчик получает то, чего ждет – объект DatagramSocketMessageReceivedEventArgs ( http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketmessagereceivedeventargs.aspx) (и не выговоришь). Он содержит свойства localAddress и remoteAddress и то и другое - объекты Windows.Networking.HostName (http://msdn.microsoft.com/library/windows/apps/windows.networking.hostname.aspx), которые содержат IP-адрес, отображаемое имя и некоторые другие данные. Смотрите раздел "Информация о сети (Реестр сетевых объектов)" выше в этой лекции чтобы узнать подробности. Аргументы события, так же, содержат строку remotePort. Гораздо важнее, однако, это два метода, посредством которых можно извлечь данные. Один из них, getDataStream (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketmessagereceivedeventargs.getdatastream.aspx), который возвращает IInputStream с помощью которого можно прочесть последовательно расположенные байты данных.

Другой – getDataReader (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketmessagereceivedeventargs.getdatareader.aspx), возвращает объект Windows.Storage.Streams.DataReader (http://msdn.microsoft.com/library/windows/apps/windows.storage.streams.datareader.aspx), высокоуровневую абстракцию, построенную на основе IInputStream, которая помогает считывать конкретные типы данных непосредственно. Очевидно, если вам известна структура данных, которую вы ожидаете получить в сообщении, использование DataReader освободит вас от необходимости самостоятельно выполнять преобразование типов данных.

Конечно, для того, чтобы получить из сокета какие-то данные, вам нужно к чему-то его подключить. Для этой цели в DatagramSocket существует несколько методов для установления соединения и управления им:

  • connectAsync Запускает операцию соединения с использованием заданного объекта HostName и имени службы (или строкового представления UDP-порта) удаленного сетевого пункта назначения. Этот метод используется для создания одностороннего соединения клиента с сервером.
  • Другая форма connectAsync принимает объект Windows.Networking.EndpointPair (http://msdn.microsoft.com/library/windows/apps/windows.networking.endpointpair.aspx), который задаёт имя узла и службы и для локальной и для удалённой конечных точек сетевого подключения. Он используется для создания двустороннего соединения клиента и сервера, в то время как локальная конечная точка означает вызов bindEndpointAsync, как показано ниже.
  • bindEndpointAsync Используется для одностороннего серверного соединения, то есть – только для прослушивания, но не для отправки данных в сокет – этот метод просто привязывает локальную конечную точку, заданную в HostName и имени службы или порта. Привязка службы может быть реализована с помощью bindServiceNameAsync.
  • joinMulticastGroup Принимает HostName, присоединяет объект сокета датаграмм к группе многоадресной рассылки.
  • close Прекращает соединение и прерывает все ожидающие операции.

Совет. Для того, чтобы открыть сокет на порту локального хоста для целей отладки, воспользуйтесь connectAsync следующим образом:

  var socket = new Windows.Networking.Sockets.DatagramSocket();
  socket.connectAsync(new Windows.Networking.Sockets.DatagramSocket("localhost",
  "12345", Windows.Networking.Sockets.SocketProtectionLevel.plainSocket)
  .done(function () {
  // ...
  }, onError);

Обратите внимание на то, что так как каждый сокет может быть подключен к любому количеству конечных точек, вы можете вызывать метод connectAsync много раз, присоединяться к множеству групп многоадресной рассылки и привязывать множество локальных конечных точек с помощью bindEndpointAsync и bindServiceNameAsync. Метод close, учтите, закрывает сразу всё!

Как только у сокета есть одно или большее количество подключений, информацию о подключении можно получить с помощью свойства DatagramSocket.information (типа DatagramSocketInformation (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketinformation.aspx)). Кроме того, обратите внимание на то, что статический метод DatagramSocket.getEndpointPairsAsync ( http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocket.getendpointpairsasync.aspx) (в виде асинхронного результата) вектор доступных объектов EndpointPair для заданных удалённого имени узла и сервиса. Так же вы можете дополнительно указать, что вам нужна информация о конечных точках, отсортированная по флагу optimizeForLongConnections (http://msdn.microsoft.com/library/windows/apps/windows.networking.hostnamesortoptions.aspx). Смотрите документацию по приведенным ссылкам, но обычно это позволяет вам управлять выбором предпочтительных конечных точек на основе того, нужна ли вам оптимизация для высококачественного и длительного соединения, что может занять больше времени на установление соединения (как для потоковой передачи видео), или для соединения, которое устанавливается быстрее (по умолчанию).

Управляющие данные могут быть заданы с помощью свойства DatagramSocket.control, которое является объектом

DatagramSocketControl (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketcontrol.aspx) со свойствами qualityOfService (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketcontrol.qualityofservice.aspx) и outputUnicastHopLimit ( http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocketcontrol.outboundunicasthoplimit.aspx).

Всё это, конечно, лишь предварительная подготовка к передаче данных по соединению сокета. Это выполняется с помощью свойства DatagramSocket.outputStream (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.datagramsocket.outputstream.aspx) типа IOutputStream (http://msdn.microsoft.com/library/windows/apps/windows.storage.streams.ioutputstream.aspx), куда вы записываете любые необходимые даные с использованием его методов writeAsync и flushAsync. Благодаря этому данные можно отправить по любому из соединений сокета. Так же можно использовать один из вариантов getOutputStreamAsync для задания конкретных параметров EndpointPair или HostName/порта на которые нужно отправить данные. Результат каждой из этих асинхронных операций – это, снова, IOutputStream. И во всех случаях вы можете создавать высокоуровневый объект DataWriter на основе данного потока:

    var dataWriter = new Windows.Storage.Streams.DataWriter(socket.outputStream)

Вот, как это всё показано в примере "DatagramSocket" ( http://code.msdn.microsoft.com/windowsapps/DatagramSocket-sample-76a7d82b), небольшом приложении, в котором вам нужно последовательно запустить каждый из сценариев. Сценарий 1, в первую очередь, устанавливает серверный прослушиватель на локальный хост, используя номер порта 22112 (имя службы) по умолчанию. Для того чтобы это сделать, он создает сокеты, добавляет прослушиватель и вызывает bindServiceNameAsync (js/startListener.js):

socketsSample.listener = new Windows.Networking.Sockets.DatagramSocket();
  // Напоминание: вызовите при необходимости removeEventListener; это может быть обычной процедуре при работе с сокетами,
  // которая сопровождает жизненный цикл приложения.
  socketsSample.listener.addEventListener("messagereceived", onServerMessageReceived);

  socketsSample.listener.bindServiceNameAsync(serviceName).done(function () {
  // ...
  }, onError);

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

  function onServerMessageReceived(eventArgument) {
  // [Код здесь проверяет, есть ли уже у нас выходной поток]

  socketsSample.listener.getOutputStreamAsync(eventArgument.remoteAddress,
  eventArgument.remotePort).done(function (outputStream) {
  // [Сохраняем выходной поток с некоторыми другими данными, опущено]
  socketsSample.listenerOutputStream = outputStream;
  }

  // Это вспомогательная функция
  echoMessage(socketsSample.listenerOutputStream, eventArgument);
  });
  }
  // eventArgument здесь - это DatagramSocketMessageReceivedEventArgs с методом getDataReader method
  function echoMessage(outputStream, eventArgument) {
  // [некоторый код для вывода информации опущен]

  // Получаем поток сообщения из DataReader и отправляем его в выходной поток
  outputStream.writeAsync(eventArgument.getDataReader().detachBuffer()).done(function () {
  // Ничего не делаем – клиент выведет сообщение при получении данных.
  });
  }

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

Сценарий 2 устанавливает прослушиватель на локальный хост, на тот же порт. С этой стороны, мы так же создаем DatagramSocket и настраиваем прослушиватель для messagereceived. Эти сообщения, как то, что записывается в выходной поток на серверной стороне, как мы только что видели – принимаются в обработчике события, код которого приведен ниже (js/connectToListener.js), который использует DataReader для извлечения и отображения сообщения:

  function onMessageReceived(eventArgument) {
  try {
  var messageLength = eventArgument.getDataReader().unconsumedBufferLength;
  var message = eventArgument.getDataReader().readString(messageLength);
   socketsSample.displayStatus("Client: receive message from server \"" + message + "\"");
  } catch (exception) {
  status = Windows.Networking.Sockets.SocketError.getStatus(exception.number);
  // [Отображение подробностей об ошибке]
  }
  }

Обратите внимание на то, что в вышеприведенном коде, когда возникает ошибка в соединении сокета, вы можете передать номер ошибки методу getStatus объекта Windows.Networking.Sockets.SocketError (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.socketerror.getstatus.aspx) и получить в ответ более подходящее для принятия решений значение SocketErrorStatus (http://msdn.microsoft.com/library/windows/apps/windows.networking.sockets.socketerrorstatus.aspx). Существует множество возможных ошибок, поэтому смотрите страницу справки для того, чтобы узнать подробности.

Даже после того, как всё вышеописанное сделано, ничего пока не происходит, так как мы еще не отправили данные! Переход к Сценарию 3 и нажатие его кнопки Send ‘Hello’ Now выполняет действия от имени клиента:

      // [Это происходит после проверки действительности сокета]
      socketsSample.clientDataWriter =
      new Windows.Storage.Streams.DataWriter(socketsSample.clientSocket.outputStream);

      var string = "Hello World";
      socketsSample.clientDataWriter.writeString(string);

      socketsSample.clientDataWriter.storeAsync().done(function () {
      socketsSample.displayStatus("Client sent: " + string + ".");
      }, onError);

Вызов DataWriter.storeAsync это то, что осуществляет запись в поток в сокете. Если вы установите здесь точку останова и в обоих обработчиках события messagereceived, вы увидите, что storeAsync генерирует сообщение для сервера, когда точка останова сработает в onServerMessageReceived в js/startListener.js. Затем будет осуществлена запись обратно в сокет, что вызовет остановку в onMessageReceived в js/connectToListener.js, что приведет к выводу сообщения. (И, для того, чтобы завершить этот процесс, Сценарий 4 предоставляет кнопку для вызова метода сокета close.)

Пример выполняет всё с одним и тем же приложением на локальном хосте, что даёт простую возможность увидеть, как всё это работает. Обычно, конечно, сервер будет исполняться на другом компьютере, но шаги по настройке прослушивателей в подобном случае выглядят так же. Как отмечено в Главе 2, соединения локального хоста работают лишь на компьютере с лицензией разработчика и не будут работать для приложений, полученных из Магазина Windows.

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >