Содержание


Работаем с Mono

Часть 8. Асинхронная работа с сокетами и получение информации о сетевых ресурсах

Comments

Серия контента:

Этот контент является частью # из серии # статей: Работаем с Mono

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Работаем с Mono

Следите за выходом новых статей этой серии.

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

Асинхронная работа с сокетами

Асинхронная работа с сокетами подразумевает, что любой из участников (клиент или сервер) может не ожидать окончания приема или передачи данных, а продолжать свою работу. По окончании операции участникам будет доставлено уведомление об успешном завершении или аварийной ситуации. В качестве примера приложение типа клиент/сервер, разработанное в предыдущей статье, будет адаптировано для асинхронной работы с сокетами.

Для асинхронной отправки данных вместо метода Send используется метод SendAsync, в который передается объект типа SocketAsyncEventArgs, в котором должны быть заполнены следующие поля:

  • Buffer — массив байтов, который необходимо передать;
  • Count — число байтов для передачи;
  • Offset — смещение в заданном массиве, откуда начинается передача;
  • Completed — делегат, который вызывается по окончании операции.

Метод SendAsync возвращает true, если удалось начать передачу данных в асинхронном режиме. Если это невозможно, то возвращается false, и передача выполняется в синхронном режиме. Результаты выполнения передачи данных будут помещены в объект SocketAsyncEventArgs, который можно будет проанализировать сразу же после вызова SendAsync. В листинге 1 приведен исходный код для асинхронной отправки сообщения.

Листинг 1. Пример асинхронной отправки сообщения
byte[] data = new byte[1024];
// … заполнение массива data опущено
SocketAsyncEventArgs async_e = new SocketAsyncEventArgs();
async_e.SetBuffer(data, 0, 1024);
async_e.Completed += SocketAsyncCompleted;
socket.SendAsync(async_e);

Для получения данных в асинхронном режиме нужно вызвать метод ReceiveAsync. Набор формальных параметров и возвращаемое значение также, как и требования к заполнению полей объекта типа SocketAsyncEventArgs, полностью совпадают с методом SendAsync. В листинге 2 представлен пример получения сообщения в асинхронном режиме.

Листинг 2. Пример асинхронного приема сообщения
byte[] data = new byte[1024];
// … заполнение массива data опущено
SocketAsyncEventArgs async_e = new SocketAsyncEventArgs();
async_e.SetBuffer(data, 0, 1024);
e.Completed += SocketAsyncCompleted;
socket.ReceiveAsync(async_e);

Метод SocketAsyncCompleted, вызываемый по завершении асинхронной операции, может быть общим для различных операций (отправки и получения). В нем необходимо определить источник вызова (какая из асинхронных операций завершилась) и проверить, возникли ошибки в ходе операции или нет. Информацию об ошибке можно считать из свойства SocketError. В случае успешного завершения SocketError принимает значение SocketError.Success.

Для проверки причины вызова можно использовать свойство LastOperation, представляющее собой значение перечисляемого типа (enum) SocketAsyncOperation с информацией об операции, выполнявшейся последней. Для передачи и приема информации используются значения: SocketAsyncOperation.Send и SocketAsyncOperation.Receive соответственно. Для других асинхронных операций определен дополнительный набор значений, описанных в документации.

Листинг 3. Пример метода реализации SocketAsyncCompleted
void SocketAsyncCompleted(object o, SocketAsyncEventArgs e)
{
  if(e.LastOperation == SocketAsyncOperation.Send)
  {
    if(e.SocketError == SocketError.Success)
      Console.WriteLine("Передача выполнена успешна");
    else
      Console.WriteLine("При передаче произошла ошибка");
  }
  if(e.LastOperation == SocketAsyncOperation.Receive)
  {
    if(e.SocketError == SocketError.Success)
      Console.WriteLine("Прием выполнен успешно");
    else
      Console.WriteLine("При приеме произошла ошибка");
  }
}

Исходный код примера клиент-серверного приложения, использующего асинхронное взаимодействие, находится в файле mono_async.zip, прикрепленном к статье. Скомпилировать и запустить исходный код можно командами, представленными в листинге 4.

Листинг 4. Компиляция и запуск примера приложения
#компиляция
gmcs ClientAsync.cs
gmcs ServerAsync.cs
#запуск
mono ServerAsync.exe
mono ClientAsync.exe

Также существуют асинхронные аналоги многих синхронных методов класса Socket. Для асинхронных операций можно использовать методы AcceptAsync, ConnectAsync, DisconnectAsync – аналоги методов Accept, Connect, Disconnect.

Взаимодействие с DNS

Для получения имени локального компьютера или для получения IP-адресов сервера по его доменному имени (при помощи службы DNS) можно использовать статические методы класса System.Net.Dns. В документации описано большинство методов класса Dns, однако, большая часть из них помечены как obsolete - устаревшие и не рекомендуемые к использованию. Поэтому стоит рассмотреть только два метода, использующиеся чаще всего.

Метод GetLocalName() возвращает строку с именем локального компьютера.

string localname = Dns.GetLocalName();

Второй метод GetHostEntry() получает на вход доменное имя компьютера и возвращает объект типа IPHostEntry, содержащий коллекцию IP-адресов для заданного доменного имени.

IPHostEntry host = Dns.GetHostEntry("kernel.org");

Объект IPHostEntry содержит коллекцию объектов IPAddress, из которых можно извлечь информацию об IP-адресах (одно доменное имя может быть зарегистрировано на несколько IP-адресов). В листинге 5 приведен пример анализа информации, полученной из метода GetHostEntry().

Листинг 5. Анализ информации, предоставленной методом GetHostEntry
foreach(IPAddress ipaddr in host.AddressList)
{
  byte[] ipaddr = addr.GetAddressBytes();
  if(addr.AddressFamily == AddressFamily.InterNetworkV4)
  {
    Console.WriteLine(ipaddr[0].ToString() + "." + ipaddr[1].ToString() + "." +
    ipaddr[2].ToString() + "." + ipaddr[3].ToString());
  }
  else
  {
    Console.WriteLine("Адрес не является адресом IPv4");
  }
}

Полностью исходный код приложения для работы с DNS можно посмотреть в архиве dns_example.zip, прикрепленном к статье. Для компиляции и запуска примера в консоль необходимо ввести следующие команды:

gmcs DnsExample.cs
mono DnsExample.exe kernel.org

Результат работы программы должен быть похож на рисунок 1.

Рисунок 1. Получение информации из DNS
Рисунок 1. Получение информации из DNS
Рисунок 1. Получение информации из DNS

Проверка доступности компьютера в сети

Часто требуется проверить доступность компьютера (роутера, сетевого принтера или другого ресурса) в сети. Для этого не требуется открывать соединение с удаленным компьютером, так как если ресурс не ожидает соединения, то открыть его не удастся. Поэтому определить, что именно «зависло»: отдельное приложение или сервер целиком, подобным способом все равно не получится.

Также иногда нужно определить скорость прохождения пакетов по сети и число узлов, через которые проходит пакет. Все это делается при помощи специальной диагностической программы ping, входящей практически во все современные операционные системы и работающей по протоколу ICMP поверх соединения TCP/IP. Если такую функциональность требуется встроить в собственное приложение, то на помощь придет готовая реализация класса Ping, входящего в состав Mono.

Проверка связи с удаленной системой включает в себя несколько шагов:

  • создать объект класса Ping;
  • создать объект класса PingOptions и задать настройки;
  • отправить тестовый пакет и получить ответ в виде объекта PingReply;
  • проанализировать ответ.

Класс PingOptions имеет всего две настройки:

  • DontFragment — свойство типа bool, определяющее, можно ли фрагментировать пакет при отправке по сети;
  • Ttl — время жизни пакета.

Если свойство DontFragment установлено в true, то фрагментация пакета невозможна. Это значение обычно используется для тестирования значения MTU (максимальный размер блока, который может быть передан на канальном уровне и который зависит от коммуникационного интерфейса и оборудования). В случае, если размер пакета, который запрещено фрагментировать, превысит MTU, то класс Ping вернет ошибку со значением PacketTooBig.

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

В объекте класса PingReply возвращаются следующие свойства:

  • Address — IP-адрес удаленного узла;
  • Buffer — массив байтов, переданный удаленным узлом;
  • Options — объект PingOptions, содержащий параметры, установленные удаленным ресурсом при отправке пакета;
  • RoundtripTime — время (в миллисекундах), за которое был доставлен пакет и получен ответ;
  • Status — состояние, переданное после завершения ping'а.

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

Наиболее часто в свойстве Status используются следующие значения:

  • IPStatus.Success — успешное выполнение;
  • IPStatus.PacketTooBig — пакет превышает MTU одного из встретившихся в пути узлов;
  • IPStatus.TimedOut — в течение определенного времени ответ не получен;
  • IPStatus.TtlExpired — время жизни пакета истекло в пути.

Для отправки пакета в синхронном режиме можно воспользоваться одним из вариантов метода Send объекта Ping. Наиболее полная версия метода Send принимает на вход четыре параметра:

  • адрес удаленного узла;
  • время в миллисекундах до момента, когда пакет будет считаться не дошедшим;
  • буфер байтов для передачи (не может быть больше 65500 байтов);
  • параметры отправки (PingOptions).

Для асинхронного выполнения отправки пакета используется метод SendAsync, в наиболее полной версии которого, кроме четырех описанных выше параметров присутствует еще пятый — объект класса object, который будет передан обработчику события, вызванному при получении ответа (или превышении времени ожидания запроса).

В случае асинхронной отправки необходимо подписаться на событие PingCompleted объекта Ping. В параметрах произошедшего события передается объект PingReply, объект userToken, переданный в вызов SendAsync, а также информация об ошибке или отмене запроса (до истечения времени ожидания можно вызвать метод SendAsyncCancel() и отменить ожидание пакета).

Листинг 6. Использование класса Ping в синхронном режиме
PingOptions options = new PingOptions();
options.DontFragment = true;
options.Ttl = 100;

string str = "Ping request";
byte[] buffer = Encoding.ASCII.GetBytes(str);
Ping ping = new Ping();
PingReply reply = ping.Send("192.168.1.5", 100, buffer, options);
if(reply.Status == IPStatus.Success)
{
  // анализ ответа
}

Полный код примера приложения, использующего класс Ping, можно найти в файле ping_example.zip и скомпилировать и запустить следующими командами:

gmcs PingExample.cs
mono PingExample.exe www.ibm.com

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

Рисунок 2. Выполнение ping с помощью Mono
Рисунок 2. Выполнение ping с помощью Mono
Рисунок 2. Выполнение ping с помощью Mono

Получение дополнительной сетевой информации

Пространство имен System.Net.NetworkInformation содержит большое количество функций, позволяющих идентифицировать сетевые интерфейсы и подключения, а также получать от системы статистику, собранную в ходе работы. Например, можно получить объект TcpStatistics, содержащий статистическую информацию о TCP-соединениях:

IPGlobalProperties prop = IPGlobalProperties.GetIPGlobalProperties();
TcpStatistics stat = prop.GetTcpIPv4Statistics();

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

Листинг 6. Получение информации о сетевых интерфейсах
IPGlobalProperties prop = IPGlobalProperties.GetIPGlobalProperties();
NetworkInterface[] nis = NetworkInterface.GetAllNetworkInterfaces();
if (nis == null || nis.Length < 1)
{
  Console.WriteLine("Сетевых интерфейсов не найдено");
  return;
}
Console.WriteLine("Число интерфейсов: " + nis.Length.ToString());
foreach (NetworkInterface ni in nis)
{
  IPInterfaceProperties iprop = ni.GetIPProperties();
  Console.WriteLine(ni.Description);
  Console.WriteLine("Тип интерфейса: " + ni.NetworkInterfaceType.ToString());
  Console.Write("MAC-адрес: ");
  PhysicalAddress addr = ni.GetPhysicalAddress();
  byte[] bytes = address.GetAddressBytes();
  for(int i = 0; i< bytes.Length; i++)
  {
    Console.Write(bytes[i].ToString("X2") + " ");
  }
}

Заключение

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

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=751950
ArticleTitle=Работаем с Mono: Часть 8. Асинхронная работа с сокетами и получение информации о сетевых ресурсах
publish-date=08112011