Похожие статьи

Perl и сокеты. Часть 1 (95,45%)
В этой статье рассматривается один из способов взаимодействия с сокетами языка Perl с помощью стандартного модуля Socket. Предполагается, что читатель знаком с базовым синтаксисом Perl и стеком протоколов TCP/IP. Возможности Perl-модуля Socket рассматриваются на примере клиента службы времени, который выдает текущую дату и время…

Миф о специалисте (35,9%)
Один из специалистов по продажам в интернет жалуется другому: знаешь, вот я провожу семинары и тренинги, чтобы научить людей зарабатывать в Сети. Я говорю им: у вас есть продукт, давайте посмотрим, как сделать его прибыльным продуктом, как изучить электронный рынок, как правильно предложить товар…а они после курса обучения начинают писать и…

История создания и раскрутки одного сайта (часть 2) (35,62%)
В этой статье мне бы хотелось поделиться с читателями своим небольшим опытом в создании и раскрутке небольшого проекта и охарактеризовать основные моменты этих процессов на примере своего сайта. Далее я расскажу о методах раскрутки сайта и проиллюстрирую их на конкретных примерах своего проекта ПартА…

Автор: Кручинин Даниил aka Asgard
dubalom(DOG)gmail(DOT)com

Дата публикации 19-06-2005 17:21
Статья просмотрена 21397 раз

В предыдущей статье мы разобрали, как 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::Std

my %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 "Can't create socket =(";
   connect(SOCK, $socket_addr) 
      or die "Can't 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 "Can't 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/perl

use 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 "Can't 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;

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

Похожие статьи

Дайте вашим посетителям текст (35,29%)
Исследования показали, что интернет-сайты кардинальным образом отличаются от печатных изданий. В чем заключаются эти отличия, как они влияют на поведение пользователей, каким должен быть правильный контент-проект — ответы на все эти вопросы вы найдете в данной статье…

Могут ли ваши посетители доверять вам? (33,33%)
Соблюдение конфиденциальности личной информации в Интернете становится все более и более важным вопросом для рядовых пользователей Сети. В некоторых случаях посетителям сайтов приходится целиком и полностью полагаться на порядочность владельца сайта. И в этом смысле вопрос доверия посетителя к сайту в целом становится ключевым…

CSS: врезка к статье (33,33%)
Мы часто сталкиваемся в журналах и газетах с информационными блоками, которые визуально выделены и по смыслу несколько оторваны от основного содержания. Это не прихоть верстальщика или редактора, эти блоки давно заняли заслуженное место в бумажных изданиях. К сожалению, в интернет-публикациях врезки встречаются нечасто. О создании врезок с помощью CSS как раз и рассказывается в статье…

История создания и раскрутки одного сайта (часть 1) (32,88%)
В этой статье мне бы хотелось поделиться с читателями своим небольшим опытом в создании и раскрутке небольшого проекта и охарактеризовать основные моменты этих процессов на примере своего сайта. Я расскажу о том, с чего начинается создание сайта, о выборе хостинга, о системах подсчета статистики и о регистрации в по в поисковых системах и каталогах…

Валидность HTML (32,43%)
На примере известных веб-сайтов видно, что практически никто не поддерживает стандарт HTML. И главная причина в том, что дизайнеры абсолютно не задумываются о поддержке стандарта, хотя это достаточно просто. В статье рассматриваются основные моменты, которые приводят к невалидности документов, и показывается, что исправление таких ситуаций не требует кардинальных изменений на сайте…

← Раздел «Программирование: Perl» | Комментарии (3) →

Ваше имя: 

Цифры с картинки:
Включите графику в браузере

Ваши комментарии:

Все поля формы обязательны для заполнения.
Комментарий: Любые HTML-теги в сообщениях запрещены.
Гиперссылки в комментариях не работают — добавлять спам бесполезно!
Максимальная длина комментария — 5000 символов.
Комментарии, не имеющие отношения к статье, будут удалены.

 

Perl и сокеты. Часть 2 В статье рассматривается работа с сокетами на Perl с помощью стандартного модуля Socket на примере клиента, получающего данные о погоде с weather.yandex.ru. А также работа с сокетами через модуль IO::Socket на примере клиента службы времени…

Perl и сокеты. Часть 1 В этой статье рассматривается один из способов взаимодействия с сокетами языка Perl с помощью стандартного модуля Socket. Предполагается, что читатель знаком с базовым синтаксисом Perl и стеком протоколов TCP/IP. Возможности Perl-модуля Socket рассматриваются на примере клиента службы времени, который выдает текущую дату и время…

Perl и сокеты. Часть 2 В статье рассматривается работа с сокетами на Perl с помощью стандартного модуля Socket на примере клиента, получающего данные о погоде с weather.yandex.ru. А также работа с сокетами через модуль IO::Socket на примере клиента службы времени…

Perl и сокеты. Часть 1 В этой статье рассматривается один из способов взаимодействия с сокетами языка Perl с помощью стандартного модуля Socket. Предполагается, что читатель знаком с базовым синтаксисом Perl и стеком протоколов TCP/IP. Возможности Perl-модуля Socket рассматриваются на примере клиента службы времени, который выдает текущую дату и время…