Основы разработки приложений для мобильных устройств на платформе Windows Phone

Средства Windows Phone для работы с сетью

Разбить на страницы
Показывать лекцию целиком

Презентацию к данной лекции Вы можете скачать здесь.

Все устройства Windows Phone могут использовать сетевые подключения к сети оператора сотовой связи и к сетям Wi-Fi. Эти две сетевых технологии являются взаимозаменяемыми. Однако, оба типа сетей не всегда бывают доступными одновременно.

Если при подключении к одной из сетей мобильной связи или Wi-Fi сигнал сети пропадёт, то это подключение прервётся. Приложения должны корректно работать в подобной ситуации — по крайней мере, выводить на экран сообщение вида: "Используемое сетевое подключение временно недоступно. Попробуйте повторить попытку позже". Предпочтительнее, если программа сохранит данные в устройстве, чтобы отправить их, когда подключение к сети будет доступно.

В эмуляторе Windows Phone доступны такие же сетевые возможности, как и в реальном устройстве. Эмулятор использует сетевые подключения компьютера, на котором он запущен. Его также можно использовать для тестирования приложений Windows Phone, использующих сетевые подключения. Однако, имеет смысл протестировать работу программы на физическом устройстве. В телефоне можно отключить сетевые службы и проверить работу приложения, чтобы быть уверенным, что оно будет работать должным образом и с низкоскоростным подключением к сети, если высокоскоростное подключение окажется недоступным.

17.1. Основные сведения о сетях

Перед изучением использования сетевых подключений в приложениях для Windows Phone необходимо ознакомиться с основными сведениями о сетях. Это необходимо для того чтобы лучше понять, как будут работать создаваемые нами программы. Подробное изучение вопросов сетевого взаимодействия устройств не входит в темы данного курса — приводится лишь некоторая базовая информация.

Сетевые сигналы

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

В Windows Phone существует несколько типов сетевых сигналов, которые могут использоваться для подключения к сети: Wi-Fi, сотовая сеть и USB-подключение к программному обеспечению Zune на компьютере. Все эти технологии позволяют передавать данные между программами в телефоне и Интернетом (рис. 17.1).

(рис 17.1) Схема взаимодействия программ телефона с сетевыми подключениями

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

Программа Windows Phone может определить, какие доступны подключения, с помощью методов класса DeviceNetworkInformation.

using Microsoft.Phone.Net.NetworkInformation;

...

System.Text.StringBuilder sb = new System.Text.StringBuilder();

sb.Append("Оператор: " );
sb.AppendLine(DeviceNetworkInformation.CellularMobileOperator);

sb.Append("Сеть доступна:  ");
sb.AppendLine(DeviceNetworkInformation.IsNetworkAvailable.ToString());

sb.Append("Передача данных по сети доступна:  ");
sb.AppendLine(DeviceNetworkInformation.IsCellularDataEnabled.ToString());

sb.Append("Передача данных в роуминге доступна:  ");
sb.AppendLine(DeviceNetworkInformation.IsCellularDataRoamingEnabled.ToString());

sb.Append("Wi-Fi включён:  ");
sb.AppendLine(DeviceNetworkInformation.IsWiFiEnabled.ToString());

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

Сетевые пакеты

Последовательности двоичных сигналов представляют какую-то информацию. Чтобы передавать полезную информацию, необходимо определить формат передаваемого сообщения.

Современная сеть передаёт данные частями по 8 битов, которые называют октетами. В языке C# значение одного октета можно сохранить в переменной типа byte. Октет — это наименьшая единица информации, которую можно передать по сети. Октет может быть, например, числом в диапазоне от 0 до 255, символом или частью большего элемента, например числа с плавающей точкой или файла изображения. В Интернете пакет данных может состоять из нескольких тысяч октетов.

Адресация пакетов

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

Все устройства сети могут получать и организовывать широковещательную передачу данных. Фактически, узлы сети могут принимать все сообщения, переданные по каналу Wi-Fi или проводам. При этом возникает проблема безопасности передачи информации: устройства в сети могут использовать любую информацию, которая предназначена другим устройствам. При подключении к безопасному веб-сайту компьютер кодирует все передаваемые на сайт сообщения так, чтобы другие узлы сети, которые получают эти сообщения, не могли их прочитать зашифрованную в них информацию.

Для формирования адреса узла, которому необходимо передать сообщение, существующая версия протокола IPv4, который используется в компьютерных сетях и в Интернете, использует четыре 8-битовых значения, записанных последовательно. Теоретически такой способ позволяет адресовать более 4 млрд различных систем в Интернете. Однако, на практике эта цифра оказывается существенно меньше, поскольку некоторые адреса зарезервированы для различных целей и их нельзя использовать для адресации устройства. Не так давно вышла в свет новая версия протокола IPv6, в котором каждый адрес занимает 128 битов (16 октетов). Новая версия протокола должна учесть значительное увеличение количества возможных узлов, однако его использование приводит к существенным изменениям в функционировании сети, осуществление которых требует много времени. В настоящее время сетевые возможности Windows Phone не поддерживают протокол IPv6.

Маршрутизация

Не все устройства в Интернете соединены в одну физическую сеть. По сути, Интернет объединяет большое количество отдельных сетей. Что чтобы передать сообщение из одной физической сети в другую, необходимо осуществить маршрутизацию.

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

Пакеты, которые необходимо отправить с домашнего компьютера на удалённые устройства, передаются через сеть Интернет-провайдера (ISP), который передаёт эти пакеты к следующей системе в сети. Обычно пакеты доходят до узла-адресата, минуя несколько устройств. Интернет определяет и периодически изменяет маршруты, по которым следуют пакеты, что позволяет управлять внезапным возрастанием трафика и отказами частей сети, но это может привести к ситуациям, когда один пакет прибывает на узел адресата до того момента, как туда прибывает пакет, отправленный раньше первого. Иногда пакеты могут потеряться (хотя это происходит довольно редко), и таким образом, нельзя быть уверенным, что все пакеты прибыли, пока не придёт подтверждение об этом.

Передача данных в Интернете осуществляется в соответствии с локальным протоколом (протокол управления передачей данных TCP) и межсетевым протоколом (Интернет-протокол IP). Эти протоколы работают совместно и образуют стек протоколов TCP/IP.

Протокол TCP/IP можно использовать для связи устройств без их подключения к Интернету. Например, можно подключить устройство Windows Phone к компьютеру и передавать мультимедиа данные с помощью программного обеспечения Zune.

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

Сетевые подключения и протоколы

Подключения между разными частями сети могут использовать разные типы подключения. Многие устройства поддерживают несколько типов сетевых подключений. Устройство Windows Phone может подключаться к Интернету через сеть оператора сотовой связи или через сеть Wi-Fi. При этом, для программ Windows Phone не имеет значения, каким способом телефон подключён к Интернету.

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

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

Нахождение адресов в Интернете с помощью DNS

Все адреса в Интернете имеют размер 32 бита и обычно записываются в виде значений четырёх октетов, разделённых точкой. Адрес состоит из адреса подсети и адреса узла в этой подсети. Такой адрес называется IP-адресом системы.

Однако, IP-адреса всех используемых узлов запомнить довольно сложно, и к тому же, в них легко запутаться. Поэтому для обращения к узлам в Интернете используют более удобные имена. Для того чтобы по имени узла найти его IP-адрес, существуют специальные серверы, которые преобразуют имена узлов в IP-адреса. Такая система называется системой доменных имён, или DNS. Когда компьютеру нужно узнать IP-адрес веб-сайта, на который хочет зайти пользователь, он направляет запрос к одному из DNS-серверов, который выдаёт необходимый результат.

В сети Интернет работает так много устройств, что при использовании протокола IPv4 всех доступных IP-адресов недостаточно для того, чтобы выделить каждому устройству отдельный IP-адрес. Поэтому большинство устройств находятся в локальных подсетях, каждая из которых подключается к Интернету через маршрутизатор, который имеет прямой выход в Интернет. При этом, локальные адреса устройств позволяют получать доступ только к устройствам в той же подсети и к устройствам, подключённым к Интернету напрямую. Соответственно, два телефона обычно не могут соединяться друг с другом напрямую — для этого необходимо промежуточное устройство для связи телефонов в одну подсеть, например, Wi-Fi-маршрутизатор.

Сетевые порты

Если между двумя компьютерами установлено сетевое подключение, то его могут использовать любые приложения, которым разрешён доступ к сети. Например, сервер может предоставлять подключённым клиентам доступ к различным службам. При этом, клиентам нужен способ определения местонахождения службы, которую они хотят использовать на сервере.

Для этих целей в Интернете используются порты. Порт — это число, которое идентифицирует службу, которую предоставляет компьютер. Некоторые из портов являются общепринятыми, например порт номер 80 обычно используется веб-серверами. Когда браузер клиента запрашивает веб-страницу, он использует Интернет-адрес сервера и использует порт 80, чтобы получить веб-страницу с этого сервера.

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

Программисты могут создавать программы, которые используют любой номер порта, но многие сети используют брандмауэры, которые пропускают пакеты, адресованные определённым номерам портов, а если пакеты приходят на другие порты — они блокируются. Эта мера позволяет повысить безопасность сервера от сетевых атак.

Сеансы подключения и дейтаграммы

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

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

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

В Интернете используются протокол управления подключениями TCP, который позволяет настраивать и управлять подключениями между сетевыми станциями, и протокол пользовательских дейтаграмм UDP, который позволяет передавать дейтаграммы.

Сетевые средства Windows Phone позволяют отправлять дейтаграммы и создавать сеансы подключения. Программисты решают сами, какой тип подключения лучше использовать в конкретной ситуации.

17.2. Создание подключения по протоколу UDP

Рассмотрим процесс создания программы, которая будет использовать протокол UDP для обмена сообщениями с удалённой службой под названием echo. Эта служба принимает входящие сообщения и отправляет их назад. Такая служба полезна для тестирования сетевых подключений. Это одна из самых первых Интернет-служб, и ей выделен порт с номером 7. Службу echo можно запустить на компьютере и использовать её для обмена дейтаграммами.

Создадим метод, который будет использовать протокол UDP для отправки сообщения на удалённый сервер. Пользователь должен будет ввести в текстовые поля программы сообщение для отправки и адрес целевого узла, а программа выполнит все необходимые действия. Метод может выглядеть так:

/// <summary>
/// Отправляет сообщение по указанному адресу, используя протокол UDP
/// </summary>
/// <param name="message">сообщение для отправки</param>
/// <param name="hostUrl">URL узла</param>
/// <param name="portNumber">порт</param>
/// <returns>сообщение об ошибке</returns>
public string SendMessageUDP(string message, string hostUrl, int portNumber)
{  
    // создать сокет и отправить сообщение
    // возвратить сообщение об ошибке, если оправка сообщения завершится неудачей
    // возвратить пустую строку, если сообщение будет передано успешно
}

Этот метод можно использовать так:

const int ECHO_PORT = 7;
string sendResponse = SendMessageUDP(MessageTextBox.Text, HostTextBox.Text,
    ECHO_PORT);

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

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

Класс Socket

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

Несмотря на то, что Windows Phone поддерживает несколько способов подключения к Интернету, программа будет использовать ту же самую технологию сокетов для управления подключением. Класс Socket выбирает наиболее подходящий способ подключения. Если доступная сеть Wi-Fi, то будет использоваться она, а при её отсутствии будет установлено подключение к сети оператора сотовой связи.

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

Класс Socket описан в пространстве имён System.Net.Sockets, ссылку на которое рекомендуется добавить с помощью директивы using. Обычно файл библиотеки для работы с сетью уже является частью проекта, и его не нужно добавлять в проект вручную.

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

Socket hostSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
    ProtocolType.Udp);

Этот код создаёт экземпляр класса Socket, который использует адресацию IPv4 и дейтаграммы протокола UDP.

Класс SocketAsyncEventArgs

Теперь необходимо создать экземпляр класса SocketAsyncEventArgs, который используется для того чтобы сконфигурировать созданный сокет. Этот класс позволяет создать асинхронное подключение.

Для начала нужно задать конечную точку подключения, с которой будет взаимодействовать программа. Конечная точка состоит из адреса узла и номера порта на этом узле. Номер порта фактически идентифицирует программу на компьютере, с которой будет взаимодействовать наша программа. В нашем примере будет производиться передача сообщений в программу echo, которая использует порт 7.

Класс SocketAsyncEventArgs содержит свойство RemoteEndPoint, которое описывает конечную точку подключения:

SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber);

Этот код создаёт новый экземпляр класса SocketAsyncEventArgs и использует класс DnsEndPoint для указания Интернет-адреса узла и номера порта. При создании экземпляра класса DnsEndPoint указывается строка URL целевой системы, и класс будет использовать систему доменных имён, чтобы найти фактический IP-адрес узла, который необходим для создания подключения. Если этот адрес не будет найден, класс сгенерирует исключение.

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

// поместить сообщение в буфер
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);

Чтобы передать любое сообщение, оно должно быть представлено в виде последовательности октетов. При этом, само сообщение может быть текстом, изображением, аудио- или видеофайлом и т.д. Служба echo просто возвращает переданную ей последовательность октетов, независимо от того, какая информация в ней закодирована.

Чтобы преобразовать текст в последовательность 8-битовых значений, можно использовать класс Encoding, который описан в пространстве System.Text. Этот класс использует метод GetBytes для преобразования строки, закодированной в системе UTF-8, в массив байтов. Кодировка UTF-8 используется в системе Windows Phone для кодирования символов.

Создание метода SendMessageUDP

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

Класс SocketAsyncEventArgs содержит свойство Completed, которое генерирует событие при завершении взаимодействия с сетью. При наступлении события можно выполнить код, который проверит результат выполнения сетевого запроса. Этот код необходимо связать со свойством Completed.

public string SendMessageUDP(string message, string hostUrl, int portNumber)
{
    // строка для хранения ответного сообщения
    string response = "";

    // создание аргумента для запроса
    SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
    // создание конечной точки подключения
    socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber,
        AddressFamily.InterNetwork);

    // поместить сообщение в буфер
    byte[] messageBytes = Encoding.UTF8.GetBytes(message);
    socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);

    // подключить обработчик события завершения запроса
    socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
        (delegate(object s, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            response = "";
        }
        else 
        {
            response = e.SocketError.ToString();
        }
        // запустить все ожидающие потоки
        transferDoneFlag.Set();
    });

    ...
}

В этом коде блок программы связывается с событием Completed. В этом случае код обработчика события может получить доступ к локальным переменным в методе SendMessageUDP, в частности, к переменной response, которая будет содержать строку с сообщением об ошибке. Обработчик события Completed вызывается только когда будет завершена сетевая операция.

Событие Completed использует ссылку на объект класса SocketAsyncEventArgs, который описывает состояние транзакции. Он устанавливает значение флага состояния SocketError, которое сообщает, успешно ли было передано сообщение. Если передача сообщения завершилась неудачей, в переменную response записывается сообщение об ошибке.

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

В .NET асинхронные операции выполняются в отдельных потоках. Запускаемая в Windows Phone программа, по сути, является потоком, который становится активным потоком устройства. Программы могут запускать другие потоки для выполнения асинхронных операций, и иногда один поток должен ожидать завершения другого потока. Среда .NET предоставляет возможность взаимодействия потоков с помощью класса ManualResetEvent:

static ManualResetEvent transferDoneFlag = new ManualResetEvent(false);

Программа содержит переменную transferDoneFlag, которая может использоваться в качестве флага. Переменная объявлена как static, чтобы все экземпляры класса использовали один флаг, поскольку можно создать ограниченное количество экземпляров класса ManualResetEvent.

Переменная transferDoneFlag может находиться в одном из двух состояний. Изначально значение флага равно false, т.е. флаг сброшен. Поток может ожидать завершения другого потока, чтобы установить этот флаг, т.е. изменить его значение на true, используя метод WaitOne. Это остановит текущий поток, пока другой поток не установит флаг, вызвав метод Set.

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

Этот подход особенно полезен на платформе Windows Phone, поскольку в мобильных устройствах высока вероятность, что сетевой запрос завершится неудачей. При создании приложения, которое использует сетевые подключения, рекомендуется задавать тайм-аут и обрабатывать ситуацию, когда подключение недоступно. Кроме того, ожидание завершения других потоков позволяет экономить системные ресурсы.

Фактически, в нашей программе будет использоваться главный поток, который создаётся при запуске программы, и фоновый поток, который запускается для выполнения сетевой операции. Если главный поток должен ожидать, пока флаг не будет установлен, перед этим флаг нужно сбросить, вызвав метод Reset, иначе программа не будет ожидать завершения потока, поскольку флаг может быть установлен перед вызовом метода WaitOne.

После выполнения всех необходимых настроек, можно отправить сообщение удалённому компьютеру:

// отправка сообщения
hostSocket.SendToAsync(socketEventArgs);

// ожидание события
// работа будет продолжена после получения ответа или по истечению времени тайм-аута
transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);

Полная версия метода SendMessageUDP будет выглядеть следующим образом:

static ManualResetEvent transferDoneFlag = new ManualResetEvent(false);
const int MESSAGE_TIMEOUT_MSECS = 10000;

public string SendMessageUDP(string message, string hostUrl, int portNumber)
{
    string response = "Истекло время ожидания";

    SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();

    socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber,
        AddressFamily.InterNetwork);

    byte[] messageBytes = Encoding.UTF8.GetBytes(message);
    socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);


    socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
        (delegate(object s, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            response = "";
        }
        else
        {
            response = e.SocketError.ToString();
        }
        transferDoneFlag.Set();
    });

    transferDoneFlag.Reset();

    hostSocket.SendToAsync(socketEventArgs);

    transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);

    return response;
}

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

Отправка и получение сообщения

Созданный метод можно использовать, указав текст передаваемого сообщения, имя узла и номер порта. Метод вернёт ответ, который мы можно проверить в программе:

string sendResponse = SendMessageUDP(MessageTextBox.Text, HostTextBox.Text,
    ECHO_PORT);

Если адрес узла окажется недоступным, то метод завершится неудачей. Однако, это единственный вариант, при котором метод завершится неудачей. Поскольку информация будет передана в виде дейтаграммы, программа не сможет узнать, получила ли удаленная система переданное сообщение, если она не отправит ответное сообщение.

Для того чтобы получить ответ от службы echo после отправки сообщения нужно настроить другое подключение с удалённым узлом. Можно создать другой метод для отправки запроса на чтение:

public string ReceiveMessageUDP(int portNumber, out string result)
{
    string response = "Ошибка: тайм-аут запроса";
    string message ="";

    SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
    socketEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, portNumber);

    byte[] responseBytes = new byte[MAX_BUFFER_SIZE];
    socketEventArgs.SetBuffer(responseBytes, 0, MAX_BUFFER_SIZE);

    socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
        (delegate(object s, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            response = "";
            message = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
            message = message.Trim('\0');
        }
        else
        {
            response = e.SocketError.ToString();
        }

        transferDoneFlag.Set();
    });

    // сбросить флаг, он будет установлен после обработки запроса
    transferDoneFlag.Reset();

    // отправить запрос на чтение
    hostSocket.ReceiveFromAsync(socketEventArgs);

    // приостановить поток, пока не будет получен ответ или не истечёт время ожидания
    transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);

    // скопировать полученный текст в выходной параметр
    result = message;
    // вернуть результат запроса
    // если запрос выполнился успешно, возвращается пустая строка
    return response;  
}

Этот метод похож на метод SendMessageUDP. Он будет прослушивать указанный порт для получения ответа из сети. Однако, теперь адрес удалённого узла указывается с помощью класса IPEndPoint. Адрес конечной точки задаётся значением IPAddress.Any, что соответствует любому адресу, т.е. программа получит сообщение, отправленное на заданный порт с любого узла в сети. Если нужно получать сообщения только от одного адреса, здесь можно указать этот адрес.

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

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

Если запрос успешно считывает сообщение, оно должно быть преобразовано из последовательности 8-битовых значений в тип данных, с которым будет работать программа. Поскольку программа работает с текстовыми сообщениями, для декодирования сообщения используется класс Encoding для получения строки в кодировке UTF-8. Последним действием обработчик событий устанавливает флаг для обеспечения синхронизации потоков.

Созданный метод можно использовать так:

string receiveResponse;
string message;

receiveResponse = ReceiveMessageUDP(ECHO_PORT, out message);

Отправка дейтаграммы службе echo

Теперь можно создать код, который отправит сообщение службе echo. Сначала нужно вызвать метод SendMessageUDP для отправки сообщения службе, и затем вызвать ReceiveMessageUDP для получения ответа.

string sendResponse = SendMessageUDP(MessageTextBox.Text, HostTextBox.Text,
    ECHO_PORT);

if (sendResponse.Length == 0)
{
    string receiveResponse;
    string message;

    receiveResponse = ReceiveMessageUDP(ECHO_PORT, out message);
    if (receiveResponse.Length == 0)
    {
        ResponseTextBox.Text = message;
    }
    else
    {
        ErrorTextBox.Text = "Ошибка получения: " + receiveResponse;
    }
}
else
{
    ErrorTextBox.Text = "Ошибка отправки: " + sendResponse;
}

Если метод SendMessageUDP возвращает пустую строку, это значит, что сообщение отправлено успешно, и программа может вызвать метод ReceiveMessageUDP, чтобы получить ответное сообщение от службы echo.

Для проверки правильности работы программы необходима система, в которой есть служба echo. Такая служба есть в операционной системе Windows, но по умолчанию эта служба не устанавливается. Чтобы использовать службу echo, нужно в панели управления выбрать пункт Включение и отключение компонентов Windows и поставить галочку напротив пункта Простые службы TCPIP (такие как echo, datetime и т.п.). После этого можно либо перезагрузить компьютер, либо в оснастке Службы запустить службу Простые службы TCP/IP. Описанная последовательность действий работает в Windows 7, а в других версиях Windows может отличаться некоторыми деталями.

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

17.3. Создание подключения по протоколу TCP

Проблема использования протокола UDP заключается в том, что система, которая передаёт информацию по этому протоколу, не знает, принял ли эту информацию получатель. Если необходимо создать надёжное подключение, нужно использовать протокол TCP.

Протокол TCP позволяет создавать сеансы подключения между двумя устройствами. Сообщения создаются и передаются точно так же, как и при использовании протокола UDP, но каждое сообщение является частью сеанса. Сеанс будет продолжаться, пока одна из сторон не решит его прекратить. Ответ, который передаётся после приёма сообщения, позволяет программам определить, успешно ли было передано сообщение. В конце сеанса одна из сторон закрывает подключение.

Рассмотрим процесс создания метода для запроса веб-страницы:

private string RequestWebPage(string url, string page, out string pageContent)

Этот метод принимает в качестве параметров адрес веб-сервера, имя запрашиваемой страницы, и в третий параметр заносится содержимое веб-страницы. Метод возвращает пустую строку, если запрос был выполнен успешно, или описание ошибки, если запрос завершился неудачей.

В Интернете используется протокол передачи гипертекста HTTP. Этот протокол определяет язык, используемый браузером для запроса страниц содержимого веб-сервера. Например, чтобы получить с сервера веб-страницу, браузер отправляет команду GET и адрес веб-страницы на сервере.

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

  • Создать TCP-подключение к веб-серверу, используя порт 80, который зарезервирован для HTTP-запросов.
  • Отправить веб-серверу запрос GET, в котором указать нужную страницу.
  • Получить содержимое запрошенной веб-страницы. Если страница большая, может потребоваться несколько операций чтения.
  • Закрыть подключение.
  • Создание TCP-подключения

    Подключение по протоколу TCP создаётся аналогично рассмотренному ранее примеру.

    private string ConnectTCP(string host, int port)
    {
        string response = "Истекло время ожидания";
    
        DnsEndPoint hostEntry = new DnsEndPoint(host, port);
    
        hostSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
            ProtocolType.Tcp);
    
        SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
        socketEventArgs.RemoteEndPoint = hostEntry;
    
        socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
            }
            else
            {
                // получить результат запроса
                response = e.SocketError.ToString();
                // очистить параметры подключения
                hostSocket = null;
            }
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.ConnectAsync(socketEventArgs);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
        return response;
    }
    

    В отличие от предыдущего примера, в этом методе вызывается метод ConnectAsync класса Socket. Если этот метод выполнится успешно, то он создаст настроенное TCP-подключение к удаленной системе, с помощью которого можно будет отправлять и получать сообщения, пока подключение не будет закрыто. Однако, подключение создаётся для передачи только одной веб-страницы. Если пользователь перейдёт на другую страницу, необходимо будет создать новое TCP-подключение к другой странице.

    Отправка сообщения через TCP-подключение

    Далее нужно сформировать текст сообщения и отправить его через созданное подключение:

    private string SendMessageTCP(string message)
    {
        if (hostSocket == null)
            return "Подключение не установлено";
    
        string response = "Тайм-аут подключения";
      
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
    
        // использование параметров конечной точки для настройки передачи сообщения
        socketEventArg.RemoteEndPoint = hostSocket.RemoteEndPoint;
    
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);
        socketEventArg.SetBuffer(messageBytes, 0, messageBytes.Length);
    
        socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
            }
            else
            {
                response = e.SocketError.ToString();
                CloseTCP();
            }
    
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.SendAsync(socketEventArg);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    
        return response;
    }
    

    Этот код почти такой же, как и для отправки сообщения по UDP-подключению. Перед передачей сообщения проверяется, установлено ли подключение.

    Если в процессе передачи произошла ошибка, подключение закрывается методом CloseTCP:

    void CloseTCP()
    {
        if (hostSocket != null)
        {
            hostSocket.Close();
            hostSocket = null;
        }
    }
    

    Получение сообщения через TCP-подключение

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

    private string ReceiveMessageTCP(out string message)
    {
        message = "";
    
        if (hostSocket == null)
        {
            return "Подключение не установлено";
        }
    
        string response = "Тайм-аут подключения";
        string result = "";
    
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
        socketEventArg.RemoteEndPoint = hostSocket.RemoteEndPoint;
    
        // создания буфера для получения сообщения
        byte[] responseBytes = new Byte[MAX_BUFFER_SIZE];
        socketEventArg.SetBuffer(responseBytes, 0, MAX_BUFFER_SIZE);
    
        socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
                if (e.BytesTransferred > 0)
                {
                    result = Encoding.UTF8.GetString(e.Buffer, e.Offset,
                        e.BytesTransferred);
                    result = result.Trim('\0');
                }
            }
            else
            {
                response = e.SocketError.ToString();
            }
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.ReceiveAsync(socketEventArg);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    
        message = result;
    
        return response;
    }
    

    Эта версия метода проверяет, передал ли сервер какую-либо информацию, и если да, то декодирует полученную информацию и возвращает её в виде строки.

    Создание запроса

    Теперь можно использовать созданные методы для построения запроса веб-страницы. Метод отправит запрос и получит содержимое веб-страницы. Об окончании передачи свидетельствует получение пустого результата от метода ReceiveMessageTCP.

    private string RequestWebPage(string url, string page, out string pageContent)
    {
        pageContent = "";
    
        // создание подключения
        string response = ConnectTCP(url, 80);
    
        if (response != "")
        {
            return response;
        }
    
        // отправка запроса страницы с помощью HTTP-команды GET
        response = SendMessageTCP("GET " + page + " HTTP/1.1\r\nHost: " + url +
            "\r\nConnection: Close\r\n\r\n");
    
        if (response != "")
        {
            CloseTCP();
            return response;
        }
    
        string wholePage = "";
        string fetchText;
    
        // повторение получения пакетов от сервера
        // до тех пор, пока метод не вернёт пустую строку
        do
        {  
            response = ReceiveMessageTCP(out fetchText);
    
            if (response != "")
            {
                CloseTCP();
                return response;
            }
    
            wholePage += fetchText;
        }
        while (fetchText != "");
    
        pageContent = wholePage;
    
        CloseTCP();
    
        return response;
    }
    

    Этот метод возвращает полный текст запрошенной веб-страницы. Если какой-либо метод вернёт сообщение об ошибке, метод RequestWebPage закроет TCP-подключение и завершит работу. Код метода CloseTCP для закрытия подключения выглядит так:

    void CloseTCP()
    {
        if (hostSocket != null)
        {
            hostSocket.Close();
            hostSocket = null;
        }
    }
    

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

    17.4. Подключение к сетевому ресурсу

    Часто программам приходится получать информацию из различных сетевых ресурсов. Обычно это довольно утомительный процесс, но его можно упростить, используя класс WebClient.

    Класс WebClient является частью сетевой библиотеки.NET, которую можно использовать как в программах для Windows Phone, так и в программах для компьютеров под управлением Windows. Можно использовать класс для взаимодействия с удалённым сервером по протоколу HTTP.

    Создадим простую программу, которая при нажатии на кнопку выведет на экран код веб-страницы, адрес которой вводит пользователь в текстовое поле. Фактически, аналогичное действие выполняют веб-браузеры, только последние обрабатывают код страницы и выводят её в том виде, в каком её ожидает увидеть пользователь.

    Сначала нужно объявить переменную типа WebClient в классе MainPage. Эта переменная будет использоваться для управления веб-запросами, которые будут использоваться в программе. Экземпляр класса WebClient создаётся в конструкторе страницы:

    WebClient client;
    
    public MainPage()
    {
        InitializeComponent();
    
        client = new WebClient();
        client.DownloadStringCompleted +=
            new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    }
    

    В конструкторе также создаётся обработчик события DownloadStringCompleted, который выводит код веб-страницы в визуальный элемент pageTextBlock, если загрузка веб-страницы выполнилась успешно:

    void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            pageTextBlock.Text = e.Result;
        }
    }
    

    Остаётся добавить в программу обработчик нажатия на кнопку, в котором будет загружаться веб-страница, адрес которой ввёл пользователь:

    private void loadButton_Click(object sender, RoutedEventArgs e)
    {
        client.DownloadStringAsync(new Uri(urlTextBox.Text));
    }
    

    При нажатии на кнопку выполняются следующие действия:

  • Строка, введённая в текстовое поле urlTextBox, используется для создания URI требуемой веб-страницы.
  • Вызывается метод DownloadStringAsync класса WebClient, который запускает новую веб-транзакцию, после чего завершает свою работу и передаёт управление программе.
  • Через какое-то время класс WebClient считает содержимое запрошенной веб-страницы и сгенерирует событие DownloadStringCompleted, которое сигнализирует об окончании приёма сообщения.
  • Событие вызовет метод-обработчик client_DownloadStringCompleted, который проверит, возникли ли при получении данных ошибки, и если нет — выводит полученный код страницы в визуальный элемент pageTextBlock.
  • 17.5. Чтение данных из XML-потока с помощью LINQ

    Приложения могут взаимодействовать с различными информационными службами, например c Twitter, используя протокол передачи состояния представления REST. REST использует URL для указания необходимого действия. Например, если в браузере указать адрес http://twitter.com/statuses/user_timeline/user_name.xml, будет выдан XML-документ, который содержит новые сообщения пользователя "user_name". Если заменить в этой строке "user_name" на имя другого пользователя Twitter, будет выдана информация о сообщениях этого пользователя.

    Можно изменить программу, созданную в предыдущем примере, так, чтобы она создавала запрос для Twitter и использовала его для получения информации об указанном пользователе. Достаточно изменить имя элемента текстового поля на nameTextBox (но можно этого не делать — имя элемента несёт только смысловую нагрузку) и изменить код метода loadButton_Click:

    private void loadButton_Click(object sender, RoutedEventArgs e)
    {
        string url = "http://twitter.com/statuses/user_timeline/" + nameTextBox.Text
            + ".xml";
    
        client.DownloadStringAsync(new Uri(url));
    }
    

    Twitter предоставляет информацию о состоянии в формате XML. XML представляет информацию в структурированном виде, и её можно обработать и вывести на экран в удобном виде. Для обработки XML можно использовать LINQ.

    XML-документ похож на базу данных, в которой содержатся элементы и атрибутами. Кроме инструментов для создания запросов к базе данных и получения из них структурированной информации, LINQ также содержит инструменты для создания объектов на основе данных XML.

    Информация, полученная от Twitter, является структурированным XML-документом, который содержит элементы и атрибуты, которые описывают содержание сообщений Twitter:

    <?xml version="1.0" encoding="UTF-8"?>
    <statuses type="array">
      <status>
        <created_at>Tue Oct 11 11:57:37 +0000 2011</created_at>
        <id>27131245083</id>
        <text>
            Had 12,000 hits on www.csharpcourse.com for my C# Yellow Book. No idea why.
        </text>
        <source>web</source>
        <truncated>false</truncated>
        <in_reply_to_status_id></in_reply_to_status_id>
        <in_reply_to_user_id></in_reply_to_user_id>
        <favorited>false</favorited>
        <in_reply_to_screen_name></in_reply_to_screen_name>
        <retweet_count>0</retweet_count>
        <retweeted>false</retweeted>
        <user>
          <id>2479801</id>
          <name>Rob Miles</name>
          <screen_name>robmiles</screen_name>
          <location>Hull, UK</location>
          <description>A Computer Science lecturer and Microsoft MVP.</description>
          <profile_image_url>
              http://a3.twimg.com/prof_im/157/me2_normal.jpg
          </profile_image_url>
          <url>http://www.robmiles.com</url>
          <protected>false</protected>
          <friends_count>21</friends_count>
          <created_at>Tue Mar 27 12:35:28 +0000 2007</created_at>
          <favourites_count>0</favourites_count>
          <utc_offset>0</utc_offset>
          <time_zone>London</time_zone>
          <profile_background_image_url>
              http://s.twimg.com/a/1286/images/themes/theme1/bg.png
          </profile_background_image_url>
          <notifications>false</notifications>
          <geo_enabled>true</geo_enabled>
          <verified>false</verified>
          <following>true</following>
          <statuses_count>731</statuses_count>
          <lang>en</lang>
          <contributors_enabled>false</contributors_enabled>
          <follow_request_sent>false</follow_request_sent>
          <listed_count>61</listed_count>
          <show_all_inline_media>false</show_all_inline_media>
        </user>
        <geo/>
        <coordinates/>
        <place/>
        <contributors/>
      </status>
    </statuses>
    

    В этой XML-структуре представлена небольшая часть информации, которую выдаёт Twitter в виде XML-файла. В действительности, этот файл бывает гораздо большего объёма.

    С помощью LINQ можно создать запрос к базе данных, который возвратит коллекцию найденных объектов, а можно создать конструкцию, которая представляет содержание XML-документа:

    XElement TwitterElement = XElement.Parse(twitterText);
    

    Класс XElement является частью LINQ и представляет собой элемент XML. Строка twitterText содержит информацию, полученную от Twitter с помощью класса WebClient. Экземпляр класса XElement позволяет сохранить эту информацию в виде структурированного документа. Необходимо преобразовать эту информацию в форму, удобную для вывода на экран.

    Как было рассмотрено в предыдущих лекциях, можно создать классы C# для хранения информации об объектах и применить привязку данных для связи этих классов с визуальными элементами Silverlight. В эти классы можно загружать данные как из базы данных, так и из XML-файла.

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

    public class TwitterPost
    {
        public string PostText { get; set; }
    
        public string DatePosted { get; set; }
    
        public string UserImage { get; set; }
    }
    

    Эти свойства будут содержать значения, полученные от Twitter, и пользователь не будет их изменять. Обратите внимание, что свойство UserImage имеет строковый тип и содержит URI изображения, которое будет загружаться из Интернета.

    Теперь необходимо использовать LINQ для получения коллекции объектов из элемента XElement, который содержит всю информацию, полученную от Twitter.

    var postList = from tweet in twitterElements.Descendants("status")
                    select new TwitterPost
                    {
                        UserImage =
                            tweet.Element("user").Element("profile_image_url").Value,
                        PostText = tweet.Element("text").Value,
                        DatePosted = tweet.Element("created_at").Value
                    };
    

    В этом коде в секции from выполняется перебор элементов с именем <status>. Эти элементы программа обрабатывает по очереди. В секции select для каждого элемента создаётся экземпляр класса TwitterPost, и его свойствам присваиваются значения указанных XML-элементов, вложенных в элемент <status>. Метод Element возвращает содержимое XML-элемента с заданным именем.

    Результат запроса заносится в переменную postList, которая фактически будет являться коллекцией объектов класса TwitterPost. Если применить запрос к XML-документу, который был представлен выше, то запрос создаст список из одного элемента. Однако, в общем случае полученный список будет содержать несколько объектов.

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

    (рис 17.2) Внешний вид сообщения, полученного из сети Twitter

    Это изображение можно разделить на три блока: слева будет размещена фотография пользователя, в верхней части — дата создания сообщения, по центру — текст сообщения (рис. 17.3).

    (рис 17.3) Структура сообщения, полученного из сети Twitter

    В Silverlight эту разметку можно выполнить, используя элементы-контейнеры StackPanel:

    <StackPanel Orientation="Horizontal" Height="132">
      <Image Source="{Binding UserImage}" Height="73" Width="73" VerticalAlignment="Top"
          Margin="0,10,8,0"/>
      <StackPanel Width="370">
        <TextBlock Text="{Binding DatePosted}" Foreground="#FFC8AB14" FontSize="22" />
        <TextBlock Text="{Binding PostText}" TextWrapping="Wrap" FontSize="24" />
      </StackPanel>
    </StackPanel>
    

    Класс StackPanel позволяет автоматически располагать и выравнивать элементы на странице программы. В этом коде используется привязка данных, которая связывает элементы со свойствами класса TwitterPost.

    Полученную разметку необходимо указать в качестве шаблона разметки элементов списка ListBox:

    <ListBox Height="442" HorizontalAlignment="Left" Name="tweetsListBox"
        VerticalAlignment="Top" Width="468">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal" Height="132">
            <Image Source="{Binding UserImage}" Height="73" Width="73"
                VerticalAlignment="Top" Margin="0,10,8,0"/>
            <StackPanel Width="370">
              <TextBlock Text="{Binding DatePosted}" Foreground="#FFC8AB14"
                  FontSize="22" />
              <TextBlock Text="{Binding PostText}" TextWrapping="Wrap" FontSize="24" />
            </StackPanel>
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
    

    После этого остаётся только указать переменную postList, в которой хранятся сообщения из Twitter, в качестве источника данных для списка ListBox:

    tweetsListBox.ItemsSource = postList;
    

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

    17.6. Взаимодействие приложений с сетевыми службами

    Приложения для Windows Phone могут взаимодействовать с веб-серверами, используя протоколы Интернета. Также можно создавать сетевые службы и использовать их в устройствах Windows Phone.

    Службы Windows Communication Foundation, или WCF, предоставляют возможность процессам взаимодействовать друг с другом по сети. Процессы могут обмениваться сообщениями по сети, используя различные сетевые среды. Службы работают на сервере, к которому могут получать доступ клиентские программы. В нашем случае клиентская программа будет работать в Windows Phone.

    На рис. 17.4 представлен принцип взаимодействия программы с сетевой службой. Программное обеспечение телефона взаимодействует с прокси-объектом, который содержит методы, предоставляемые службой. Вызов метода прокси-объекта приводит к созданию и оправке сетевого сообщения службе на сервере. При получении сообщения сервер вызывает код метода серверного объекта. Результат, возвращаемый методом, упаковывается в другое сетевое сообщение, которое отправляется клиенту, который пересылает возвращаемое значение программе, которая вызвала этот метод. При этом, система выполняет всю работу по созданию и передаче сообщений. Необходимо только создать серверные методы и вызывать их на клиентской системе.

    (рис 17.4) Взаимодействие программы для Windows Phone с сетевой службой

    Также служба предоставляет описание её методов. Это описание использует среда разработки (в нашем случае — Visual Studio) для создания прокси-объекта в клиентской программе. Это упрощает создание клиентского приложения.

    Рассмотрим пример создания службы Новость дня, которая будет возвращать одну из новостей по запросу клиента. Клиент должен передать в качестве параметра цифру 1, 2 или 3, которая, для удобства, будет обозначать категорию новостей.

    Приложение будет состоять из двух частей: программа на сервере, которая экспортирует службу, и программа на клиенте, которая её использует. Сервер будет службой WCF, а клиент — приложением для Windows Phone, которое подключается к службе и запрашивает новость.

    Для создания службы WCF нужно при создании проекта Visual Studio выбрать шаблон Приложение службы WCF в группе WCF. Службу можно тестировать в среде Visual Studio. После тестирования файлы проекта можно будет загрузить на рабочий сервер. Обратите внимание, что службу можно создать в Visual Web Developer 2010 Express.

    Службы и клиенты

    Службы и клиенты похожи на методы. При создании сервера создаётся метод, который выполняет какие-то действия и возвращает результат. Клиента вызывает метод сервера и обрабатывает результат. При этом, служба WCF выполняет все необходимые действия по упаковке параметров и возвращаемого значения метода в сетевые сообщения. При создании проекта службы в файле .cs создаётся интерфейс, который определяет предоставляемый службой метод:

    [ServiceContract]
    public interface INewsOfTheDayService
    {
        [OperationContract]
        string GetData(int value);
    }
    

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

    После создания интерфейса можно создать код метода, который будет предоставлять служба. Заготовка метода создаётся по умолчанию в файле службы с расширением .svc, с которым связан одноимённый файл .cs. Атрибуты [ServiceContract] и [OperationContract] предоставляют средства для создания описания службы при построении проекта, которые будут использоваться клиентами для взаимодействия с этими службами.

    public class NewsOfTheDayService : INewsOfTheDayService
    {
        public string GetData(int value)
        {
            string result;
            switch (value)
            {
                // категория "Компьютеры"
                case 1:
                    result = "Intel Xeon E5-2600: "облачные" процессоры ставят рекорды";
                    break;
      
                // категория "Программное обеспечение"
                case 2:
                    result =
                        "Вышла в свет ОС Windows 8 Consumer Preview";
                    break;
    
                // категория "Мобильные устройства"
                case 3:
                    result =
                        "Nokia выпускает новые телефоны с ОС Windows Phone 7.5 Mango";
                    break;
    
                // если указан неверный номер категории
                default:
                    result = "Выбрана несуществующая категория";
                    break;
            }
            return result;
        }
    }
    

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

    Если попытаться запустить проект, нажав клавишу F5 (или другим способом), Visual Studio создаст веб-сервер, на котором будет запущена служба, и откроется окно программы Тестовый клиент WCF, в котором можно вызвать метод службы с различными параметрами и просмотреть результаты, которые метод возвращает (рис. 17.5).

    (рис 17.5) . Окно программы Тестовый клиент WCF

    Также можно просмотреть описание службы в браузере, указав URL службы, который можно найти в окне программы Тестовый клиент WCF (рис. 17.6).

    (рис 17.6) Страница службы, открытая в браузере

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

    Создание клиентского приложения

    Для того чтобы создать приложение Windows Phone, которое будет работать с созданной службой, нужно создать новый проект. Клиентское приложение должно подключаться к службе NewsOfTheDayService. Ссылка на службу добавляется в проект так же, как и ссылка на ресурс, только в контекстном меню в окне обозревателя решений нужно выбрать пункт Добавить ссылку на службу…. Откроется диалоговое окно Добавить ссылку на службу, в котором нужно указать URL запущенной службы. Visual Studio найдёт описание службы и все предоставляемые ею методы. Также можно изменить пространство имён по умолчанию, которое будет использоваться в создаваемом приложении, на NewsOfTheDayService.

    После добавления ссылки на службу её можно использовать в решении. Теперь необходимо создать прокси-объект, который будет использоваться для вызова методов службы. Лучше сделать это в конструкторе главной страницы приложения:

    NewsOfTheDayService.NewsOfTheDayServiceClient newsclient;
    
    // Конструктор
    public MainPage()
    {
        InitializeComponent();
    
        newsclient = new NewsOfTheDayService.NewsOfTheDayServiceClient();
    
        newsclient.GetDataCompleted +=
            new EventHandler<NewsOfTheDayService.GetDataCompletedEventArgs>
            (newsclient_GetDataCompleted);
    }
    

    Этот код создаёт прокси-объект для службы и связывает метод-обработчик с событием GetDataCompleted, которое происходит при получении ответа от службы. Вызов метода службы выполняется асинхронно. Метод-обработчик должен выводить на экран полученную новость:

    void newsclient_GetDataCompleted(object sender,
        NewsOfTheDayService.GetDataCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            newsTextBlock.Text = e.Result;
        }
    }
    

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

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

    private void getNewsButton_Click(object sender, RoutedEventArgs e)
    {
        int category = 0;
    
        if (int.TryParse(newsTextBox.Text, out category))
        {
            newsclient.GetDataAsync(category);
        }
    }
    

    Этот метод получает номер категории новости и передаёт его в качестве параметра при вызове метода GetDataAsync. Если текст в поле newsTextBox не может быть преобразован в целое число, метод TryParse вернёт значение false.

    Если служба будет недоступна, при вызове метода GetDataAsync будет сгенерировано исключение. Это может произойти, если во время выполнения программы в телефоне сигнал сети не будет доступен. В приложениях для Windows Phone необходимо обрабатывать исключения в блоке trycatch и выдавать пользователю сообщение о недоступности сети.

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

    Краткие итоги

  • Компьютерные сети предоставляют возможность отправки сообщений из одной системы в другую. Устройства могут объединяться в локальную сеть. В локальной сети может быть маршрутизатор, который отправляет пакеты из локальной сети удалённым узлах. Маршрутизаторы являются устройствами передачи данных, которые позволяют создавать межсетевые подключения.
  • Полный адрес системы в сети содержит адрес этой станции в её локальной сети и адрес этой сети в Интернете.
  • Данные могут передаваться между системами в виде отдельных дейтаграмм (UDP) или как часть подключения между двумя системами (TCP).
  • К различным службам системы можно обращаться, указав номер порта, на котором работает требуемая служба. Некоторые номера портов зарезервированы и являются общепринятыми, например веб-сервер обычно работает на порту с номером 80.
  • Приложения Windows Phone могут создавать и использовать объекты, которые представляют подключение к сетевой службе. Эти объекты работают в асинхронном режиме, при котором сетевые операции выполняются в отдельном потоке. Результаты асинхронного запроса обрабатываются при наступлении события окончания выполнения запроса без необходимости ожидания завершения запроса в программе.
  • Сокеты являются абстракцией сетевого подключения.
  • Некоторые сетевые вызовы могут возвращать структурированные данные, которые содержат XML-описание содержимого.
  • Библиотеки LINQ могут создавать динамические объекты для представления структурированных данных и их использования в программе.
  • Технология Windows Communication Foundation (WCF) предоставляет возможность настроить сервер так, чтобы можно было вызывать их методы в клиентских приложениях.
  • Клиентское приложение может прочитать описание службы, которое предоставляет сервер WCF, и использовать его для создания прокси-объекта, который содержит методы, предоставляемые службой. При вызове в клиентском коде метод прокси-объекта запускается сетевая операция, которая передаёт параметры метода на сервер. Затем вызывается метод сервера, и сетевая операция возвращает результат коду, который вызвал серверный метод.
  • Ключевые термины

    IP-адрес — уникальный сетевой адрес узла в компьютерной сети, построенной по протоколу IP.

    Windows Communication Foundation — технология компании Microsoft, основанная на .NET Framework, которая используется для обмена данными между приложениями, находящихся обычно на разных устройствах.

    Асинхронное подключение — тип подключения, которое работает паралллельно с основной программой и обычно обрабатывается в отдельном потоке.

    Брандмауэр — комплекс аппаратных или программных средств, осуществляющий контроль и фильтрацию проходящих через него сетевых пакетов в соответствии с заданными правилами.

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

    Конечная точка подключения — удалённый узел, к которому подключается программа на текущем узле.

    Маршрутизация — процесс определения маршрута следования информации в сетях связи.

    Октетами — наименьшая единица информации, которая может быть передана по сети, равняется 8 битам.

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

    Сетевой маршрутизатор — сетевое устройство, пересылающее пакеты данных между различными сегментами сети.

    Сетевой протокол — набор соглашений, которые определяют, как происходит обмен данными между устройствами в сети.

    Сетевые сигналы — носитель информации, которая передаётся через компьютеную сеть.

    Система доменных имён — компьютерная распределённая система для получения информации о доменах, позволяет связать имя узла с его IP-адресом.

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

    Вопросы

  • Какие типы подключений к сети доступны в Windows Phone?
  • Какие образом данные передаются по сети?
  • Для чего используется система доменных имён?
  • Каким образом несколько взаимодействующих с сетью служб могут работать на одном сервере только с теми сообщениями, которые для них предназначены?
  • В чём отличие сеансов подключений от дейтаграмм?
  • Для чего используется класс Socket?
  • Как в программе можно создать подключение по протоколу UDP?
  • Как в программе можно создать подключение по протоколу TCP?
  • Для чего используется класс WebClient?
  • Как получить необходимые данные из XML-структуры с помощью LINQ?
  • Для чего предназначена технология Windows Communication Foundation?
  • Как создать службу WCF?
  • Как создать приложение для Windows Phone для использования методов службы WCF?
  • Упражнения

    Для выполнения упражнений необходимо работающее сетевое подключение, а также среда Visual Studio для создания службы WCF.

    Упражнение 1. Создание службы TimeTracker

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

    Создание службы

  • Откройте Visual Studio.
  • Выберите в главном меню пункт Файл -> Создать проект. Откроется диалоговое окно Создать проект.
  • В списке Установленные шаблоны выберите в группе WCF шаблон Приложение службы WCF.
  • Назовите проект TimeTrackerService и нажмите OK, чтобы создать проект. Visual Studio создаст новый проект и откроет файл Service1.svc.cs.
  • Задайте службе и её интерфейсу более удобные имена. Для этого выберите в обозревателе решений файл IService1.cs, нажмите F2 и введите новое имя ITimeTrackerService.cs. При нажатии клавиши Enter Visual Studio предложит переименовать интерфейс и все ссылки на него, чтобы его имя соответствовало новому имени файла. Нажмите OK для подтверждения.
  • Аналогичным образом переименуйте файл службы от Service1.svc в TimeTrackerService.svc.
  • Откройте файл TimeTrackerService.svc.cs и щёлкните правой кнопкой по названию класса Service1. В контекстном меню выберите пункт Рефакторинг -> Переименовать…, укажите в открывшемся окне новое имя класса TimeTrackerService и подтвердите переименование.
  • Создание интерфейсов методов службы

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

  • Откройте файл ITimeTrackerService.cs. Он содержит интерфейс, который описывает методы, предоставляемые службой.
  • Удалите весь код внутри интерфейса и добавьте следующее описание методов:
    [OperationContract]
    void SaveSession(string employeeName, string companyName, int startHour, int startMin,
        int endHour, int endMin);
    
    [OperationContract]
    string GetSessionList();
    
  • Удалите описание класса CompositeType.
  • Создание методов службы

  • Откройте файл TimeTrackerService.svc.cs.
  • Удалите всё содержимое класса TimeTrackerService и добавьте в него следующий код:
    class Session
    {
        public string EmployeeName { get; set; }
        public string CompanyName { get; set; }
        public int StartHour { get; set; }
        public int StartMin { get; set; }
        public int EndHour { get; set; }
        public int EndMin { get; set; }
    
        public override string ToString()
        {
            return EmployeeName + " " + CompanyName;
        }
    }
    
    static List<Session> sessions = new List<Session>();
    
    Этот класс будет хранить информацию о каждой встрече, а также содержит список встреч, который будет формироваться во время работы службы. В реальном приложении этот список должен заполняться информацией из базы данных.
  • Добавьте в класс TimeTrackerService следующие методы:
    public void SaveSession(string employeeName, string companyName,
        int startHour, int startMin, int endHour, int endMin)
    {
        sessions.Add(new Session
        {
            EmployeeName = employeeName,
            CompanyName = companyName,
            StartHour = startHour,
            StartMin = startMin,
            EndHour = endHour,
            EndMin = endMin
        });
    }
    
    public string GetSessionList()
    {
        StringBuilder result = new StringBuilder();
    
        for (int i = 0; i < sessions.Count; i++)
        {
            result.AppendLine(sessions[i].ToString());
        }
        return result.ToString();
    }
    
  • Метод SaveSession сохраняет информацию о сеансе на сервере. Метод GetSessionList возвращает сохранённую информацию о встречах в виде многострочного текста.

    Развёртывание службы

  • Выберите в обозревателе решений файл TimeTrackerService.svc и нажмите F5 для запуска проекта. После построения решения откроется окно тестового клиента WCF.
  • Дважды щёлкните мышью по методу SaveSession(). Введите следующие значения параметров метода:
  • employeeNameTest Employee
  • companyNameTest Company
  • startHour — 1
  • startMin — 2
  • endHour —3
  • endMin — 4
  • Нажмите на кнопку Вызвать, чтобы вызвать метод службы с указанными параметрами. Метод вернёт пустое значение.
  • Дважды щёлкните мышью по методу GetSessionList() и нажмите на кнопку Вызвать. Метод вернёт строку, соответствующую встрече, добавленной в систему при вызове метода SaveSession.
  • Нажмите клавиши Shift + F5 для остановки службы.
  • В обозревателе решений щёлкните правой кнопкой мыши по файлу TimeTrackerService.svc и выберите в контекстом меню пункт Просмотр в обозревателе. Обратите внимание на URL службы в адресной строке браузера — это значение понадобится для подключения к службе клиента:
    http://localhost:30975/TimeTrackerService.svc
    
    В вашем случае номер порта может отличаться, поскольку Visual Studio генерирует его автоматически.
  • Закройте браузер и запустите службу в Visual Studio ещё раз. Запущенная служба будет использоваться в следующем упражнении.
  • Упражнение 2. Создание клиента службы

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

    Подключение клиентского приложения к службе

  • Убедитесь в том, что созданная в предыдущем упражнении служба запущена.
  • Запустите другую копию Visual Studio и откройте в ней проект TimeTrackerClient в папке Lab6 TimeTrackerClient. В текстовые поля пользователь может ввести имя сотрудника, название компании, с которой проводится встреча, а также время начала и окончания встречи. При нажатии на кнопку сохранить, программа должна сохранить информацию о встрече на сервере, и при нажатии на кнопку список встреч — вывести информацио о встречах, полученную от службы.
  • В обозревателе решений щёлкните правой кнопкой мыши по элементу Ссылки и выберите пункт Добавить ссылку на службу….
  • В открывшемся диалоговом окне Добавить ссылку на службу в поле Адрес введите URL службы, полученный в предыдущем упражнении в результате запуска службы, и нажмите кнопку Перейти. Visual Studio загрузит описание службы с сервера и выведет на экран информацию о службе.
  • Откройте описание службы, щёлкнув по стрелке слева от её имени, и выберите имя интерфейса ITimeTrackerService. В поле операции будут отображены имена двух созданных методов службы.
  • Измените пространство имён на TimeTrackerService и нажмите кнопку OK. Visual Studio добавит в проект ссылку на службу.
  • Щёлкните правой кнопкой мыши по созданной ссылке на службу TimeTrackerService и выберите пункт Настроить ссылку на службу….
  • В открывшемся диалоговом окне снимите галочку Повторно использовать типы в сборках, на которые есть ссылки и нажмите OK.
  • Использование метода службы SaveSession в приложении

  • Откройте файл MainPage.xaml.cs.
  • Добавьте в начало объявления класса следующую строку:
    private TimeTrackerService.TimeTrackerServiceClient timeTracker;
    
  • Добавьте в конструктор класса после вызова метода InitializeComponent следующий код для создания связанного со службой объекта и обработчиков событий службы (для создания обработчиков событий можно использовать Intellisense — в этом случае автоматически будет создана большая часть кода):
    timeTracker = new TimeTrackerService.TimeTrackerServiceClient();
    
    timeTracker.SaveSessionCompleted +=
        new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(
        timeTracker_SaveSessionCompleted);
    
    timeTracker.GetSessionListCompleted +=
        new EventHandler<TimeTrackerService.GetSessionListCompletedEventArgs>(
        timeTracker_GetSessionListCompleted);
    
  • Добавьте код метода saveButton_Click для асинхронного вызова метода службы и передачи введённых пользователем значений:
    private void saveButton_Click(object sender, RoutedEventArgs e)
    {
        int startHour, startMin, endHour, endMin;
    
        if (int.TryParse(startHourTextBox.Text, out startHour) 
            int.TryParse(startMinTextBox.Text, out startMin) 
            int.TryParse(endHourTextBox.Text, out endHour) 
            int.TryParse(endMinTextBox.Text, out endMin))
        {
            timeTracker.SaveSessionAsync(employeeNameTextBox.Text,
                companyNameTextBox.Text, startHour, startMin, endHour, endMin);
        }
    }
    
  • Добавьте код метода-обработчика timeTracker_SaveSessionCompleted, который будет вызываться при завершении выполнения метода службы:
    void timeTracker_SaveSessionCompleted(object sender,
        System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            employeeNameTextBox.Text = "Введите имя сотрудника...";
            companyNameTextBox.Text = "Введите название компании...";
        }
    }
  • Если метод службы выполнится успешно, то текст в текстовых полях приложения будет изменён на первоначальный.

    Тестирование метода SaveSession

  • Нажмите F5, чтобы запустить программу в эмуляторе.
  • Введите в текстовые поля информацию о встрече и нажмите на кнопку сохранить. Текст в текстовых полях должен измениться на первоначальный.
  • Таким же образом добавьте информацию о второй встрече.
  • Остановите выполнение программы.
  • Использование метода службы GetSessionList

  • Добавьте в приложение код метода listButton_click для вызова метода службы:
    private void listButton_Click(object sender, RoutedEventArgs e)
    {
        timeTracker.GetSessionListAsync();
    }
    
  • Добавьте в приложение код метода timeTracker_GetSessionListCompleted для вывода на экран результата выполнения метода службы:
    void timeTracker_GetSessionListCompleted(object sender,
        TimeTrackerService.GetSessionListCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            sessionsTextBlock.Text = e.Result;
        }
    }
    
  • Запустите программу и нажмите кнопку список встреч. На экран будет выведена информация о двух введённых встречах.
  • Страницы:

    Презентацию к данной лекции Вы можете скачать здесь.

    Все устройства Windows Phone могут использовать сетевые подключения к сети оператора сотовой связи и к сетям Wi-Fi. Эти две сетевых технологии являются взаимозаменяемыми. Однако, оба типа сетей не всегда бывают доступными одновременно.

    Если при подключении к одной из сетей мобильной связи или Wi-Fi сигнал сети пропадёт, то это подключение прервётся. Приложения должны корректно работать в подобной ситуации — по крайней мере, выводить на экран сообщение вида: "Используемое сетевое подключение временно недоступно. Попробуйте повторить попытку позже". Предпочтительнее, если программа сохранит данные в устройстве, чтобы отправить их, когда подключение к сети будет доступно.

    В эмуляторе Windows Phone доступны такие же сетевые возможности, как и в реальном устройстве. Эмулятор использует сетевые подключения компьютера, на котором он запущен. Его также можно использовать для тестирования приложений Windows Phone, использующих сетевые подключения. Однако, имеет смысл протестировать работу программы на физическом устройстве. В телефоне можно отключить сетевые службы и проверить работу приложения, чтобы быть уверенным, что оно будет работать должным образом и с низкоскоростным подключением к сети, если высокоскоростное подключение окажется недоступным.

    17.1. Основные сведения о сетях

    Перед изучением использования сетевых подключений в приложениях для Windows Phone необходимо ознакомиться с основными сведениями о сетях. Это необходимо для того чтобы лучше понять, как будут работать создаваемые нами программы. Подробное изучение вопросов сетевого взаимодействия устройств не входит в темы данного курса — приводится лишь некоторая базовая информация.

    Сетевые сигналы

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

    В Windows Phone существует несколько типов сетевых сигналов, которые могут использоваться для подключения к сети: Wi-Fi, сотовая сеть и USB-подключение к программному обеспечению Zune на компьютере. Все эти технологии позволяют передавать данные между программами в телефоне и Интернетом (рис. 17.1).

    (рис 17.1) Схема взаимодействия программ телефона с сетевыми подключениями

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

    Программа Windows Phone может определить, какие доступны подключения, с помощью методов класса DeviceNetworkInformation.

    using Microsoft.Phone.Net.NetworkInformation;
    
    ...
    
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    
    sb.Append("Оператор: " );
    sb.AppendLine(DeviceNetworkInformation.CellularMobileOperator);
    
    sb.Append("Сеть доступна:  ");
    sb.AppendLine(DeviceNetworkInformation.IsNetworkAvailable.ToString());
    
    sb.Append("Передача данных по сети доступна:  ");
    sb.AppendLine(DeviceNetworkInformation.IsCellularDataEnabled.ToString());
    
    sb.Append("Передача данных в роуминге доступна:  ");
    sb.AppendLine(DeviceNetworkInformation.IsCellularDataRoamingEnabled.ToString());
    
    sb.Append("Wi-Fi включён:  ");
    sb.AppendLine(DeviceNetworkInformation.IsWiFiEnabled.ToString());
    

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

    Сетевые пакеты

    Последовательности двоичных сигналов представляют какую-то информацию. Чтобы передавать полезную информацию, необходимо определить формат передаваемого сообщения.

    Современная сеть передаёт данные частями по 8 битов, которые называют октетами. В языке C# значение одного октета можно сохранить в переменной типа byte. Октет — это наименьшая единица информации, которую можно передать по сети. Октет может быть, например, числом в диапазоне от 0 до 255, символом или частью большего элемента, например числа с плавающей точкой или файла изображения. В Интернете пакет данных может состоять из нескольких тысяч октетов.

    Адресация пакетов

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

    Все устройства сети могут получать и организовывать широковещательную передачу данных. Фактически, узлы сети могут принимать все сообщения, переданные по каналу Wi-Fi или проводам. При этом возникает проблема безопасности передачи информации: устройства в сети могут использовать любую информацию, которая предназначена другим устройствам. При подключении к безопасному веб-сайту компьютер кодирует все передаваемые на сайт сообщения так, чтобы другие узлы сети, которые получают эти сообщения, не могли их прочитать зашифрованную в них информацию.

    Для формирования адреса узла, которому необходимо передать сообщение, существующая версия протокола IPv4, который используется в компьютерных сетях и в Интернете, использует четыре 8-битовых значения, записанных последовательно. Теоретически такой способ позволяет адресовать более 4 млрд различных систем в Интернете. Однако, на практике эта цифра оказывается существенно меньше, поскольку некоторые адреса зарезервированы для различных целей и их нельзя использовать для адресации устройства. Не так давно вышла в свет новая версия протокола IPv6, в котором каждый адрес занимает 128 битов (16 октетов). Новая версия протокола должна учесть значительное увеличение количества возможных узлов, однако его использование приводит к существенным изменениям в функционировании сети, осуществление которых требует много времени. В настоящее время сетевые возможности Windows Phone не поддерживают протокол IPv6.

    Маршрутизация

    Не все устройства в Интернете соединены в одну физическую сеть. По сути, Интернет объединяет большое количество отдельных сетей. Что чтобы передать сообщение из одной физической сети в другую, необходимо осуществить маршрутизацию.

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

    Пакеты, которые необходимо отправить с домашнего компьютера на удалённые устройства, передаются через сеть Интернет-провайдера (ISP), который передаёт эти пакеты к следующей системе в сети. Обычно пакеты доходят до узла-адресата, минуя несколько устройств. Интернет определяет и периодически изменяет маршруты, по которым следуют пакеты, что позволяет управлять внезапным возрастанием трафика и отказами частей сети, но это может привести к ситуациям, когда один пакет прибывает на узел адресата до того момента, как туда прибывает пакет, отправленный раньше первого. Иногда пакеты могут потеряться (хотя это происходит довольно редко), и таким образом, нельзя быть уверенным, что все пакеты прибыли, пока не придёт подтверждение об этом.

    Передача данных в Интернете осуществляется в соответствии с локальным протоколом (протокол управления передачей данных TCP) и межсетевым протоколом (Интернет-протокол IP). Эти протоколы работают совместно и образуют стек протоколов TCP/IP.

    Протокол TCP/IP можно использовать для связи устройств без их подключения к Интернету. Например, можно подключить устройство Windows Phone к компьютеру и передавать мультимедиа данные с помощью программного обеспечения Zune.

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

    Сетевые подключения и протоколы

    Подключения между разными частями сети могут использовать разные типы подключения. Многие устройства поддерживают несколько типов сетевых подключений. Устройство Windows Phone может подключаться к Интернету через сеть оператора сотовой связи или через сеть Wi-Fi. При этом, для программ Windows Phone не имеет значения, каким способом телефон подключён к Интернету.

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

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

    Нахождение адресов в Интернете с помощью DNS

    Все адреса в Интернете имеют размер 32 бита и обычно записываются в виде значений четырёх октетов, разделённых точкой. Адрес состоит из адреса подсети и адреса узла в этой подсети. Такой адрес называется IP-адресом системы.

    Однако, IP-адреса всех используемых узлов запомнить довольно сложно, и к тому же, в них легко запутаться. Поэтому для обращения к узлам в Интернете используют более удобные имена. Для того чтобы по имени узла найти его IP-адрес, существуют специальные серверы, которые преобразуют имена узлов в IP-адреса. Такая система называется системой доменных имён, или DNS. Когда компьютеру нужно узнать IP-адрес веб-сайта, на который хочет зайти пользователь, он направляет запрос к одному из DNS-серверов, который выдаёт необходимый результат.

    В сети Интернет работает так много устройств, что при использовании протокола IPv4 всех доступных IP-адресов недостаточно для того, чтобы выделить каждому устройству отдельный IP-адрес. Поэтому большинство устройств находятся в локальных подсетях, каждая из которых подключается к Интернету через маршрутизатор, который имеет прямой выход в Интернет. При этом, локальные адреса устройств позволяют получать доступ только к устройствам в той же подсети и к устройствам, подключённым к Интернету напрямую. Соответственно, два телефона обычно не могут соединяться друг с другом напрямую — для этого необходимо промежуточное устройство для связи телефонов в одну подсеть, например, Wi-Fi-маршрутизатор.

    Сетевые порты

    Если между двумя компьютерами установлено сетевое подключение, то его могут использовать любые приложения, которым разрешён доступ к сети. Например, сервер может предоставлять подключённым клиентам доступ к различным службам. При этом, клиентам нужен способ определения местонахождения службы, которую они хотят использовать на сервере.

    Для этих целей в Интернете используются порты. Порт — это число, которое идентифицирует службу, которую предоставляет компьютер. Некоторые из портов являются общепринятыми, например порт номер 80 обычно используется веб-серверами. Когда браузер клиента запрашивает веб-страницу, он использует Интернет-адрес сервера и использует порт 80, чтобы получить веб-страницу с этого сервера.

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

    Программисты могут создавать программы, которые используют любой номер порта, но многие сети используют брандмауэры, которые пропускают пакеты, адресованные определённым номерам портов, а если пакеты приходят на другие порты — они блокируются. Эта мера позволяет повысить безопасность сервера от сетевых атак.

    Сеансы подключения и дейтаграммы

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

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

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

    В Интернете используются протокол управления подключениями TCP, который позволяет настраивать и управлять подключениями между сетевыми станциями, и протокол пользовательских дейтаграмм UDP, который позволяет передавать дейтаграммы.

    Сетевые средства Windows Phone позволяют отправлять дейтаграммы и создавать сеансы подключения. Программисты решают сами, какой тип подключения лучше использовать в конкретной ситуации.

    17.2. Создание подключения по протоколу UDP

    Рассмотрим процесс создания программы, которая будет использовать протокол UDP для обмена сообщениями с удалённой службой под названием echo. Эта служба принимает входящие сообщения и отправляет их назад. Такая служба полезна для тестирования сетевых подключений. Это одна из самых первых Интернет-служб, и ей выделен порт с номером 7. Службу echo можно запустить на компьютере и использовать её для обмена дейтаграммами.

    Создадим метод, который будет использовать протокол UDP для отправки сообщения на удалённый сервер. Пользователь должен будет ввести в текстовые поля программы сообщение для отправки и адрес целевого узла, а программа выполнит все необходимые действия. Метод может выглядеть так:

    /// <summary>
    /// Отправляет сообщение по указанному адресу, используя протокол UDP
    /// </summary>
    /// <param name="message">сообщение для отправки</param>
    /// <param name="hostUrl">URL узла</param>
    /// <param name="portNumber">порт</param>
    /// <returns>сообщение об ошибке</returns>
    public string SendMessageUDP(string message, string hostUrl, int portNumber)
    {  
        // создать сокет и отправить сообщение
        // возвратить сообщение об ошибке, если оправка сообщения завершится неудачей
        // возвратить пустую строку, если сообщение будет передано успешно
    }
    

    Этот метод можно использовать так:

    const int ECHO_PORT = 7;
    string sendResponse = SendMessageUDP(MessageTextBox.Text, HostTextBox.Text,
        ECHO_PORT);
    

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

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

    Класс Socket

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

    Несмотря на то, что Windows Phone поддерживает несколько способов подключения к Интернету, программа будет использовать ту же самую технологию сокетов для управления подключением. Класс Socket выбирает наиболее подходящий способ подключения. Если доступная сеть Wi-Fi, то будет использоваться она, а при её отсутствии будет установлено подключение к сети оператора сотовой связи.

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

    Класс Socket описан в пространстве имён System.Net.Sockets, ссылку на которое рекомендуется добавить с помощью директивы using. Обычно файл библиотеки для работы с сетью уже является частью проекта, и его не нужно добавлять в проект вручную.

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

    Socket hostSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
        ProtocolType.Udp);
    

    Этот код создаёт экземпляр класса Socket, который использует адресацию IPv4 и дейтаграммы протокола UDP.

    Класс SocketAsyncEventArgs

    Теперь необходимо создать экземпляр класса SocketAsyncEventArgs, который используется для того чтобы сконфигурировать созданный сокет. Этот класс позволяет создать асинхронное подключение.

    Для начала нужно задать конечную точку подключения, с которой будет взаимодействовать программа. Конечная точка состоит из адреса узла и номера порта на этом узле. Номер порта фактически идентифицирует программу на компьютере, с которой будет взаимодействовать наша программа. В нашем примере будет производиться передача сообщений в программу echo, которая использует порт 7.

    Класс SocketAsyncEventArgs содержит свойство RemoteEndPoint, которое описывает конечную точку подключения:

    SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
    socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber);
    

    Этот код создаёт новый экземпляр класса SocketAsyncEventArgs и использует класс DnsEndPoint для указания Интернет-адреса узла и номера порта. При создании экземпляра класса DnsEndPoint указывается строка URL целевой системы, и класс будет использовать систему доменных имён, чтобы найти фактический IP-адрес узла, который необходим для создания подключения. Если этот адрес не будет найден, класс сгенерирует исключение.

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

    // поместить сообщение в буфер
    byte[] messageBytes = Encoding.UTF8.GetBytes(message);
    socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);
    

    Чтобы передать любое сообщение, оно должно быть представлено в виде последовательности октетов. При этом, само сообщение может быть текстом, изображением, аудио- или видеофайлом и т.д. Служба echo просто возвращает переданную ей последовательность октетов, независимо от того, какая информация в ней закодирована.

    Чтобы преобразовать текст в последовательность 8-битовых значений, можно использовать класс Encoding, который описан в пространстве System.Text. Этот класс использует метод GetBytes для преобразования строки, закодированной в системе UTF-8, в массив байтов. Кодировка UTF-8 используется в системе Windows Phone для кодирования символов.

    Создание метода SendMessageUDP

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

    Класс SocketAsyncEventArgs содержит свойство Completed, которое генерирует событие при завершении взаимодействия с сетью. При наступлении события можно выполнить код, который проверит результат выполнения сетевого запроса. Этот код необходимо связать со свойством Completed.

    public string SendMessageUDP(string message, string hostUrl, int portNumber)
    {
        // строка для хранения ответного сообщения
        string response = "";
    
        // создание аргумента для запроса
        SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
        // создание конечной точки подключения
        socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber,
            AddressFamily.InterNetwork);
    
        // поместить сообщение в буфер
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);
        socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);
    
        // подключить обработчик события завершения запроса
        socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
            }
            else 
            {
                response = e.SocketError.ToString();
            }
            // запустить все ожидающие потоки
            transferDoneFlag.Set();
        });
    
        ...
    }
    

    В этом коде блок программы связывается с событием Completed. В этом случае код обработчика события может получить доступ к локальным переменным в методе SendMessageUDP, в частности, к переменной response, которая будет содержать строку с сообщением об ошибке. Обработчик события Completed вызывается только когда будет завершена сетевая операция.

    Событие Completed использует ссылку на объект класса SocketAsyncEventArgs, который описывает состояние транзакции. Он устанавливает значение флага состояния SocketError, которое сообщает, успешно ли было передано сообщение. Если передача сообщения завершилась неудачей, в переменную response записывается сообщение об ошибке.

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

    В .NET асинхронные операции выполняются в отдельных потоках. Запускаемая в Windows Phone программа, по сути, является потоком, который становится активным потоком устройства. Программы могут запускать другие потоки для выполнения асинхронных операций, и иногда один поток должен ожидать завершения другого потока. Среда .NET предоставляет возможность взаимодействия потоков с помощью класса ManualResetEvent:

    static ManualResetEvent transferDoneFlag = new ManualResetEvent(false);
    

    Программа содержит переменную transferDoneFlag, которая может использоваться в качестве флага. Переменная объявлена как static, чтобы все экземпляры класса использовали один флаг, поскольку можно создать ограниченное количество экземпляров класса ManualResetEvent.

    Переменная transferDoneFlag может находиться в одном из двух состояний. Изначально значение флага равно false, т.е. флаг сброшен. Поток может ожидать завершения другого потока, чтобы установить этот флаг, т.е. изменить его значение на true, используя метод WaitOne. Это остановит текущий поток, пока другой поток не установит флаг, вызвав метод Set.

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

    Этот подход особенно полезен на платформе Windows Phone, поскольку в мобильных устройствах высока вероятность, что сетевой запрос завершится неудачей. При создании приложения, которое использует сетевые подключения, рекомендуется задавать тайм-аут и обрабатывать ситуацию, когда подключение недоступно. Кроме того, ожидание завершения других потоков позволяет экономить системные ресурсы.

    Фактически, в нашей программе будет использоваться главный поток, который создаётся при запуске программы, и фоновый поток, который запускается для выполнения сетевой операции. Если главный поток должен ожидать, пока флаг не будет установлен, перед этим флаг нужно сбросить, вызвав метод Reset, иначе программа не будет ожидать завершения потока, поскольку флаг может быть установлен перед вызовом метода WaitOne.

    После выполнения всех необходимых настроек, можно отправить сообщение удалённому компьютеру:

    // отправка сообщения
    hostSocket.SendToAsync(socketEventArgs);
    
    // ожидание события
    // работа будет продолжена после получения ответа или по истечению времени тайм-аута
    transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    

    Полная версия метода SendMessageUDP будет выглядеть следующим образом:

    static ManualResetEvent transferDoneFlag = new ManualResetEvent(false);
    const int MESSAGE_TIMEOUT_MSECS = 10000;
    
    public string SendMessageUDP(string message, string hostUrl, int portNumber)
    {
        string response = "Истекло время ожидания";
    
        SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
    
        socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber,
            AddressFamily.InterNetwork);
    
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);
        socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);
    
    
        socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
            }
            else
            {
                response = e.SocketError.ToString();
            }
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.SendToAsync(socketEventArgs);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    
        return response;
    }
    

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

    Отправка и получение сообщения

    Созданный метод можно использовать, указав текст передаваемого сообщения, имя узла и номер порта. Метод вернёт ответ, который мы можно проверить в программе:

    string sendResponse = SendMessageUDP(MessageTextBox.Text, HostTextBox.Text,
        ECHO_PORT);
    

    Если адрес узла окажется недоступным, то метод завершится неудачей. Однако, это единственный вариант, при котором метод завершится неудачей. Поскольку информация будет передана в виде дейтаграммы, программа не сможет узнать, получила ли удаленная система переданное сообщение, если она не отправит ответное сообщение.

    Для того чтобы получить ответ от службы echo после отправки сообщения нужно настроить другое подключение с удалённым узлом. Можно создать другой метод для отправки запроса на чтение:

    public string ReceiveMessageUDP(int portNumber, out string result)
    {
        string response = "Ошибка: тайм-аут запроса";
        string message ="";
    
        SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
        socketEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, portNumber);
    
        byte[] responseBytes = new byte[MAX_BUFFER_SIZE];
        socketEventArgs.SetBuffer(responseBytes, 0, MAX_BUFFER_SIZE);
    
        socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
                message = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
                message = message.Trim('\0');
            }
            else
            {
                response = e.SocketError.ToString();
            }
    
            transferDoneFlag.Set();
        });
    
        // сбросить флаг, он будет установлен после обработки запроса
        transferDoneFlag.Reset();
    
        // отправить запрос на чтение
        hostSocket.ReceiveFromAsync(socketEventArgs);
    
        // приостановить поток, пока не будет получен ответ или не истечёт время ожидания
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    
        // скопировать полученный текст в выходной параметр
        result = message;
        // вернуть результат запроса
        // если запрос выполнился успешно, возвращается пустая строка
        return response;  
    }
    

    Этот метод похож на метод SendMessageUDP. Он будет прослушивать указанный порт для получения ответа из сети. Однако, теперь адрес удалённого узла указывается с помощью класса IPEndPoint. Адрес конечной точки задаётся значением IPAddress.Any, что соответствует любому адресу, т.е. программа получит сообщение, отправленное на заданный порт с любого узла в сети. Если нужно получать сообщения только от одного адреса, здесь можно указать этот адрес.

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

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

    Если запрос успешно считывает сообщение, оно должно быть преобразовано из последовательности 8-битовых значений в тип данных, с которым будет работать программа. Поскольку программа работает с текстовыми сообщениями, для декодирования сообщения используется класс Encoding для получения строки в кодировке UTF-8. Последним действием обработчик событий устанавливает флаг для обеспечения синхронизации потоков.

    Созданный метод можно использовать так:

    string receiveResponse;
    string message;
    
    receiveResponse = ReceiveMessageUDP(ECHO_PORT, out message);
    

    Отправка дейтаграммы службе echo

    Теперь можно создать код, который отправит сообщение службе echo. Сначала нужно вызвать метод SendMessageUDP для отправки сообщения службе, и затем вызвать ReceiveMessageUDP для получения ответа.

    string sendResponse = SendMessageUDP(MessageTextBox.Text, HostTextBox.Text,
        ECHO_PORT);
    
    if (sendResponse.Length == 0)
    {
        string receiveResponse;
        string message;
    
        receiveResponse = ReceiveMessageUDP(ECHO_PORT, out message);
        if (receiveResponse.Length == 0)
        {
            ResponseTextBox.Text = message;
        }
        else
        {
            ErrorTextBox.Text = "Ошибка получения: " + receiveResponse;
        }
    }
    else
    {
        ErrorTextBox.Text = "Ошибка отправки: " + sendResponse;
    }
    

    Если метод SendMessageUDP возвращает пустую строку, это значит, что сообщение отправлено успешно, и программа может вызвать метод ReceiveMessageUDP, чтобы получить ответное сообщение от службы echo.

    Для проверки правильности работы программы необходима система, в которой есть служба echo. Такая служба есть в операционной системе Windows, но по умолчанию эта служба не устанавливается. Чтобы использовать службу echo, нужно в панели управления выбрать пункт Включение и отключение компонентов Windows и поставить галочку напротив пункта Простые службы TCPIP (такие как echo, datetime и т.п.). После этого можно либо перезагрузить компьютер, либо в оснастке Службы запустить службу Простые службы TCP/IP. Описанная последовательность действий работает в Windows 7, а в других версиях Windows может отличаться некоторыми деталями.

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

    17.3. Создание подключения по протоколу TCP

    Проблема использования протокола UDP заключается в том, что система, которая передаёт информацию по этому протоколу, не знает, принял ли эту информацию получатель. Если необходимо создать надёжное подключение, нужно использовать протокол TCP.

    Протокол TCP позволяет создавать сеансы подключения между двумя устройствами. Сообщения создаются и передаются точно так же, как и при использовании протокола UDP, но каждое сообщение является частью сеанса. Сеанс будет продолжаться, пока одна из сторон не решит его прекратить. Ответ, который передаётся после приёма сообщения, позволяет программам определить, успешно ли было передано сообщение. В конце сеанса одна из сторон закрывает подключение.

    Рассмотрим процесс создания метода для запроса веб-страницы:

    private string RequestWebPage(string url, string page, out string pageContent)
    

    Этот метод принимает в качестве параметров адрес веб-сервера, имя запрашиваемой страницы, и в третий параметр заносится содержимое веб-страницы. Метод возвращает пустую строку, если запрос был выполнен успешно, или описание ошибки, если запрос завершился неудачей.

    В Интернете используется протокол передачи гипертекста HTTP. Этот протокол определяет язык, используемый браузером для запроса страниц содержимого веб-сервера. Например, чтобы получить с сервера веб-страницу, браузер отправляет команду GET и адрес веб-страницы на сервере.

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

  • Создать TCP-подключение к веб-серверу, используя порт 80, который зарезервирован для HTTP-запросов.
  • Отправить веб-серверу запрос GET, в котором указать нужную страницу.
  • Получить содержимое запрошенной веб-страницы. Если страница большая, может потребоваться несколько операций чтения.
  • Закрыть подключение.
  • Создание TCP-подключения

    Подключение по протоколу TCP создаётся аналогично рассмотренному ранее примеру.

    private string ConnectTCP(string host, int port)
    {
        string response = "Истекло время ожидания";
    
        DnsEndPoint hostEntry = new DnsEndPoint(host, port);
    
        hostSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
            ProtocolType.Tcp);
    
        SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();
        socketEventArgs.RemoteEndPoint = hostEntry;
    
        socketEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
            }
            else
            {
                // получить результат запроса
                response = e.SocketError.ToString();
                // очистить параметры подключения
                hostSocket = null;
            }
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.ConnectAsync(socketEventArgs);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
        return response;
    }
    

    В отличие от предыдущего примера, в этом методе вызывается метод ConnectAsync класса Socket. Если этот метод выполнится успешно, то он создаст настроенное TCP-подключение к удаленной системе, с помощью которого можно будет отправлять и получать сообщения, пока подключение не будет закрыто. Однако, подключение создаётся для передачи только одной веб-страницы. Если пользователь перейдёт на другую страницу, необходимо будет создать новое TCP-подключение к другой странице.

    Отправка сообщения через TCP-подключение

    Далее нужно сформировать текст сообщения и отправить его через созданное подключение:

    private string SendMessageTCP(string message)
    {
        if (hostSocket == null)
            return "Подключение не установлено";
    
        string response = "Тайм-аут подключения";
      
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
    
        // использование параметров конечной точки для настройки передачи сообщения
        socketEventArg.RemoteEndPoint = hostSocket.RemoteEndPoint;
    
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);
        socketEventArg.SetBuffer(messageBytes, 0, messageBytes.Length);
    
        socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
            }
            else
            {
                response = e.SocketError.ToString();
                CloseTCP();
            }
    
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.SendAsync(socketEventArg);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    
        return response;
    }
    

    Этот код почти такой же, как и для отправки сообщения по UDP-подключению. Перед передачей сообщения проверяется, установлено ли подключение.

    Если в процессе передачи произошла ошибка, подключение закрывается методом CloseTCP:

    void CloseTCP()
    {
        if (hostSocket != null)
        {
            hostSocket.Close();
            hostSocket = null;
        }
    }
    

    Получение сообщения через TCP-подключение

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

    private string ReceiveMessageTCP(out string message)
    {
        message = "";
    
        if (hostSocket == null)
        {
            return "Подключение не установлено";
        }
    
        string response = "Тайм-аут подключения";
        string result = "";
    
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
        socketEventArg.RemoteEndPoint = hostSocket.RemoteEndPoint;
    
        // создания буфера для получения сообщения
        byte[] responseBytes = new Byte[MAX_BUFFER_SIZE];
        socketEventArg.SetBuffer(responseBytes, 0, MAX_BUFFER_SIZE);
    
        socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>
            (delegate(object s, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                response = "";
                if (e.BytesTransferred > 0)
                {
                    result = Encoding.UTF8.GetString(e.Buffer, e.Offset,
                        e.BytesTransferred);
                    result = result.Trim('\0');
                }
            }
            else
            {
                response = e.SocketError.ToString();
            }
            transferDoneFlag.Set();
        });
    
        transferDoneFlag.Reset();
    
        hostSocket.ReceiveAsync(socketEventArg);
    
        transferDoneFlag.WaitOne(MESSAGE_TIMEOUT_MSECS);
    
        message = result;
    
        return response;
    }
    

    Эта версия метода проверяет, передал ли сервер какую-либо информацию, и если да, то декодирует полученную информацию и возвращает её в виде строки.

    Создание запроса

    Теперь можно использовать созданные методы для построения запроса веб-страницы. Метод отправит запрос и получит содержимое веб-страницы. Об окончании передачи свидетельствует получение пустого результата от метода ReceiveMessageTCP.

    private string RequestWebPage(string url, string page, out string pageContent)
    {
        pageContent = "";
    
        // создание подключения
        string response = ConnectTCP(url, 80);
    
        if (response != "")
        {
            return response;
        }
    
        // отправка запроса страницы с помощью HTTP-команды GET
        response = SendMessageTCP("GET " + page + " HTTP/1.1\r\nHost: " + url +
            "\r\nConnection: Close\r\n\r\n");
    
        if (response != "")
        {
            CloseTCP();
            return response;
        }
    
        string wholePage = "";
        string fetchText;
    
        // повторение получения пакетов от сервера
        // до тех пор, пока метод не вернёт пустую строку
        do
        {  
            response = ReceiveMessageTCP(out fetchText);
    
            if (response != "")
            {
                CloseTCP();
                return response;
            }
    
            wholePage += fetchText;
        }
        while (fetchText != "");
    
        pageContent = wholePage;
    
        CloseTCP();
    
        return response;
    }
    

    Этот метод возвращает полный текст запрошенной веб-страницы. Если какой-либо метод вернёт сообщение об ошибке, метод RequestWebPage закроет TCP-подключение и завершит работу. Код метода CloseTCP для закрытия подключения выглядит так:

    void CloseTCP()
    {
        if (hostSocket != null)
        {
            hostSocket.Close();
            hostSocket = null;
        }
    }
    

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

    17.4. Подключение к сетевому ресурсу

    Часто программам приходится получать информацию из различных сетевых ресурсов. Обычно это довольно утомительный процесс, но его можно упростить, используя класс WebClient.

    Класс WebClient является частью сетевой библиотеки.NET, которую можно использовать как в программах для Windows Phone, так и в программах для компьютеров под управлением Windows. Можно использовать класс для взаимодействия с удалённым сервером по протоколу HTTP.

    Создадим простую программу, которая при нажатии на кнопку выведет на экран код веб-страницы, адрес которой вводит пользователь в текстовое поле. Фактически, аналогичное действие выполняют веб-браузеры, только последние обрабатывают код страницы и выводят её в том виде, в каком её ожидает увидеть пользователь.

    Сначала нужно объявить переменную типа WebClient в классе MainPage. Эта переменная будет использоваться для управления веб-запросами, которые будут использоваться в программе. Экземпляр класса WebClient создаётся в конструкторе страницы:

    WebClient client;
    
    public MainPage()
    {
        InitializeComponent();
    
        client = new WebClient();
        client.DownloadStringCompleted +=
            new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    }
    

    В конструкторе также создаётся обработчик события DownloadStringCompleted, который выводит код веб-страницы в визуальный элемент pageTextBlock, если загрузка веб-страницы выполнилась успешно:

    void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            pageTextBlock.Text = e.Result;
        }
    }
    

    Остаётся добавить в программу обработчик нажатия на кнопку, в котором будет загружаться веб-страница, адрес которой ввёл пользователь:

    private void loadButton_Click(object sender, RoutedEventArgs e)
    {
        client.DownloadStringAsync(new Uri(urlTextBox.Text));
    }
    

    При нажатии на кнопку выполняются следующие действия:

  • Строка, введённая в текстовое поле urlTextBox, используется для создания URI требуемой веб-страницы.
  • Вызывается метод DownloadStringAsync класса WebClient, который запускает новую веб-транзакцию, после чего завершает свою работу и передаёт управление программе.
  • Через какое-то время класс WebClient считает содержимое запрошенной веб-страницы и сгенерирует событие DownloadStringCompleted, которое сигнализирует об окончании приёма сообщения.
  • Событие вызовет метод-обработчик client_DownloadStringCompleted, который проверит, возникли ли при получении данных ошибки, и если нет — выводит полученный код страницы в визуальный элемент pageTextBlock.
  • 17.5. Чтение данных из XML-потока с помощью LINQ

    Приложения могут взаимодействовать с различными информационными службами, например c Twitter, используя протокол передачи состояния представления REST. REST использует URL для указания необходимого действия. Например, если в браузере указать адрес http://twitter.com/statuses/user_timeline/user_name.xml, будет выдан XML-документ, который содержит новые сообщения пользователя "user_name". Если заменить в этой строке "user_name" на имя другого пользователя Twitter, будет выдана информация о сообщениях этого пользователя.

    Можно изменить программу, созданную в предыдущем примере, так, чтобы она создавала запрос для Twitter и использовала его для получения информации об указанном пользователе. Достаточно изменить имя элемента текстового поля на nameTextBox (но можно этого не делать — имя элемента несёт только смысловую нагрузку) и изменить код метода loadButton_Click:

    private void loadButton_Click(object sender, RoutedEventArgs e)
    {
        string url = "http://twitter.com/statuses/user_timeline/" + nameTextBox.Text
            + ".xml";
    
        client.DownloadStringAsync(new Uri(url));
    }
    

    Twitter предоставляет информацию о состоянии в формате XML. XML представляет информацию в структурированном виде, и её можно обработать и вывести на экран в удобном виде. Для обработки XML можно использовать LINQ.

    XML-документ похож на базу данных, в которой содержатся элементы и атрибутами. Кроме инструментов для создания запросов к базе данных и получения из них структурированной информации, LINQ также содержит инструменты для создания объектов на основе данных XML.

    Информация, полученная от Twitter, является структурированным XML-документом, который содержит элементы и атрибуты, которые описывают содержание сообщений Twitter:

    <?xml version="1.0" encoding="UTF-8"?>
    <statuses type="array">
      <status>
        <created_at>Tue Oct 11 11:57:37 +0000 2011</created_at>
        <id>27131245083</id>
        <text>
            Had 12,000 hits on www.csharpcourse.com for my C# Yellow Book. No idea why.
        </text>
        <source>web</source>
        <truncated>false</truncated>
        <in_reply_to_status_id></in_reply_to_status_id>
        <in_reply_to_user_id></in_reply_to_user_id>
        <favorited>false</favorited>
        <in_reply_to_screen_name></in_reply_to_screen_name>
        <retweet_count>0</retweet_count>
        <retweeted>false</retweeted>
        <user>
          <id>2479801</id>
          <name>Rob Miles</name>
          <screen_name>robmiles</screen_name>
          <location>Hull, UK</location>
          <description>A Computer Science lecturer and Microsoft MVP.</description>
          <profile_image_url>
              http://a3.twimg.com/prof_im/157/me2_normal.jpg
          </profile_image_url>
          <url>http://www.robmiles.com</url>
          <protected>false</protected>
          <friends_count>21</friends_count>
          <created_at>Tue Mar 27 12:35:28 +0000 2007</created_at>
          <favourites_count>0</favourites_count>
          <utc_offset>0</utc_offset>
          <time_zone>London</time_zone>
          <profile_background_image_url>
              http://s.twimg.com/a/1286/images/themes/theme1/bg.png
          </profile_background_image_url>
          <notifications>false</notifications>
          <geo_enabled>true</geo_enabled>
          <verified>false</verified>
          <following>true</following>
          <statuses_count>731</statuses_count>
          <lang>en</lang>
          <contributors_enabled>false</contributors_enabled>
          <follow_request_sent>false</follow_request_sent>
          <listed_count>61</listed_count>
          <show_all_inline_media>false</show_all_inline_media>
        </user>
        <geo/>
        <coordinates/>
        <place/>
        <contributors/>
      </status>
    </statuses>
    

    В этой XML-структуре представлена небольшая часть информации, которую выдаёт Twitter в виде XML-файла. В действительности, этот файл бывает гораздо большего объёма.

    С помощью LINQ можно создать запрос к базе данных, который возвратит коллекцию найденных объектов, а можно создать конструкцию, которая представляет содержание XML-документа:

    XElement TwitterElement = XElement.Parse(twitterText);
    

    Класс XElement является частью LINQ и представляет собой элемент XML. Строка twitterText содержит информацию, полученную от Twitter с помощью класса WebClient. Экземпляр класса XElement позволяет сохранить эту информацию в виде структурированного документа. Необходимо преобразовать эту информацию в форму, удобную для вывода на экран.

    Как было рассмотрено в предыдущих лекциях, можно создать классы C# для хранения информации об объектах и применить привязку данных для связи этих классов с визуальными элементами Silverlight. В эти классы можно загружать данные как из базы данных, так и из XML-файла.

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

    public class TwitterPost
    {
        public string PostText { get; set; }
    
        public string DatePosted { get; set; }
    
        public string UserImage { get; set; }
    }
    

    Эти свойства будут содержать значения, полученные от Twitter, и пользователь не будет их изменять. Обратите внимание, что свойство UserImage имеет строковый тип и содержит URI изображения, которое будет загружаться из Интернета.

    Теперь необходимо использовать LINQ для получения коллекции объектов из элемента XElement, который содержит всю информацию, полученную от Twitter.

    var postList = from tweet in twitterElements.Descendants("status")
                    select new TwitterPost
                    {
                        UserImage =
                            tweet.Element("user").Element("profile_image_url").Value,
                        PostText = tweet.Element("text").Value,
                        DatePosted = tweet.Element("created_at").Value
                    };
    

    В этом коде в секции from выполняется перебор элементов с именем <status>. Эти элементы программа обрабатывает по очереди. В секции select для каждого элемента создаётся экземпляр класса TwitterPost, и его свойствам присваиваются значения указанных XML-элементов, вложенных в элемент <status>. Метод Element возвращает содержимое XML-элемента с заданным именем.

    Результат запроса заносится в переменную postList, которая фактически будет являться коллекцией объектов класса TwitterPost. Если применить запрос к XML-документу, который был представлен выше, то запрос создаст список из одного элемента. Однако, в общем случае полученный список будет содержать несколько объектов.

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

    (рис 17.2) Внешний вид сообщения, полученного из сети Twitter

    Это изображение можно разделить на три блока: слева будет размещена фотография пользователя, в верхней части — дата создания сообщения, по центру — текст сообщения (рис. 17.3).

    (рис 17.3) Структура сообщения, полученного из сети Twitter

    В Silverlight эту разметку можно выполнить, используя элементы-контейнеры StackPanel:

    <StackPanel Orientation="Horizontal" Height="132">
      <Image Source="{Binding UserImage}" Height="73" Width="73" VerticalAlignment="Top"
          Margin="0,10,8,0"/>
      <StackPanel Width="370">
        <TextBlock Text="{Binding DatePosted}" Foreground="#FFC8AB14" FontSize="22" />
        <TextBlock Text="{Binding PostText}" TextWrapping="Wrap" FontSize="24" />
      </StackPanel>
    </StackPanel>
    

    Класс StackPanel позволяет автоматически располагать и выравнивать элементы на странице программы. В этом коде используется привязка данных, которая связывает элементы со свойствами класса TwitterPost.

    Полученную разметку необходимо указать в качестве шаблона разметки элементов списка ListBox:

    <ListBox Height="442" HorizontalAlignment="Left" Name="tweetsListBox"
        VerticalAlignment="Top" Width="468">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Horizontal" Height="132">
            <Image Source="{Binding UserImage}" Height="73" Width="73"
                VerticalAlignment="Top" Margin="0,10,8,0"/>
            <StackPanel Width="370">
              <TextBlock Text="{Binding DatePosted}" Foreground="#FFC8AB14"
                  FontSize="22" />
              <TextBlock Text="{Binding PostText}" TextWrapping="Wrap" FontSize="24" />
            </StackPanel>
          </StackPanel>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
    

    После этого остаётся только указать переменную postList, в которой хранятся сообщения из Twitter, в качестве источника данных для списка ListBox:

    tweetsListBox.ItemsSource = postList;
    

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

    17.6. Взаимодействие приложений с сетевыми службами

    Приложения для Windows Phone могут взаимодействовать с веб-серверами, используя протоколы Интернета. Также можно создавать сетевые службы и использовать их в устройствах Windows Phone.

    Службы Windows Communication Foundation, или WCF, предоставляют возможность процессам взаимодействовать друг с другом по сети. Процессы могут обмениваться сообщениями по сети, используя различные сетевые среды. Службы работают на сервере, к которому могут получать доступ клиентские программы. В нашем случае клиентская программа будет работать в Windows Phone.

    На рис. 17.4 представлен принцип взаимодействия программы с сетевой службой. Программное обеспечение телефона взаимодействует с прокси-объектом, который содержит методы, предоставляемые службой. Вызов метода прокси-объекта приводит к созданию и оправке сетевого сообщения службе на сервере. При получении сообщения сервер вызывает код метода серверного объекта. Результат, возвращаемый методом, упаковывается в другое сетевое сообщение, которое отправляется клиенту, который пересылает возвращаемое значение программе, которая вызвала этот метод. При этом, система выполняет всю работу по созданию и передаче сообщений. Необходимо только создать серверные методы и вызывать их на клиентской системе.

    (рис 17.4) Взаимодействие программы для Windows Phone с сетевой службой

    Также служба предоставляет описание её методов. Это описание использует среда разработки (в нашем случае — Visual Studio) для создания прокси-объекта в клиентской программе. Это упрощает создание клиентского приложения.

    Рассмотрим пример создания службы Новость дня, которая будет возвращать одну из новостей по запросу клиента. Клиент должен передать в качестве параметра цифру 1, 2 или 3, которая, для удобства, будет обозначать категорию новостей.

    Приложение будет состоять из двух частей: программа на сервере, которая экспортирует службу, и программа на клиенте, которая её использует. Сервер будет службой WCF, а клиент — приложением для Windows Phone, которое подключается к службе и запрашивает новость.

    Для создания службы WCF нужно при создании проекта Visual Studio выбрать шаблон Приложение службы WCF в группе WCF. Службу можно тестировать в среде Visual Studio. После тестирования файлы проекта можно будет загрузить на рабочий сервер. Обратите внимание, что службу можно создать в Visual Web Developer 2010 Express.

    Службы и клиенты

    Службы и клиенты похожи на методы. При создании сервера создаётся метод, который выполняет какие-то действия и возвращает результат. Клиента вызывает метод сервера и обрабатывает результат. При этом, служба WCF выполняет все необходимые действия по упаковке параметров и возвращаемого значения метода в сетевые сообщения. При создании проекта службы в файле .cs создаётся интерфейс, который определяет предоставляемый службой метод:

    [ServiceContract]
    public interface INewsOfTheDayService
    {
        [OperationContract]
        string GetData(int value);
    }
    

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

    После создания интерфейса можно создать код метода, который будет предоставлять служба. Заготовка метода создаётся по умолчанию в файле службы с расширением .svc, с которым связан одноимённый файл .cs. Атрибуты [ServiceContract] и [OperationContract] предоставляют средства для создания описания службы при построении проекта, которые будут использоваться клиентами для взаимодействия с этими службами.

    public class NewsOfTheDayService : INewsOfTheDayService
    {
        public string GetData(int value)
        {
            string result;
            switch (value)
            {
                // категория "Компьютеры"
                case 1:
                    result = "Intel Xeon E5-2600: "облачные" процессоры ставят рекорды";
                    break;
      
                // категория "Программное обеспечение"
                case 2:
                    result =
                        "Вышла в свет ОС Windows 8 Consumer Preview";
                    break;
    
                // категория "Мобильные устройства"
                case 3:
                    result =
                        "Nokia выпускает новые телефоны с ОС Windows Phone 7.5 Mango";
                    break;
    
                // если указан неверный номер категории
                default:
                    result = "Выбрана несуществующая категория";
                    break;
            }
            return result;
        }
    }
    

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

    Если попытаться запустить проект, нажав клавишу F5 (или другим способом), Visual Studio создаст веб-сервер, на котором будет запущена служба, и откроется окно программы Тестовый клиент WCF, в котором можно вызвать метод службы с различными параметрами и просмотреть результаты, которые метод возвращает (рис. 17.5).

    (рис 17.5) . Окно программы Тестовый клиент WCF

    Также можно просмотреть описание службы в браузере, указав URL службы, который можно найти в окне программы Тестовый клиент WCF (рис. 17.6).

    (рис 17.6) Страница службы, открытая в браузере

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

    Создание клиентского приложения

    Для того чтобы создать приложение Windows Phone, которое будет работать с созданной службой, нужно создать новый проект. Клиентское приложение должно подключаться к службе NewsOfTheDayService. Ссылка на службу добавляется в проект так же, как и ссылка на ресурс, только в контекстном меню в окне обозревателя решений нужно выбрать пункт Добавить ссылку на службу…. Откроется диалоговое окно Добавить ссылку на службу, в котором нужно указать URL запущенной службы. Visual Studio найдёт описание службы и все предоставляемые ею методы. Также можно изменить пространство имён по умолчанию, которое будет использоваться в создаваемом приложении, на NewsOfTheDayService.

    После добавления ссылки на службу её можно использовать в решении. Теперь необходимо создать прокси-объект, который будет использоваться для вызова методов службы. Лучше сделать это в конструкторе главной страницы приложения:

    NewsOfTheDayService.NewsOfTheDayServiceClient newsclient;
    
    // Конструктор
    public MainPage()
    {
        InitializeComponent();
    
        newsclient = new NewsOfTheDayService.NewsOfTheDayServiceClient();
    
        newsclient.GetDataCompleted +=
            new EventHandler<NewsOfTheDayService.GetDataCompletedEventArgs>
            (newsclient_GetDataCompleted);
    }
    

    Этот код создаёт прокси-объект для службы и связывает метод-обработчик с событием GetDataCompleted, которое происходит при получении ответа от службы. Вызов метода службы выполняется асинхронно. Метод-обработчик должен выводить на экран полученную новость:

    void newsclient_GetDataCompleted(object sender,
        NewsOfTheDayService.GetDataCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            newsTextBlock.Text = e.Result;
        }
    }
    

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

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

    private void getNewsButton_Click(object sender, RoutedEventArgs e)
    {
        int category = 0;
    
        if (int.TryParse(newsTextBox.Text, out category))
        {
            newsclient.GetDataAsync(category);
        }
    }
    

    Этот метод получает номер категории новости и передаёт его в качестве параметра при вызове метода GetDataAsync. Если текст в поле newsTextBox не может быть преобразован в целое число, метод TryParse вернёт значение false.

    Если служба будет недоступна, при вызове метода GetDataAsync будет сгенерировано исключение. Это может произойти, если во время выполнения программы в телефоне сигнал сети не будет доступен. В приложениях для Windows Phone необходимо обрабатывать исключения в блоке trycatch и выдавать пользователю сообщение о недоступности сети.

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

    Краткие итоги

  • Компьютерные сети предоставляют возможность отправки сообщений из одной системы в другую. Устройства могут объединяться в локальную сеть. В локальной сети может быть маршрутизатор, который отправляет пакеты из локальной сети удалённым узлах. Маршрутизаторы являются устройствами передачи данных, которые позволяют создавать межсетевые подключения.
  • Полный адрес системы в сети содержит адрес этой станции в её локальной сети и адрес этой сети в Интернете.
  • Данные могут передаваться между системами в виде отдельных дейтаграмм (UDP) или как часть подключения между двумя системами (TCP).
  • К различным службам системы можно обращаться, указав номер порта, на котором работает требуемая служба. Некоторые номера портов зарезервированы и являются общепринятыми, например веб-сервер обычно работает на порту с номером 80.
  • Приложения Windows Phone могут создавать и использовать объекты, которые представляют подключение к сетевой службе. Эти объекты работают в асинхронном режиме, при котором сетевые операции выполняются в отдельном потоке. Результаты асинхронного запроса обрабатываются при наступлении события окончания выполнения запроса без необходимости ожидания завершения запроса в программе.
  • Сокеты являются абстракцией сетевого подключения.
  • Некоторые сетевые вызовы могут возвращать структурированные данные, которые содержат XML-описание содержимого.
  • Библиотеки LINQ могут создавать динамические объекты для представления структурированных данных и их использования в программе.
  • Технология Windows Communication Foundation (WCF) предоставляет возможность настроить сервер так, чтобы можно было вызывать их методы в клиентских приложениях.
  • Клиентское приложение может прочитать описание службы, которое предоставляет сервер WCF, и использовать его для создания прокси-объекта, который содержит методы, предоставляемые службой. При вызове в клиентском коде метод прокси-объекта запускается сетевая операция, которая передаёт параметры метода на сервер. Затем вызывается метод сервера, и сетевая операция возвращает результат коду, который вызвал серверный метод.
  • Ключевые термины

    IP-адрес — уникальный сетевой адрес узла в компьютерной сети, построенной по протоколу IP.

    Windows Communication Foundation — технология компании Microsoft, основанная на .NET Framework, которая используется для обмена данными между приложениями, находящихся обычно на разных устройствах.

    Асинхронное подключение — тип подключения, которое работает паралллельно с основной программой и обычно обрабатывается в отдельном потоке.

    Брандмауэр — комплекс аппаратных или программных средств, осуществляющий контроль и фильтрацию проходящих через него сетевых пакетов в соответствии с заданными правилами.

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

    Конечная точка подключения — удалённый узел, к которому подключается программа на текущем узле.

    Маршрутизация — процесс определения маршрута следования информации в сетях связи.

    Октетами — наименьшая единица информации, которая может быть передана по сети, равняется 8 битам.

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

    Сетевой маршрутизатор — сетевое устройство, пересылающее пакеты данных между различными сегментами сети.

    Сетевой протокол — набор соглашений, которые определяют, как происходит обмен данными между устройствами в сети.

    Сетевые сигналы — носитель информации, которая передаётся через компьютеную сеть.

    Система доменных имён — компьютерная распределённая система для получения информации о доменах, позволяет связать имя узла с его IP-адресом.

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

    Вопросы

  • Какие типы подключений к сети доступны в Windows Phone?
  • Какие образом данные передаются по сети?
  • Для чего используется система доменных имён?
  • Каким образом несколько взаимодействующих с сетью служб могут работать на одном сервере только с теми сообщениями, которые для них предназначены?
  • В чём отличие сеансов подключений от дейтаграмм?
  • Для чего используется класс Socket?
  • Как в программе можно создать подключение по протоколу UDP?
  • Как в программе можно создать подключение по протоколу TCP?
  • Для чего используется класс WebClient?
  • Как получить необходимые данные из XML-структуры с помощью LINQ?
  • Для чего предназначена технология Windows Communication Foundation?
  • Как создать службу WCF?
  • Как создать приложение для Windows Phone для использования методов службы WCF?
  • Упражнения

    Для выполнения упражнений необходимо работающее сетевое подключение, а также среда Visual Studio для создания службы WCF.

    Упражнение 1. Создание службы TimeTracker

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

    Создание службы

  • Откройте Visual Studio.
  • Выберите в главном меню пункт Файл -> Создать проект. Откроется диалоговое окно Создать проект.
  • В списке Установленные шаблоны выберите в группе WCF шаблон Приложение службы WCF.
  • Назовите проект TimeTrackerService и нажмите OK, чтобы создать проект. Visual Studio создаст новый проект и откроет файл Service1.svc.cs.
  • Задайте службе и её интерфейсу более удобные имена. Для этого выберите в обозревателе решений файл IService1.cs, нажмите F2 и введите новое имя ITimeTrackerService.cs. При нажатии клавиши Enter Visual Studio предложит переименовать интерфейс и все ссылки на него, чтобы его имя соответствовало новому имени файла. Нажмите OK для подтверждения.
  • Аналогичным образом переименуйте файл службы от Service1.svc в TimeTrackerService.svc.
  • Откройте файл TimeTrackerService.svc.cs и щёлкните правой кнопкой по названию класса Service1. В контекстном меню выберите пункт Рефакторинг -> Переименовать…, укажите в открывшемся окне новое имя класса TimeTrackerService и подтвердите переименование.
  • Создание интерфейсов методов службы

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

  • Откройте файл ITimeTrackerService.cs. Он содержит интерфейс, который описывает методы, предоставляемые службой.
  • Удалите весь код внутри интерфейса и добавьте следующее описание методов:
    [OperationContract]
    void SaveSession(string employeeName, string companyName, int startHour, int startMin,
        int endHour, int endMin);
    
    [OperationContract]
    string GetSessionList();
    
  • Удалите описание класса CompositeType.
  • Создание методов службы

  • Откройте файл TimeTrackerService.svc.cs.
  • Удалите всё содержимое класса TimeTrackerService и добавьте в него следующий код:
    class Session
    {
        public string EmployeeName { get; set; }
        public string CompanyName { get; set; }
        public int StartHour { get; set; }
        public int StartMin { get; set; }
        public int EndHour { get; set; }
        public int EndMin { get; set; }
    
        public override string ToString()
        {
            return EmployeeName + " " + CompanyName;
        }
    }
    
    static List<Session> sessions = new List<Session>();
    
    Этот класс будет хранить информацию о каждой встрече, а также содержит список встреч, который будет формироваться во время работы службы. В реальном приложении этот список должен заполняться информацией из базы данных.
  • Добавьте в класс TimeTrackerService следующие методы:
    public void SaveSession(string employeeName, string companyName,
        int startHour, int startMin, int endHour, int endMin)
    {
        sessions.Add(new Session
        {
            EmployeeName = employeeName,
            CompanyName = companyName,
            StartHour = startHour,
            StartMin = startMin,
            EndHour = endHour,
            EndMin = endMin
        });
    }
    
    public string GetSessionList()
    {
        StringBuilder result = new StringBuilder();
    
        for (int i = 0; i < sessions.Count; i++)
        {
            result.AppendLine(sessions[i].ToString());
        }
        return result.ToString();
    }
    
  • Метод SaveSession сохраняет информацию о сеансе на сервере. Метод GetSessionList возвращает сохранённую информацию о встречах в виде многострочного текста.

    Развёртывание службы

  • Выберите в обозревателе решений файл TimeTrackerService.svc и нажмите F5 для запуска проекта. После построения решения откроется окно тестового клиента WCF.
  • Дважды щёлкните мышью по методу SaveSession(). Введите следующие значения параметров метода:
  • employeeNameTest Employee
  • companyNameTest Company
  • startHour — 1
  • startMin — 2
  • endHour —3
  • endMin — 4
  • Нажмите на кнопку Вызвать, чтобы вызвать метод службы с указанными параметрами. Метод вернёт пустое значение.
  • Дважды щёлкните мышью по методу GetSessionList() и нажмите на кнопку Вызвать. Метод вернёт строку, соответствующую встрече, добавленной в систему при вызове метода SaveSession.
  • Нажмите клавиши Shift + F5 для остановки службы.
  • В обозревателе решений щёлкните правой кнопкой мыши по файлу TimeTrackerService.svc и выберите в контекстом меню пункт Просмотр в обозревателе. Обратите внимание на URL службы в адресной строке браузера — это значение понадобится для подключения к службе клиента:
    http://localhost:30975/TimeTrackerService.svc
    
    В вашем случае номер порта может отличаться, поскольку Visual Studio генерирует его автоматически.
  • Закройте браузер и запустите службу в Visual Studio ещё раз. Запущенная служба будет использоваться в следующем упражнении.
  • Упражнение 2. Создание клиента службы

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

    Подключение клиентского приложения к службе

  • Убедитесь в том, что созданная в предыдущем упражнении служба запущена.
  • Запустите другую копию Visual Studio и откройте в ней проект TimeTrackerClient в папке Lab6 TimeTrackerClient. В текстовые поля пользователь может ввести имя сотрудника, название компании, с которой проводится встреча, а также время начала и окончания встречи. При нажатии на кнопку сохранить, программа должна сохранить информацию о встрече на сервере, и при нажатии на кнопку список встреч — вывести информацио о встречах, полученную от службы.
  • В обозревателе решений щёлкните правой кнопкой мыши по элементу Ссылки и выберите пункт Добавить ссылку на службу….
  • В открывшемся диалоговом окне Добавить ссылку на службу в поле Адрес введите URL службы, полученный в предыдущем упражнении в результате запуска службы, и нажмите кнопку Перейти. Visual Studio загрузит описание службы с сервера и выведет на экран информацию о службе.
  • Откройте описание службы, щёлкнув по стрелке слева от её имени, и выберите имя интерфейса ITimeTrackerService. В поле операции будут отображены имена двух созданных методов службы.
  • Измените пространство имён на TimeTrackerService и нажмите кнопку OK. Visual Studio добавит в проект ссылку на службу.
  • Щёлкните правой кнопкой мыши по созданной ссылке на службу TimeTrackerService и выберите пункт Настроить ссылку на службу….
  • В открывшемся диалоговом окне снимите галочку Повторно использовать типы в сборках, на которые есть ссылки и нажмите OK.
  • Использование метода службы SaveSession в приложении

  • Откройте файл MainPage.xaml.cs.
  • Добавьте в начало объявления класса следующую строку:
    private TimeTrackerService.TimeTrackerServiceClient timeTracker;
    
  • Добавьте в конструктор класса после вызова метода InitializeComponent следующий код для создания связанного со службой объекта и обработчиков событий службы (для создания обработчиков событий можно использовать Intellisense — в этом случае автоматически будет создана большая часть кода):
    timeTracker = new TimeTrackerService.TimeTrackerServiceClient();
    
    timeTracker.SaveSessionCompleted +=
        new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(
        timeTracker_SaveSessionCompleted);
    
    timeTracker.GetSessionListCompleted +=
        new EventHandler<TimeTrackerService.GetSessionListCompletedEventArgs>(
        timeTracker_GetSessionListCompleted);
    
  • Добавьте код метода saveButton_Click для асинхронного вызова метода службы и передачи введённых пользователем значений:
    private void saveButton_Click(object sender, RoutedEventArgs e)
    {
        int startHour, startMin, endHour, endMin;
    
        if (int.TryParse(startHourTextBox.Text, out startHour) 
            int.TryParse(startMinTextBox.Text, out startMin) 
            int.TryParse(endHourTextBox.Text, out endHour) 
            int.TryParse(endMinTextBox.Text, out endMin))
        {
            timeTracker.SaveSessionAsync(employeeNameTextBox.Text,
                companyNameTextBox.Text, startHour, startMin, endHour, endMin);
        }
    }
    
  • Добавьте код метода-обработчика timeTracker_SaveSessionCompleted, который будет вызываться при завершении выполнения метода службы:
    void timeTracker_SaveSessionCompleted(object sender,
        System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            employeeNameTextBox.Text = "Введите имя сотрудника...";
            companyNameTextBox.Text = "Введите название компании...";
        }
    }
  • Если метод службы выполнится успешно, то текст в текстовых полях приложения будет изменён на первоначальный.

    Тестирование метода SaveSession

  • Нажмите F5, чтобы запустить программу в эмуляторе.
  • Введите в текстовые поля информацию о встрече и нажмите на кнопку сохранить. Текст в текстовых полях должен измениться на первоначальный.
  • Таким же образом добавьте информацию о второй встрече.
  • Остановите выполнение программы.
  • Использование метода службы GetSessionList

  • Добавьте в приложение код метода listButton_click для вызова метода службы:
    private void listButton_Click(object sender, RoutedEventArgs e)
    {
        timeTracker.GetSessionListAsync();
    }
    
  • Добавьте в приложение код метода timeTracker_GetSessionListCompleted для вывода на экран результата выполнения метода службы:
    void timeTracker_GetSessionListCompleted(object sender,
        TimeTrackerService.GetSessionListCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            sessionsTextBlock.Text = e.Result;
        }
    }
    
  • Запустите программу и нажмите кнопку список встреч. На экран будет выведена информация о двух введённых встречах.
  • Вернуться к учебному плану