Презентацию к данной лекции Вы можете скачать здесь.
Все устройства Windows Phone могут использовать сетевые подключения к сети оператора сотовой связи и к сетям Wi-Fi. Эти две сетевых технологии являются взаимозаменяемыми. Однако, оба типа сетей не всегда бывают доступными одновременно.
Если при подключении к одной из сетей мобильной связи или Wi-Fi сигнал сети пропадёт, то это подключение прервётся. Приложения должны корректно работать в подобной ситуации — по крайней мере, выводить на экран сообщение вида: "Используемое сетевое подключение временно недоступно. Попробуйте повторить попытку позже". Предпочтительнее, если программа сохранит данные в устройстве, чтобы отправить их, когда подключение к сети будет доступно.
В эмуляторе Windows Phone доступны такие же сетевые возможности, как и в реальном устройстве. Эмулятор использует сетевые подключения компьютера, на котором он запущен. Его также можно использовать для тестирования приложений Windows Phone, использующих сетевые подключения. Однако, имеет смысл протестировать работу программы на физическом устройстве. В телефоне можно отключить сетевые службы и проверить работу приложения, чтобы быть уверенным, что оно будет работать должным образом и с низкоскоростным подключением к сети, если высокоскоростное подключение окажется недоступным.
Перед изучением использования сетевых подключений в приложениях для 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 битов, которые называют 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 не имеет значения, каким способом телефон подключён к Интернету.
В Интернете используется большом количество различных протоколов, которые работают совместно и используются различными системами для обмена информацией.
Все адреса в Интернете имеют размер 32 бита и обычно записываются в виде значений четырёх октетов, разделённых точкой. Адрес состоит из адреса подсети и адреса узла в этой подсети. Такой адрес называется
Однако,
В сети Интернет работает так много устройств, что при использовании протокола IPv4 всех доступных IP-адресов недостаточно для того, чтобы выделить каждому устройству отдельный
Если между двумя компьютерами установлено сетевое подключение, то его могут использовать любые приложения, которым разрешён доступ к сети. Например, сервер может предоставлять подключённым клиентам доступ к различным службам. При этом, клиентам нужен способ определения местонахождения службы, которую они хотят использовать на сервере.
Для этих целей в Интернете используются порты.
Когда на сервере запускается служба, она сообщает системе, какой
Программисты могут создавать программы, которые используют любой номер
Интернет поддерживает два способа обмена информацией между устройствами: сеансы подключения и дейтаграммы.
Сеансы подключения используются, когда необходимо получать подтверждения об успешной передаче сообщений. Например, когда браузер загружает веб-страницу с сервера, необходимо быть уверенным, что все части веб-страницы доставлены, а если некоторые части страницы не доставлены, то необходима их повторная передача. Дополнительная работа по управлению сеансами подключения снижает скорость передачи данных, а также предъявляет дополнительные требования к каналам связи.
Дейтаграммы используются для передачи данных в тех случаях, когда не нужно получать подтверждения передачи данных. Если переданная по сети
В Интернете используются протокол управления подключениями TCP, который позволяет настраивать и управлять подключениями между сетевыми станциями, и протокол пользовательских дейтаграмм UDP, который позволяет передавать дейтаграммы.
Сетевые средства Windows Phone позволяют отправлять дейтаграммы и создавать сеансы подключения. Программисты решают сами, какой тип подключения лучше использовать в конкретной ситуации.
Рассмотрим процесс создания программы, которая будет использовать протокол UDP для обмена сообщениями с удалённой службой под названием echo. Эта служба принимает входящие сообщения и отправляет их назад. Такая служба полезна для тестирования сетевых подключений. Это одна из самых первых Интернет-служб, и ей выделен 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 строки из текстовых полей, которые содержат текст сообщения и адрес узла, и номер
Для того чтобы создать соединение между программой и сетью, необходимо создать экземпляр класса 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 содержит свойство RemoteEndPoint, которое описывает
SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs(); socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber);
Этот код создаёт новый экземпляр класса SocketAsyncEventArgs и использует класс DnsEndPoint для указания Интернет-адреса узла и номера DnsEndPoint указывается строка URL целевой системы, и класс будет использовать
После указания
// поместить сообщение в буфер byte[] messageBytes = Encoding.UTF8.GetBytes(message); socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);
Чтобы передать любое сообщение, оно должно быть представлено в виде последовательности октетов. При этом, само сообщение может быть текстом, изображением, аудио- или видеофайлом и т.д. Служба echo просто возвращает переданную ей последовательность октетов, независимо от того, какая информация в ней закодирована.
Чтобы преобразовать текст в последовательность 8-битовых значений, можно использовать класс Encoding, который описан в пространстве System.Text. Этот класс использует метод GetBytes для преобразования строки, закодированной в системе UTF-8, в массив байтов. Кодировка UTF-8 используется в системе Windows Phone для кодирования символов.
На следующем шаге необходимо определить метод, который будет вызываться после выполнения передачи, поскольку мы используем класс 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. Сначала нужно вызвать метод 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, если известны адреса таких серверов.
Проблема использования протокола UDP заключается в том, что система, которая передаёт информацию по этому протоколу, не знает, принял ли эту информацию получатель. Если необходимо создать надёжное подключение, нужно использовать протокол TCP.
Протокол TCP позволяет создавать сеансы подключения между двумя устройствами. Сообщения создаются и передаются точно так же, как и при использовании протокола UDP, но каждое сообщение является частью сеанса. Сеанс будет продолжаться, пока одна из сторон не решит его прекратить. Ответ, который передаётся после приёма сообщения, позволяет программам определить, успешно ли было передано сообщение. В конце сеанса одна из сторон закрывает подключение.
Рассмотрим процесс создания метода для запроса веб-страницы:
private string RequestWebPage(string url, string page, out string pageContent)
Этот метод принимает в качестве параметров адрес веб-сервера, имя запрашиваемой страницы, и в третий параметр заносится содержимое веб-страницы. Метод возвращает пустую строку, если запрос был выполнен успешно, или описание ошибки, если запрос завершился неудачей.
В Интернете используется протокол передачи гипертекста HTTP. Этот протокол определяет язык, используемый браузером для запроса страниц содержимого веб-сервера. Например, чтобы получить с сервера веб-страницу, браузер отправляет команду GET и адрес веб-страницы на сервере.
Поскольку важно, чтобы все части веб-страницы были доставлены клиенту, протокол HTTP использует TCP-подключение. Для получения веб-страницы программа должна выполнить следующие действия:
GET, в котором указать нужную страницу.Подключение по протоколу 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-подключение к другой странице.
Далее нужно сформировать текст сообщения и отправить его через созданное подключение:
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;
}
}
Для получения содержимого веб-страницы используется код, который аналогичен получению сообщения через 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;
}
}
Если в приложении вызывать созданный метод при нажатии на кнопку, то событие нажатия на кнопку не завершится до тех пор, пока не будет получено всё содержимое страницы или сообщение об ошибке. При этом, пользователь не сможет в это время выполнять другие действия. Если необходимо разрешить пользователю параллельно выполнять другую работу, рекомендуется выполнять загрузку страницы в отдельном потоке.
Часто программам приходится получать информацию из различных сетевых ресурсов. Обычно это довольно утомительный процесс, но его можно упростить, используя класс 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.Приложения могут взаимодействовать с различными информационными службами, например 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 предоставляет много полезных функций. В частности, он поддерживает автоматическое добавление прокрутки элементов списка вверх и вниз с её ускорением и остановкой пальцем.
Приложения для Windows Phone могут взаимодействовать с веб-серверами, используя протоколы Интернета. Также можно создавать сетевые службы и использовать их в устройствах 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 необходимо обрабатывать исключения в блоке try—catch и выдавать пользователю сообщение о недоступности сети.
Приложение при необходимости может взаимодействовать сразу с несколькими удалёнными службами. В этом случае для каждой службы добавляются ссылки в проект приложения, и работа с ними организуется точно таким же образом, что и в рассмотренном примере.
WCF?Для выполнения упражнений необходимо работающее сетевое подключение, а также среда Visual Studio для создания службы WCF.
Упражнение 1. Создание службы TimeTracker
В этом упражнении вы создадите службу, которая получает и хранит отчёты о встречах, получаемые от приложения TimeTrackerClient, работающего на удалённом устройстве. Служба будет предоставлять два метода: один — для получения отчётов о встречах, другой — для предоставления отчётов о встречах.
Создание службы
WCF шаблон Приложение службы WCF. TimeTrackerService и нажмите OK, чтобы создать проект. Visual Studio создаст новый проект и откроет файл Service1.svc.cs.Service1. В контекстном меню выберите пункт Рефакторинг -> Переименовать…, укажите в открывшемся окне новое имя класса TimeTrackerService и подтвердите переименование.Создание интерфейсов методов службы
Наша служба будет содержать два метода: один метод будет использовать удалённый клиент для сохранения отчёта о встрече, а другой будет возвращать список сохранённых встреч. Visual Studio автоматически создаёт некоторые методы, которые мы удалим из проекта.
[OperationContract]
void SaveSession(string employeeName, string companyName, int startHour, int startMin,
int endHour, int endMin);
[OperationContract]
string GetSessionList();
Создание методов службы
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 возвращает сохранённую информацию о встречах в виде многострочного текста.
Развёртывание службы
SaveSession(). Введите следующие значения параметров метода:
employeeName — Test EmployeecompanyName — Test CompanystartHour — 1startMin — 2endHour —3endMin — 4GetSessionList() и нажмите на кнопку Вызвать. Метод вернёт строку, соответствующую встрече, добавленной в систему при вызове метода SaveSession.http://localhost:30975/TimeTrackerService.svcВ вашем случае номер
Упражнение 2. Создание клиента службы
В этом упражнении мы создадим приложение, которое будет подключаться к созданной в предыдущем упражнении службе и использовать её методы.
Подключение клиентского приложения к службе
ITimeTrackerService. В поле операции будут отображены имена двух созданных методов службы.TimeTrackerService и нажмите кнопку OK. Visual Studio добавит в проект ссылку на службу.TimeTrackerService и выберите пункт Настроить ссылку на службу….Использование метода службы SaveSession в приложении
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
Использование метода службы 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, использующих сетевые подключения. Однако, имеет смысл протестировать работу программы на физическом устройстве. В телефоне можно отключить сетевые службы и проверить работу приложения, чтобы быть уверенным, что оно будет работать должным образом и с низкоскоростным подключением к сети, если высокоскоростное подключение окажется недоступным.
Перед изучением использования сетевых подключений в приложениях для 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 битов, которые называют 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 не имеет значения, каким способом телефон подключён к Интернету.
В Интернете используется большом количество различных протоколов, которые работают совместно и используются различными системами для обмена информацией.
Все адреса в Интернете имеют размер 32 бита и обычно записываются в виде значений четырёх октетов, разделённых точкой. Адрес состоит из адреса подсети и адреса узла в этой подсети. Такой адрес называется
Однако,
В сети Интернет работает так много устройств, что при использовании протокола IPv4 всех доступных IP-адресов недостаточно для того, чтобы выделить каждому устройству отдельный
Если между двумя компьютерами установлено сетевое подключение, то его могут использовать любые приложения, которым разрешён доступ к сети. Например, сервер может предоставлять подключённым клиентам доступ к различным службам. При этом, клиентам нужен способ определения местонахождения службы, которую они хотят использовать на сервере.
Для этих целей в Интернете используются порты.
Когда на сервере запускается служба, она сообщает системе, какой
Программисты могут создавать программы, которые используют любой номер
Интернет поддерживает два способа обмена информацией между устройствами: сеансы подключения и дейтаграммы.
Сеансы подключения используются, когда необходимо получать подтверждения об успешной передаче сообщений. Например, когда браузер загружает веб-страницу с сервера, необходимо быть уверенным, что все части веб-страницы доставлены, а если некоторые части страницы не доставлены, то необходима их повторная передача. Дополнительная работа по управлению сеансами подключения снижает скорость передачи данных, а также предъявляет дополнительные требования к каналам связи.
Дейтаграммы используются для передачи данных в тех случаях, когда не нужно получать подтверждения передачи данных. Если переданная по сети
В Интернете используются протокол управления подключениями TCP, который позволяет настраивать и управлять подключениями между сетевыми станциями, и протокол пользовательских дейтаграмм UDP, который позволяет передавать дейтаграммы.
Сетевые средства Windows Phone позволяют отправлять дейтаграммы и создавать сеансы подключения. Программисты решают сами, какой тип подключения лучше использовать в конкретной ситуации.
Рассмотрим процесс создания программы, которая будет использовать протокол UDP для обмена сообщениями с удалённой службой под названием echo. Эта служба принимает входящие сообщения и отправляет их назад. Такая служба полезна для тестирования сетевых подключений. Это одна из самых первых Интернет-служб, и ей выделен 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 строки из текстовых полей, которые содержат текст сообщения и адрес узла, и номер
Для того чтобы создать соединение между программой и сетью, необходимо создать экземпляр класса 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 содержит свойство RemoteEndPoint, которое описывает
SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs(); socketEventArgs.RemoteEndPoint = new DnsEndPoint(hostUrl, portNumber);
Этот код создаёт новый экземпляр класса SocketAsyncEventArgs и использует класс DnsEndPoint для указания Интернет-адреса узла и номера DnsEndPoint указывается строка URL целевой системы, и класс будет использовать
После указания
// поместить сообщение в буфер byte[] messageBytes = Encoding.UTF8.GetBytes(message); socketEventArgs.SetBuffer(messageBytes, 0, messageBytes.Length);
Чтобы передать любое сообщение, оно должно быть представлено в виде последовательности октетов. При этом, само сообщение может быть текстом, изображением, аудио- или видеофайлом и т.д. Служба echo просто возвращает переданную ей последовательность октетов, независимо от того, какая информация в ней закодирована.
Чтобы преобразовать текст в последовательность 8-битовых значений, можно использовать класс Encoding, который описан в пространстве System.Text. Этот класс использует метод GetBytes для преобразования строки, закодированной в системе UTF-8, в массив байтов. Кодировка UTF-8 используется в системе Windows Phone для кодирования символов.
На следующем шаге необходимо определить метод, который будет вызываться после выполнения передачи, поскольку мы используем класс 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. Сначала нужно вызвать метод 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, если известны адреса таких серверов.
Проблема использования протокола UDP заключается в том, что система, которая передаёт информацию по этому протоколу, не знает, принял ли эту информацию получатель. Если необходимо создать надёжное подключение, нужно использовать протокол TCP.
Протокол TCP позволяет создавать сеансы подключения между двумя устройствами. Сообщения создаются и передаются точно так же, как и при использовании протокола UDP, но каждое сообщение является частью сеанса. Сеанс будет продолжаться, пока одна из сторон не решит его прекратить. Ответ, который передаётся после приёма сообщения, позволяет программам определить, успешно ли было передано сообщение. В конце сеанса одна из сторон закрывает подключение.
Рассмотрим процесс создания метода для запроса веб-страницы:
private string RequestWebPage(string url, string page, out string pageContent)
Этот метод принимает в качестве параметров адрес веб-сервера, имя запрашиваемой страницы, и в третий параметр заносится содержимое веб-страницы. Метод возвращает пустую строку, если запрос был выполнен успешно, или описание ошибки, если запрос завершился неудачей.
В Интернете используется протокол передачи гипертекста HTTP. Этот протокол определяет язык, используемый браузером для запроса страниц содержимого веб-сервера. Например, чтобы получить с сервера веб-страницу, браузер отправляет команду GET и адрес веб-страницы на сервере.
Поскольку важно, чтобы все части веб-страницы были доставлены клиенту, протокол HTTP использует TCP-подключение. Для получения веб-страницы программа должна выполнить следующие действия:
GET, в котором указать нужную страницу.Подключение по протоколу 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-подключение к другой странице.
Далее нужно сформировать текст сообщения и отправить его через созданное подключение:
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;
}
}
Для получения содержимого веб-страницы используется код, который аналогичен получению сообщения через 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;
}
}
Если в приложении вызывать созданный метод при нажатии на кнопку, то событие нажатия на кнопку не завершится до тех пор, пока не будет получено всё содержимое страницы или сообщение об ошибке. При этом, пользователь не сможет в это время выполнять другие действия. Если необходимо разрешить пользователю параллельно выполнять другую работу, рекомендуется выполнять загрузку страницы в отдельном потоке.
Часто программам приходится получать информацию из различных сетевых ресурсов. Обычно это довольно утомительный процесс, но его можно упростить, используя класс 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.Приложения могут взаимодействовать с различными информационными службами, например 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 предоставляет много полезных функций. В частности, он поддерживает автоматическое добавление прокрутки элементов списка вверх и вниз с её ускорением и остановкой пальцем.
Приложения для Windows Phone могут взаимодействовать с веб-серверами, используя протоколы Интернета. Также можно создавать сетевые службы и использовать их в устройствах 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 необходимо обрабатывать исключения в блоке try—catch и выдавать пользователю сообщение о недоступности сети.
Приложение при необходимости может взаимодействовать сразу с несколькими удалёнными службами. В этом случае для каждой службы добавляются ссылки в проект приложения, и работа с ними организуется точно таким же образом, что и в рассмотренном примере.
WCF?Для выполнения упражнений необходимо работающее сетевое подключение, а также среда Visual Studio для создания службы WCF.
Упражнение 1. Создание службы TimeTracker
В этом упражнении вы создадите службу, которая получает и хранит отчёты о встречах, получаемые от приложения TimeTrackerClient, работающего на удалённом устройстве. Служба будет предоставлять два метода: один — для получения отчётов о встречах, другой — для предоставления отчётов о встречах.
Создание службы
WCF шаблон Приложение службы WCF. TimeTrackerService и нажмите OK, чтобы создать проект. Visual Studio создаст новый проект и откроет файл Service1.svc.cs.Service1. В контекстном меню выберите пункт Рефакторинг -> Переименовать…, укажите в открывшемся окне новое имя класса TimeTrackerService и подтвердите переименование.Создание интерфейсов методов службы
Наша служба будет содержать два метода: один метод будет использовать удалённый клиент для сохранения отчёта о встрече, а другой будет возвращать список сохранённых встреч. Visual Studio автоматически создаёт некоторые методы, которые мы удалим из проекта.
[OperationContract]
void SaveSession(string employeeName, string companyName, int startHour, int startMin,
int endHour, int endMin);
[OperationContract]
string GetSessionList();
Создание методов службы
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 возвращает сохранённую информацию о встречах в виде многострочного текста.
Развёртывание службы
SaveSession(). Введите следующие значения параметров метода:
employeeName — Test EmployeecompanyName — Test CompanystartHour — 1startMin — 2endHour —3endMin — 4GetSessionList() и нажмите на кнопку Вызвать. Метод вернёт строку, соответствующую встрече, добавленной в систему при вызове метода SaveSession.http://localhost:30975/TimeTrackerService.svcВ вашем случае номер
Упражнение 2. Создание клиента службы
В этом упражнении мы создадим приложение, которое будет подключаться к созданной в предыдущем упражнении службе и использовать её методы.
Подключение клиентского приложения к службе
ITimeTrackerService. В поле операции будут отображены имена двух созданных методов службы.TimeTrackerService и нажмите кнопку OK. Visual Studio добавит в проект ссылку на службу.TimeTrackerService и выберите пункт Настроить ссылку на службу….Использование метода службы SaveSession в приложении
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
Использование метода службы 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;
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.