ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 
Глава 17 Сокеты

Сокеты

Введение

Сокеты являются "конечными пунктами" в процессе обмена данными. Один типы сокетов обеспечивают надежный обмен данными, другие почти ничего не т'л рантируют, зато обеспечивают низкий расход системных ресурсов. Обмен данными через сокеты может осуществляться на одном компьютере или через Интернет.
В этой главе мы рассмотрим два самых распространенных типа сокетов: потоковые и датаграммные. Потоковые сокеты обеспечивают двусторонние, последовательные и надежные коммуникации; они похожи на каналы (pipes). Датаграммные сокеты не обеспечивают последовательную, надежную доставку, по они гарантируют, что в процессе чтения сохранятся границы сообщений. Ваша система также может поддерживать сокеты других типов; за подробностями обращайтесь к man-странице socket(2) или эквивалентной документации. Сокеты делятся по областям (domain): сокеты Интернета и сокеты UNIX. Имя сокета Интернета содержит две составляющие: хост (IP-адрес в определенном формате) и номер порта. В мире UNIX сокеты представляют собой файлы (например, /tmp/mysock).
Кроме области и типа, с сокетом также ассоциируется определенный протокол. Протоколы не имеют особого значения для рядового программиста, поскольку для конкретного сочетания области и типа сокета редко используется более одного протокола.
Области и типы обычно идентифицируются числовыми константами (котормг возвращаются функциями, экспортируемыми модулями Socket и IO::Socket). Потоковые сокеты имеют тип SOCK_STREAM, а датаграммные - SOCK_DGRAM. Области Интернета соответствует константа PF_INET, а области UNIX - константа PFJJNIX (в POSIX вместо PFJJNIX используется PF_LOCAL, но PFJJNIX почти всегда допустима просто потому, что используется в огромном количестве существующих программ). Используйте символические имена вместо числовых значений, поскольку последние могут измениться (что неоднократно происходило).
Имена протоколов (например, tcp и udp) тоже соответствуют числам, используемым операционной системой. Встроенная функция Perl getprotobyname возвращает номер по имени протокола. Если функциям сокетов передается значение 0, система выберет подходящий протокол по умолчанию.
Perl содержит встроенные функции для создания сокетов и управления ими; они в основном дублируют свои прототипы на С. Хотя это удобно для получения низкоуровневого, прямого доступа к системе, большинство предпочитает работать с более удобными средствами. На помощь приходят классы IO::Socket::INET и IO::Socket::UNIX - они обеспечивают высокоуровневый интерфейс к низкоуровневым системным функциям. Начнем с рассмотрения встроенных функций. В случае ошибки все они возвращают undef и присваивают $! соответствующее значение. Функция socket создает сокет, bind - назначает ему локальное имя, connect - подключает локальный сокет к другому (возможно, удаленному). Функция listen готовит сокет к подключениям со стороны других сокетов, a accept последовательно принимает подключения. При обмене данными с потоковыми сокетами можно использовать как print и о, так и syswrite и sysread, а при обмене с датаграммными сокетами - send и recv.
Типичный сервер вызывает socket, bind и listen, после чего в цикле вызывает accept в блокирующем режиме, ожидая входящих подключений (см. рецепты 17.2 и 17.5). Типичный клиент вызывает socket и connect (см. рецепты 17.1 и 17.4). Да-таграммные клиенты ведут себя особым образом. Они не обязаны вызывать соnnес:для передачи данных, поскольку могут указать место назначения в качестве аргумента send.
При вызове bind, connect или send для конкретного приемника необходимо указать имя сокета. Имя сокета Интернета состоит из хоста (IP-адрес, упакованныи функцией inet_aton) и порта (числа), объединенных в С-подобную структуру функцией sockaddr_in:
use Socket;
$packed_ip = inet_aton("208.146.240.1");
$socket_name = sockaddr_ln($port, $packed_ip);
Имя сокета UNIX представляет собой имя файла, упакованное в структуру С функцией
sockaddr_un:
use Socket;
$socket_name = sockaddr_un("/tmp/mysock");
Чтобы преобразовать упакованное имя сокета и снова получить имя файла или пару "хост/порт", вызовите sockaddr_un или sockaddr_in в списковом контексте:
($port, $packed_ip) = sockaddr_in($socket_name); # Для сокетов PF_INET ($filename)
= sockaddf_un($socket_name): # Для сокетов PF_UNIX
Функция inet_ntoa преобразует упакованный IP-адрес в ASCII-строку.
$ip_address = inet_ntoa($packed_ip);
$packed_ip = inet_aton("204.148.40.9");
$packed_ip = inet_aton("www.oreilly.com");
В большинстве рецептов используются сокеты Интернета, однако практически все сказанное в равной мере относится и к сокетам UNIX. В рецепте 17.6 объясняются отличия и возможные расхождения.
Сокеты являются основой для работы сетевых серверов. Мы рассмотрим три варианта построения серверов: в нервом для каждого входящего подключения создается порожденный процесс (рецепт 17.11), во втором сервер создает порожденные процессы заранее (рецепт 17.12), а в третьем процесс-сервер вообще не создает порожденные процессы (рецепт 17.13). Некоторые серверы должны одновременно вести прослушивание но многим IP-адресам (см. рецепт 17.14). Хорошо написанный сервер деинициализируется и перезапускается при получении сигнала HUP; в рецепте 17.16 показано, как реализовать такое поведение в Perl. Кроме того, вы узнаете, как иденти4)ицировать оба конца соединения (см. рецепты 17.7 и 17.8).

17.1. Написание клиента TCP

Проблема

Вы хотите подключиться к сокету на удаленном компьютере.

Решение

Следующее решение предполагает, что связь осуществляется через Интернет TCP-подобные коммуникации на одном компьютере рассматриваются в рецепте 17.G, Либо воспользуйтесь стандартным (для версии 5.004) классом IO::Socket::INET:
use 10::Socket;
$socket = 10::Socket::INET->new(PeerAddr =>
$remote_host, PeerPort => $remote_port, Proto =>
"tcp", Type =>
SOCK_STREAM) or die "Couldn't connect to $remote_host:$remote_port : $@\n";
# . . . Сделать что-то с сокетом
print $socket "Why don't you call me anymore?\n";
$answer = <$socket>;
# Отключиться после завершения
close($socket):
либо создайте сокет вручную, чтобы лучше управлять его поведением:
use Socket;
# Создать сокет
socket(SERVER, PFJNET, SOCK_STREAM, getprotobyname('tcp'));
# Построить адрес удаленного компьютера
$internet_addr = inet_aton($remote_host)
or die "Couldn't convert $remote_host into an Internet address: $!\n";
$paddr = sockaddr_in($remote_port, $internet_addr);
# Подключиться connect(TO_SERVER, $paddr)
or die "Couldn't connect to $remote_host:$remote_port : $!\n";
# ... Сделать что-то с сокетом
print TO_SERVER "Why don't you call me anymore?\n";
# И отключиться после завершения
close(TCLSERVER);

Комментарий

Ручное кодирование состоит из множества действий, а класс IO::Socket::INET объединяет их все в удобном конструкторе. Главное, что необходимо знать, - куда вы направляетесь (параметры PeerAddr и PeerPort) и каким образом (параметр Type). По переданной информации IO::Socket::INET пытается узнать все остальное. Так, протокол по возможности вычисляется по типу и номеру порта; если это не удается сделать, предполагается протокол tcp. Параметр PeerAddr содержит строку с именем хоста ("www. o'reilly. corn") или его IP-адресом ('204.148.40.9"). PeerPort - целое число, номер порта для нолк.-почг ния. Номер порта можно включить в адрес в виде "www. oreilly. corn: 80". Параметр Type определяет тип создаваемого сокета: SOCK_DGRAM для датаграммного со-кета или SOCK_STREAM для потокового. Чтобы подключиться через SOCK_STREAM к порту конкретного компьютера, не поддерживающего других возможностей, передайте 10: : Socket: : INET->new одну строку с именем хоста и портом, разделенными двоеточием:
$client = 10::Socket::INET->new("www.yahoo.com:80") or die $@;
При возникновении ошибки IO::Socket::INET возвращает undef, а переменной $? (не $ ) присваивается сообщение об ошибке.
$s = 10::Socket::INET->new(PeerAddr => "Does not Exist", Peerport => 80,
Type => SOCK_STREAM ) or die $@;
Если ваши пакеты бесследно исчезают в глубинах сети, вероятно, невозможность подключения к порту будет обнаружена лишь через некоторое время. Вы можете уменьшить этот промежуток, передавая параметр Timeout при вызове 10::Socket::INET->new():
$s = 10::Socket::INET->new(PeerAddr => "bad.host.com", PeerPort => 80,
Type => SOCK_STREAM, Timeout => 5 )
or die
Но в этом случае вы уже не сможете использовать $! или $@, чтобы узнать причину неудачи - невозможность подключения или тайм-аут. Иногда бывает удобнее установить тайм-аут вручную, без использования модуля.
INADDR_ANY - специальный адрес, означающий "прослушивание на всех интерфейсах". Если вы хотите ограничить его конкретным IP-адресом, включите параметр LocalAddr в вызов 10:: Socket:: INET->new. При ручном кодировании это делается так:
$inet_addr = inet_aton("208.146.240.1");
$paddr = sockaddr_in($port, $inet_addr);
bind(SOCKET, $paddr) or die "bind: $!";
Если вам известно только имя, действуйте следующим образом:
$inet_addr = gethostbyname("www.yahoo.com")
or die "Can't resolve www.yahoo.com: $!";
$paddr = sockaddr_in($port, $inet_addr);
bind(SOCKET, $paddr) or die "bind: $!":
> Смотри также -------------------------------
Описание функций socket, bind, connect и gethostbyname в perlfunc(\); документация по стандартным модулям Socket, IO::Socket и Net::hostent; раздел "Internet TCP Clients and Servers" perlipc(1)\ рецепты 17.2-17.3.

17.2. Написание сервера TCP

Проблема

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

Решение

Следующее решение предполагает, что связь осуществляется через Интернет. TCP-подобные коммуникации на одном компьютере рассматриваются в рецепте 17.6. Воспользуйтесь стандартным (для версии 5.004) классом IO::Socket::INET:
use 10::Socket;
$server = 10::Socket::INET->new(LocalPort => $server_port,
Type => SOCK_STREAM, Reuse => 1,
Listen => 10 ) # or SOMAXCONN
or die "Couldn't be a tcp server on port $server_port : $@i\n";
while ($client = $server->accept()) { # $client - новое подключение
}
close($server);
Или создайте сокет вручную, что позволит получить полный контроль над ним:
use Socket;
# Создать сокет
socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
# Чтобы мы могли быстро перезапустить сервер
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1);
# Построить свой адрес сокета
$my_addr = sockaddr_in($server_port, INADDR_ANY);
bind(SERVER, $my_addr)
or die "Couldn't bind to port $server_port : $!\n";
# Установить очередь для входящих соединений
$sten(SERVER, SOMAXCONN)
or die "Couldn't listen on port
$server_port : $!\n";
# Принимать и обрабатывать подключения
while (accept(CLIENT, SERVER)) {
# Сделать что-то с CLIENT
}
close(SERVER);

Комментарий

Написать сервер сложнее, чем клиент. Необязательная функция listen сообщае'1 операционной системе, сколько подключений могут находиться в очереди к серверу, ожидая обслуживания. Функция setsockopt, использованная в решении, позволяет избежать двухминутного интервала после уничтожения сервера перед его перезапуском (полезна при тестировании). Функция bind регистрирует сервер в ядре. Наконец, функция accept последовательно принимает входящие подключения.
Числовой аргумент listen определяет количество не принятых функцией accept подключений, которые будут поставлены в очередь операционной системой перед тем, как клиенты начнут получать ошибки "отказ в обслуживании". Исторически максимальное значение этого аргумента было равно 5, но и сегодня многие операционные системы тайно устанавливают максимальный размер очереди равным примерно 20. Сильно загруженные Web-серверы стали распространенным явлением, поэтому многие поставщики увеличивают это значение. Максимальный размер очереди для вашей системы хранится в константе SOMAXCONN модуля Socket.
Функции accept передаются два аргумента: файловый манипулятор, подключаемый к удаленному клиенту, и файловый манипулятор сервера. Она возвращает IP-адрес и порт клиента, упакованные
inet_ntoa:
use Socket;
while ($client_address = accept(CLIENT, SERVER)) {
(Sport, $packed_ip) = sockaddr_in($client^address);
$dotted_quad = inet_ntoa($packed_ip);
# Обработать }
В классах IO::Socket accept является методом, вызываемым для манипулятора сервера:
while (($client,$client_address) = $server->accept()) {
# ...
}
Если ожидающих подключений нет, программа блокируется на вызове accept до того, как появится подключение. Если вы хотите гарантировать, что вызов accept не будет блокироваться, воспользуйтесь неблокирующими сокетами:
use Fcnti qw(F_GETFL F_SETFL 0_NONBLOCK);
$tlags = fcntl($SERVER, F_GETFL, 0)
or die "Can't get flags for the socket: $!\n":
$flags = fcntl($SERVER, F_SETFL, $flags | 0_NONBLOCK)
or die "Can't set flags for the socket: $!\n";
Если теперь при вызове accept не окажется ожидающих подключений, ассе]. вернет undef и присвоит $! значение EWOULDBLOCK.
Может показаться, что при возвращении нулевых флагов от F_GETFL будет вызвана функция die, как и при неудачном вызове, возвращающем undef. Это не гак - неошибочное возвращаемое значение fcnti, как и для iocti, преобразуется Perl в специальное значение "О but true". Для этой специальной строки даже не действуют надоедливые предупреждения флага -w о нечисловых величинах, поэтому вы можете использовать ее в своих функциях, когда возвращаемое значение равно 0 и тем не менее истинно.

Смотри также -------------------------------
Описание функций socket, bind, listen, accept, fcnti и setsockopt в peiifunc(1) страницы руководства socket(2), setsockopt(2) вашей системы (если они есть); документация по стандартным модулям Socket, IO::Socket и Net::hostent; раздел "Internet TCP Clients and Servers" perlipc(1), рецепты 7.13-7.14; 17.1; 17.3; 17.7.

17.3. Передача данных через TCP

Проблема

Требуется передать или принять данные по TCP-соединению.

Решение

Следующее решение предполагает, что связь осуществляется через Интернет. TCP-подобные коммуникации на одном компьютере рассматриваются в рецепте 17.6.
Первый вариант - print или ':
print SERVER "What is your name?\n";
chomp ($response = );
Второй вариант - функции send и recv:
defined (send(SERVER, $data_to_send, $flags)) or die "Can't send : $!\n";
recv(SERVER, $data_read, $maxlen, $flags) or die "Can't receive: $!\n";

Третий вариант - соответствующие методы объекта IO::Socket:
use 10::Socket;
$server->send($data_to_send, $flags) or die "Can't send: $!\n";
$server->recv($data_read, $flags) or die "Can't recv: $!\n";

Чтобы узнать, могут ли быть получены или приняты данные, воспользуйтесь функцией select, для которой в классе IO::Socket также предусмотрена удобная оболочка:
use 10::Select;
$select = 10::Select->new();
$select->add(*FROM_SERVER);
$select->add($to_client);
@read_from = $select->can_read($timeout);
foreach $socket (@read_from) {
# Прочитать ожидающие данные из
$socket }

Комментарий

Сокеты используются в двух принципиально различных типах ввода/вывода, каждый из которых обладает своими достоинствами и недостатками. Стандартные функции ввода/вывода Perl, используемые для файлов (кроме seek и sysseek), работают и для потоковых сокетов, однако для датаграммных сокетов необходимы системные функции send и recv, работающие с целыми записями. При программировании сокетов очень важно помнить о буферизации. Хотя буферизация и была спроектирована для повышения быстродействия, она может повлиять на интерактивное поведение некоторых программ. Если при вводе данных с помощью о будет обнаружен разделитель записей, программа может попытаться прочитать из сокета больше данных, чем доступно в данный момент. И print и о используют буферы stdio, поэтому без включения автоматической очистки буфера (см. введение главы 7 "Доступ к файлам") для манипулятора сокета данные не отправятся на другой конец в момент их передачи функцией print. Вместо этого они будут ждать заполнения буфера.
Вероятно, для клиентов и серверов с построчным обменом данных это подходит - при условии, что вы не забыли включить автоматическую очистку буфера. Новые версии IO::Socket делают это автоматически для анонимных 4)айловых манипуляторов, возвращаемых 10: :Socket->new.
Но стандартный ввод/вывод - не единственный источник буферизации. Операции вывода (print, printf, syswrite - или send для сокета TCP) буферизуются на уровне операционной системы по так называемому алгоритму Нейгла. Если пакет данных отправлен, но еще не подтвержден, другие передаваемые данные ставятся в очередь и отправляются либо после набора следующего полного пакета, либо при получении подтверждения. В некоторых ситуациях (события мыши в оконных системах, нажатия клавиш в приложениях реального времени) такая буферизация оказывается неудобной или попросту неверной. Буферизация Нейгла отключается параметром сокета TCP_NODELAY:
use Socket;
require "sys/socket.ph"; # Для &TCP_NODELAY
setsockopt(SOCKET, SOL_SOCKET, &TCP_NODELAY, 1)
or die "Couldn't disable Nagle's algorithm: $!\n";

Ее повторное включение происходит так:
setsockopt(SOCKET, SOL_SOCKET, &TCP_NODELAY, 0)
or die "Couldn't enable Nagle's algorithm: $!\n";

Как правило, TCP_NODELAY все же лучше не указывать. Буферизация TCP существует не зря, поэтому не отключайте ее без крайней необходимости - например, если ваше приложение работает в режиме реального времени с крайне интенсивным обменом пакетов. TCP_NODELAY загружается из sys/socket.ph - этот файл не устанавливается автоматически вместе с Perl, но может быть легко построен. Подробности приведены в рецепте 12.14. Буферизация чрезвычайно важна, поэтому в вашем распоряжении имеется функция select. Она определяет, какие манипуляторы содержат непрочитанный ввод, в какие манипуляторы возможна запись и для каких имеются необработанные "исключительные состояния". Функция select получает три строки, интерпретируемые как двоичные данные; каждый бит соответствует файловому манипулятору. Типичный вызов select выглядит так:
$rin = ''; # Инициализировать маску
vec($rin, fileno(SOCKET), 1) = 1; # Пометить SOCKET в $rin
# Повторить вызовы vec() для каждого проверяемого сокета
$timeout =10: # Подождать 10 секунд
$nfound = select($rout = $rin, undef, undef, $timeout);
if (vec($rout, fileno(socket),1)){
# В SOCKET имеются данные для чтения
}

Функция select вызывается с четырьмя аргументами. Три из них представляют собой битовые маски: первая проверяет в манипуляторах наличие непрочитанных данных в манипуляторах; вторая - возможность безопасной записи без блокировки; третья - наличие в них исключительных состояний. Четвертый аргумент определяет максимальную длительность ожидания в секундах (может быть вещественным числом).
Функция модифицирует передаваемые ей маски, поэтому при выходе из нее биты будут установлены лишь для манипуляторов, готовых к вводу/выводу. Отсюда один стандартный прием - входная маска ($rin в предыдущем примере) присваивается выходной ($rout), чтобы вызов select изменил только $rout и оставил $пп в прежнем состоянии.
Нулевой тайм-аут определяет режим опроса (проверка без блокировки). Некоторые начинающие программисты не любят блокировки, и в их программах выполняется "занятое ожидание"
(busy-wait) - программа в цикле выполняет опрос, снова и снова. Когда программа блокируется, операционная система понимает, что процесс ждет ввода, и передает процессорное время другим программам до появления входных данных. Когда программа находится в "занятом ожидании", система не оставляет ее в покое, поскольку программа всегда что-то делает - проверяет ввод! Иногда опрос действительно является правильным решением, но гораздо чаще это не так. Тайм-аут, равный undef, означает отсутствие тайм-аута, поэтому ваша программа терпеливо блокируется до появления ввода.
Поскольку select использует битовые маски, которые утомительно создавать и трудно интерпретировать, в решении используется стандартный модуль IO::Select. Он обходит работу с битовыми масками и, как правило, более удобен. Полное объяснение исключительных состояний, проверяемых третьей маской select, выходит за рамки настоящей книги.
Другие флаги send и recv перечислены в страницах руководства этих системных функций.

> Смотри также -------------------------------
Описание функций send, recv, fileno, vec и setsockopt в perlfunc(1); разделы "I/O Operators" и "Bitwise String Operators" Bperlop(1); страница руководства setsockopt(2) вашей системы (если есть); документация по стандартным модулям Socket и IO::Socket; раздел "Internet TCP Clients and Servers" perlipc(1); рецепты 17.1-17.2.

17.4. Создание клиента UDP

Проблема

Вы хотите обмениваться сообщениями с другим процессом, используя UDP (датаграммы).

Решение

Чтобы создать манипулятор для сокета UDP, воспользуйтесь либо низкоуровневым модулем Socket для уже существующего манипулятора:
use Socket;
socket(SockHandle, PF_INET, SOCK_DGRAM,
getprotobyname("udp")) or die "socket: $!";
либо модулем IO::Socket, возвращающим анонимный манипулятор:
use 10::Socket;
$handle = 10::Socket::INET->new(Proto => 'udp')
or die "socket: $@"; # Да, здесь используется $@
Отправка сообщения на компьютер с именем $HOSTNAME и адресом порта $PORTNO выполняется так:
$ipaddr = inet_aton($HOSTNAME);
$portaddr = sockaddr_in($PORTNO, $ipaddr);
send(SockHandle, $MSG, 0, $portaddr) == length($MSG)
or die "cannot send to $HOSTNAME($PORTNO): $!";
Получение сообщения, длина которого не превышает $MAXLEN:
$portadd-r = recv(SockHandle, $MSG, $MAXLEN, 0) or die "recv: $!";
($portno, $ipaddr) = sockaddr_in($portaddr);
$host = gethostbyaddr($ipaddr, AF_INET);
print "$host($portno) said $MSG\n";

Комментарий

Датаграммные сокеты не похожи на потоковые. Поток создает иллюзию посте янного соединения. Он напоминает телефонный звонок - установка связи обходится дорого, но в дальнейшем связь надежна и проста в использовании. Датаграммы больше похожи на почту - если ваш знакомый находится на другом конце света, дешевле и проще отправить ему письмо, чем дозвониться по телефону. Датаграммы потребляют меньше системных ресурсов, чем потоки. Вы пересылаете небольшой объем информации, по одному сообщению за раз. Однако доставка сообщений не гарантируется, и они могут быть приняты в неверном порядке Если очередь получателя переполнится, как маленький почтовый ящик, то дальнейшие сообщения теряются.
Если датаграммы настолько ненадежны, зачем же ими пользоваться? Просто некоторые приложения наиболее логично реализуются с применением датаграмм. Например, при пересылке аудиоданных важнее сохранить поток в целом, чем гарантировать прохождение каждого пакета, особенно если потеря пакетов вызва17.4. Создание клиента UDP 615 на недостаточной пропускной способностью. Датаграммы также часто применяются в широковещательной рассылке (аналог массовой рассылки рекламных объявлений по почте). В частности, широковещательные пакеты используются для отправки в локальную подсеть сообщений типа: "Есть здесь кто-нибудь, кто хочет быть моим сервером?"
Поскольку датаграммы не создают иллюзии постоянного соединения, в работе с ними вы располагаете несколько большей свободной. Вам не придется вызывать connect для подключения сокета к удаленной точке, с которой вы обмениваетесь данными. Вместо этого каждая датаграмма адресуется отдельно при вызове send. Предполагая, что $remote_addr является результатом вызова socknddrin, поступите следующим образом:
send(MYSOCKET, $msg_buffer, $flags, $remote_addr) or die "Can't send: $!\n";

Единственный часто используемый флаг, MSG_OOB, позволяет отправлять и принимать внеполосные (out-of-band) данные в нетривиальных приложениях. Удаленный адрес ($remote_addr) должен представлять собой комбинацию порта и адреса Интернета, возвращаемую функцией sockaddr_in модуля Socket. Если хотите, вызовите connect для этого адреса - в этом случае последний аргумент при вызове send можно опускать, а все сообщения будут отправлены этому получателю. В отличие от потоковых коммуникаций, один датаграммный сокет позволяет подключиться к другому компьютеру.
В примере 17.1 приведена небольшая программа, использующая протокол UDP. Она устанавливает связь с портом времени UDP на компьютере, имя которого задается в командной строке, или по умолчанию на локальном компьютере. Программа работает не на всех компьютерах, но при наличии сервера UDP вы получите 4-байтовое целое число, байты которого упакованы в сетевом порядке; число равно количеству секунд с 1900 года по данным этого компьютера. Чтобы передать это время функции преобразования localtime или gmtime, необходимо вычесть из него количество секунд от 1900 до 1970 года.
Пример 17.1. clockdrift
#!/usr/bin/perl
# clockdri
# - сравнение текущего времени с другой системой
use strict;
use Socket:
my ($host, $him, $src, Sport, $ipaddr, $ptime, $delta);
my $SECS_of_70_YEARS = 2_208_988_800;
socket(MsgBox, PF_INET, SOCK_DGRAM, getprotobyname("udp"))
or die "socket: $!";
$him = sockaddr_in(scalar(getservbyname("time", "udp")),
inet_aton(shift || '127.1'));
derined(send(MsgBox, 0, 0, $him))
or die "send: $!";
defined($src = recv(MsgBox, $ptime, 4, 0)) or die "recv: $!";
(Sport-, $ipaddr) = sockaddr_in($src);
$host = gethostbyaddr($ipaddr, AF_INET);
my $delta = (unpack("N", $ptime) - $SECS_of_70_YEARS) - time();
print "Clock on $host is $delta seconds ahead of this one.\n";

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

Смотри также --------------------------------
Описание функций send, recv, gethostbyaddr и unpack в perlfunc(1); документация по стандартным модулям Socket и IO::Socket; раздел "UDP: message passing" perlipc(1); рецепт

17.5. Создание сервера UDP

Проблема

Вы хотите написать сервер UDP.

Решение

Сначала вызовите функцию bind для номера порта, но которому будет осуществляться связь с вашим сервером. С модулем IO::Socket это делается просто:
use 10::Socket;
$server = 10::Socket::INET->new(LocalPort => $server_port,
Proto => "udp")
or die "Couldn't be a udp server on port $server_port : $@\n";

Затем в цикле принимайте сообщения:
while ($him = $server->recv($datagram, $MAX_TO_READ, $flags)) {
# Обработать сообщение
}

Комментарий

Программирование для UDP намного проще, чем для TCP. Вместо того чтобы последовательно принимать клиентские подключения и вступать в долгосрочную связь с клиентом, достаточно просто принимать сообщения от клиентов по мере их поступления. Функция recv возвращает адрес отправителя, подлежащий декодированию.
В примере 17.2 показан небольшой сервер UDP, который просто ожидает сообщений. Каждый раз, когда приходит очередное сообщение, мы выясняем, кто его послал, и отправляем ответ-сообщение с принятым текстом, после чего сохраняем новое сообщение.
#!/usr/bin/perl -w
# udpqotd - сервер сообщений UDP use strict;
use 10::Socket;
my($sock, $oldmsg, $newmsg, $hisaddr, $hishost, $MAXLEN, $PORTNO):
$MAXLEN = 1024;
$PORTNO = 5151;
$sock = 10::Socket::INET->new(LocalPort => $PORTNO, Proto => 'udp')
or die "socket: $@";
print "Awaiting UDP messages on port $PORTNO\n";
$oldinsg = "This is the starting message.";
while ($sock->recv($newmsg, $MAXLEN)) {
my($port, $ipaddr) = sockaddr_in($sock->peername):
$hishost = gethostbyaddr($ipaddr, AF_INET);
print "Client $hishost said ''$newmsg'"\n";.
$sock->send($oldmsg);
$oldmsg = "[$hishost] $newmsg":
} die "recv: $!";

С использованием модуля IO::Socket программа получается проще, чем с низкоуровневым модулем Socket. Нам не приходится указывать, куда отправить сообщение, поскольку библиотека сама определяет отправителя последнего сообщения и сохраняет его в объекте $sock. Метод peername извлекает данные для декодирования. Программа telnet не подходит для общения с этим сервером; для этого необходим специальный клиент. Один из вариантов приведен в примере 17.3. Пример 17.3. udpmsg
#!/usr/bin/perl -w
# udpmsg - отправка сообщения серверу udpquotd
use 10::Socket;
use strict;
my($sock, $server_host, Smsg, Sport, $ipaddr, $hishost, $MAXLEN, $PORTNO, $TIMEOUT);
$MAXLEN = 1024;
$PORTNO = 5151;
$TIMEOUT = 5;
$server_host = shift;
Smsg = "@ARGV";
$sock = 10::Socket::INET->new(Proto => 'udp',
PeerPort => $PORTNO,
PeerAddr => $server_host)
Пример 17.3 (продолжение)
or die "Creating socket: $!\n";
$sock->send($msg) or die "send: $!";
eval {
local $SIG{ALRM} = sub { die "alarm time out" };
alarm $TIMEOUT;
$sock->recv($msg, $MAXLEN) or die "recv: $!";
alarm 0;
1; # Нормальное возвращаемое значение
eval } or die "recv from $server_host timed out after $TIMEOUT seconds.\n";
(Sport, $ipaddr) = sockaddr_in($sock->peername);
$hishost = gethostbyaddr($ipaddr, AF_INET);
print "Server $hishost responded ''$msg''\n";
При создании сокета мы с самого начала указываем хост и номер порта, что по зволяет опустить эти данные при вызовах send.
Тайм-аут (alarm) был добавлен на случай, если сервер не отвечает или вообще не работает. Поскольку recv является блокирующей системной функцией, выход из которой может и не произойти, мы включаем ее в стандартный блок eval для прерывания блокировки по тайм-ауту.

> Смотри также -------------------------------
Описание функций send, recv и alarm в perlfunc(l); документация по стандартным модулям Socket и IO::Socket; раздел "UDP: message passing" perlipc(1); рецепты 16.21; 17.4.
© copyright 2000 Soft group



?????? ???????????


?????? ???????????