Главная страница статей --> Программирование, заработок, оболочки

Perl и сокеты. Часть 2

Источник: linuxcenter.ru

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

Зачем он нужен? Хотя бы для того, чтобы закрепить полученные знания. Мне бы, например, погодный граббер сильно пригодился. Возникает вполне естественный вопрос: откуда брать погоду? Лично я предпочитаю weather.yandex.ru.

Наша программа будет запрашивать всего 2 аргумента: текст запроса и временной интервал в минутах.

Что такое текст запроса? Для ответа на данный вопрос зайдем по этому адресу и выберем свой регион. Моя последовательность действий выглядела так: http://weather.yandex.ru/othercity.xml -> Санкт-Петербург. В итоге у меня получился url:

http://weather.yandex.ru/city.xml?city=26063

Итак, weather.yandex.ru — это хост, к которому мы будем подключаться, а все, что следует после него, будет текстом запроса. В моем случае это /city.xml?city=26063.

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

Далее следует разобраться, какие модули будут использоваться.

  • Уже хорошо знакомый нам Socket.
  • IO::Handle. Этот модуль содержит полезный метод autoflush. Perl имеет неприятное свойство накапливать данные в буфере, но нам нужно будет их немедленно сбрасывать, для чего и применяется указанный метод.
  • Getopt::Std позволит сделать классический консольный интерфейс в виде: $ ./script -v parametr -c parametr -a parametr etc. За подробностями обращайтесь к perldoc Getopt::Std.
  • И модуль Lingua::RU::PhTranslit. Пользователям Windows этот модуль не потребуется. Объясняю причину: данный модуль умеет менять кодировки. Например, у меня на FreeBSD стандартной кодировкой является koi8-r, а на weather.yandex.ru текст представлен в виндовой кодировке cp1251, поэтому, чтобы не выводились на экран кракозябы, мне нужно поменять кодировку выдаваемых строк.

Приступим:

#!/usr/bin/perl# подключаем нужные модулиuse strict;use Socket;use Getopt::Std;use IO::Handle;use Lingua::RU::PhTranslit;# хостuse constant HOST => "weather.yandex.ru";# интервал времени в минутах по умолчаниюuse constant DEF_INT => 30;# наш программный интерфейс прост:# -q текст запроса -t (необязательно) временной интервал# эти параметры будут находиться в хэше %params. # за подробностями в perldoc Getopt::Stdmy %params = undef;getopt(qt, \%params);# далее все знакомоmy $query = $params{q}     die "you to must set the -q argument!";my $interval = $params{t}  DEF_INT;my $host = gethostbyname(HOST)     die "HOST!";my $protocol = getprotobyname(tcp);# www-http — это http службаmy $port = getservbyname(www-http, tcp);my $socket_addr = sockaddr_in($port, $host);# делаем бесконечный циклwhile(1) {   socket(SOCK, AF_INET, SOCK_STREAM, $protocol)       or die "Cant create socket =(";   connect(SOCK, $socket_addr)       or die "Cant connect to socket =(";      # посылаем в сокет простенький GET-запрос      print SOCK "GET $query HTTP/1.1\n";   print SOCK "Host: ".HOST."\n\n";   # включаем автосбрасывание, иначе ничего не получится   SOCK->autoflush(1);   # в хэше %data будут храниться полученные данные   my %data = undef;   # регекспы писались после просмотра кода требуемой    # страницы на weather.yandex.ru   while(<SOCK>) {      if(/: <a href="\/tune\.xml\?rnd=\d+" class="black">(.+)<\/a>/)         { $data{sity} = win2koi($1) }      elsif(/<div class="big"><nobr>(.+?)</)         { $data{degr} = $1 }      elsif(/<td nowrap>([^<]+)<\/td>/)         { $data{dop} .= win2koi($1)." " }   }   close SOCK;   print $data{sity}.": ".$data{degr}."\n".      $data{dop}."\n";   # засыпаем на нужный интервал   sleep($interval*60);}

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

$ ./weather.pl -q "/city.xml?city=26063" -t 21
Санкт-Петербург: +15 °C
Пасмурно Ветер: З, 2 м/с 758 мм рт. ст. Влажность: 94%

Далее. Я, помнится, обещал рассказать о модуле IO::Socket, упрощающем работу с сокетами в perl. Зачем понадобился еще один модуль для реализации одной и той же цели? Да, многим программистам, которым доводилось работать с сокетами на C++, и стандартный модуль Socket может показаться раем, но посмотрите на описанный выше пример. Он, мягко говоря, реализован не слишком изящно, ибо нам пришлось подключать целый модуль IO::Handle только из-за одного метода autoflush. Не слишком практично, не так ли?

Да и вообще, многим неприятна эта долгая последовательность действий в виде определения хоста (getservbyname("host_name")), определения порта (getservbyname("service", "protocol")), определения числового номера протокола (getprotobyname("protocol")), составления адреса сокета (sockaddr_in($port, $host)), создания нового сокета, используя многочисленные константы, я устал перечислять… Люди часто путаются в последовательности действий, в функциях get*byname, постоянно обращаясь к perldoc Socket, что, в общем-то, не есть плохо, но довольно неудобно.

Модуль же IO::Socket является объектно-ориентированным и наследует все методы модуля IO::Handle, в том числе и уже знакомый нам autoflush, который он, кстати, выполняет автоматически, но об этом позже. Итак, после рассмотрения причин, которые подтолкнули умных и ленивых (как говорит Ларри Уол — создатель языка perl — каждым программистом движет прежде всего лень =)) людей на создание данного модуля, дабы упростить жизнь многим таким же ленивым и умным(?) программистам, как они, можно рассмотреть этот несомненно замечательный модуль подробнее.

Прежде всего, следует отметить, что модуль IO::Socket экспортирует все константы и функции метода Socket. Методы модуля IO::Socket (здесь перечислены не все методы, за подробностями обращайтесь к perldoc IO::Socket):

new([ARGS]) Конструктор (если вы не знаете, что это такое, рекомендую прочитать книгу «Object-oriented perl»).
socketpair(DOMAIN, TYPE, PROTOCOL) Возвращает список из двух созданных сокетов или пустой список в случае ошибки.
accept([PKG]) Метод выполняет ту же задачу, что и одноименная функция. Метод выбирает следующее входящее соединение из очереди и возвращает подключенный сокет сеанса. Новый сокет наследует все атрибуты своего родителя, но, в отличие от него, является подключенным. В скалярном контексте метод accept() возвращает новый подключенный сокет, в контексте списка он возвращает список из двух элементов, одним из которых является подключенный сокет, а вторым — упакованный адрес удаленного хоста.
connected Если сокет находится в подключенном состоянии, возвращается адрес хоста, к которому он подключен. В противном случае возвращается undef.
protocol Возвращает числовой номер протокола, используемого данным сокетом. Если протокол не известен, как в случае с сокетами с доменом AF_UNIX, возвращается 0.
sockdomain Возвращает числовой номер домена данного сокета. Например, для сокета с доменом AF_INET возвращенное значение будет &AF_INET.
sockopt(OPT [, VAL]) Является внешним интерфейсом для установки и определения опций на уровне SOL_SOCKET. Если метод вызывается с одним аргументом, вызывается getsockopt, в противном случае вызывается setsockopt.
socktype Возвращает числовой номер типа сокета. Например, для сокетов типа SOCK_STREAM, возвращенным значением будет &SOCK_STREAM.
timeout([VAL]) Устанавливает или определяет значение тайм-аута для данного сокета. Если метод вызывается без каких-либо аргументов, возвращается текущее значение его тайм-аута. Если же метод вызывается с аргументом, текущее значение тайм-аута меняется и возвращается его предыдущее значение.

Также существуют модули IO::Socket::INET и IO::Socket::UNIX — наследники IO::Socket. Первый определяет правила поведения для сокетов AF_INET, второй — для сокетов AF_UNIX (по стандартизации POSIX константа AF_UNIX переименована в AF_LOCAL). В данной статье будет рассматриваться лишь модуль IO::Socket::INET, если хотите узнать больше о IO::Socket::UNIX, читайте perldoc IO::Socket::UNIX.

Конструктором модуля IO::Socket::INET является метод с классическим именем new(), который принимает в качестве аргументов имя хоста и имя порта в таком виде:

my $sock = IO::Socket::INET->new(host_name:port_name)

Например:

my $sock = IO::Socket::INET->new(s2.daytime.net.ru:daytime)

Заметьте, модуль самостоятельно находит ip-адрес хоста, номер службы, номер порта, строит правильную структуру sockaddr_in(), создает сокет и автоматически предпринимает попытку подключиться к удаленному сокету (выполняет метод connect()). И все это чудо занимает одну строчку. Приятно освобождать себя от лишней рутинной работы и экономить время =), не правда ли? В случае ошибки метод new() возвращает значение undef, тогда переменная $! будет содержать системное сообщение об ошибке, а переменная $@ — более содержательное сообщение об ошибке, выработанное самим модулем. Синтаксис его очень гибок. Все ниже перечисленные варианты являются верными:

s2.daytime.net.ru:daytime
s2.daytime.net.ru:13
212.9.235.98:daytime
212.9.235.98:13

Модуль IO::Socket::INET поддерживает следующие параметры:

PeerAddr Адрес удаленного хоста <hostname>[:<port>]
PeerHost Синоним параметра PeerAddr -/-/-/-
PeerPort Удаленный порт или служба <service>[(<no>)] <no>
LocalAddr Адрес локального хоста, к которому привязан сокет hostname[:port]
LocalHost Синоним параметра LocalAddr -/-/-/-
LocalPort Порт локального хоста, к которому привязан сокет <service>[(<no>)] <no>
Proto Имя или номер протокола "tcp" "udp" …
Type Тип сокета SOCK_STREAM SOCK_DGRAM …
Listen Размер очереди входящих соединений для приемного сокета <integer number>
ReuseAddr Устанавливает SO_REUSEADDR перед привязкой сокета  
Reuse Синоним параметра ReuseAddr  
ReusePort Устанавливает SO_REUSEPORT перед привязкой сокета  
Broadcast Устанавливает SO_BROADCAST перед привязкой сокета  
Timeout Значение тайм-аута  
MultiHomed Опробовать все адреса на хосте с несколькими сетевыми интерфейсами  
Blocking Определить, находится ли соединение в блокирующем состоянии  

Параметры PeerHost, LocalAddr, LocalPort применяются в программах, работающих в качестве серверов, т.е. позволяют принимать соединения.

Метод new() может также применяться для создания сокетов с универсальными опциями. Для этого существует так называемый расширенный режим метода new(). Например:

my $sock = IO::Socket::INET->new(PeerAddr

или

my $sock = IO::Socket::INET->new(PeerAddr => localhost:smtp(25));

или

my $sock = IO::Socket::INET->new(Listen    => 5,             LocalAddr => localhost,             LocalPort => 9000,             Proto     => tcp);

или

my $sock = IO::Socket::INET->new(127.0.0.1:25);

или, наконец

my $sock = IO::Socket::INET->new(PeerPort  => 9999,             PeerAddr  => inet_ntoa(INADDR_BROADCAST),             Proto     => udp,             LocalAddr => localhost,             Broadcast => 1 )         or die "Cant bind : $@\n";

Методы объекта IO::Socket::INET:

sockaddr() Возвращает адресную часть структуры sockaddr для сокета
sockport() Возвращает номер порта, который использует сокет на локалхосте
sockhost() Возращает адресную часть sockaddr структуры для сокета в качестве текста из xx.xx.xx.xx
peeraddr() Возращает адресную часть sockaddr структуры для сокета, находящегося на удаленном хосте
peerport Возвращает номер порта для сокета, находящегося на удаленном хосте
peerhost Возвращает адресную часть sockaddr структуры для сокета, находящегося на удаленном хосте в текстовой форме из xx.xx.xx.xx

Надоело писать сухую и скучную теорию =(. Вернемся к практике, а именно — к нашему daytime клиенту. Тоже не слишком увлекательно, но все же приятнее чтения теории. Перепишем daytime клиент, используя модуль IO::Socket.

#!/usr/bin/perluse strict;use IO::Socket;# устанавливаем константу, хранящую адрес# хоста по умолчаниюuse constant HOST => s2.daytime.net.ru;my $host = shift  HOST;# создаем новый объект IO::Socket::INET с помощью# метода new(), принимающего указанный хост # и имя службыmy $sock = IO::Socket::INET->new("$host:daytime") or   die "Cant connect to socket =(";# getline — метод модуля IO::Handle, который был# унаследован модулем IO::Socket. в принципе, getline# является объектно-ориентированным # эквивалентом <DESKRIPTOR>my $time = $sock->getline;# мы не выводим символ новой строки потому, что# сервер возвращает сообщение с символом# новой строки в конце. ВНИМАНИЕ! это подойдет только для# *nix систем, т.к. символ новой строки в конце сообщения — # LF (в Windows используется CRLF, в MacOS — LFCR), # принятый в *nix. # если вы работаете в Windows или MacOS,# придется изменить строки my $time = $sock->getline; на# chomp(my $time = $sock->getline);# и# print $time; на# print "$time\n";print $time;

Все. В следующей и последней статье, посвященной сокетам, почти не будет теории, только практика. Ждите, Ill be back © …



Похожие статьи:
- nLite: делаем свой дистрибутив Windows
- Всегда в курсе последних новостей
- Page Promoter 7.0: практический кибермаркетинг
- Словари Рунета
- Кибермаркетинг: результаты первых шагов
- Использование интернет-форумов
- Теория веб-специализации
- Доступность имеет значение
- Свойство CSS float: обтекание рисунка текстом и врезка к статье
- Проверка полей формы с помощью JavaScript
- О ценах, ценностях и убогом дизайне
- Page Promoter 7.2: анализ поисковой рекламы
- Обзор технических решений для интернет-магазинов


Оглавление | Обсудить на форуме | Главная страница сайта | Карта сайта |
Контакты
Редакция:
[0.001]