Клієнт TCP h2>
Без
мереж зараз нікуди, це справедливо і для програміста і для железячніка. Стандартом
де-факто в організації сучасних мереж став протокол TCP, розуміння якого
ще на крок наблизить нас до професіоналів. До того ж, ознайомившись з
принципами мережевого обміну даними і з'єднавши ці знання з нашими знаннями
в області між процесами ми зможемо писати цілком гідні
програми. Передбачається, що ви вже маєте деякі розуміння в принципів організації
мереж на базі TCP/IP. По крайней мере, ви повинні знати що таке доменне ім'я,
IP-адресу та порт. Отже, приступимо. p>
Сервери та клієнти b>
p>
Сокети
бувають декількох типів: потокові і датаграммним. Датаграммним сокети НЕ
гарантують збереження даних для передачі. Сокети цього типу підходять хіба
що для обміну повідомленнями для користувача, тому загострювати увагу на них
ми не будемо. Потокові сокети забезпечують надійний двосторонній обмін даними.
p>
Робота
з сокетами дуже схожа на роботу з каналами. Крім типів, сокети
підрозділяються по областях дії: INET і UNIX. Область дії визначає
метод ідентифікації сокета: для інтернет-сокетів це адреса хоста і номер порту,
а для гнізд UNIX - ім'я файлу. Далі мова піде переважно про потокових
інтернет-сокетах. p>
В
випадку з каналами ми просто пов'язували два дескриптора за допомогою функції pipe.
Таким чином ми отримували односторонній канал зв'язку. У випадках, коли нам було
необхідно забезпечити двосторонній зв'язок нам доводилося напружуватися трохи
більше: створювати два канали, перевизначати потоки і т.п. Потокові сокети
забезпечують двосторонній зв'язок. Тобто, ми можемо читати і писати з кожної
сторони з'єднання. Але програмування сокетів дещо ускладнюється через
можливості мережевого з'єднання. На відміну від каналів, сокети можуть створюватися
на різних машинах з'єднаних мережею (що підтримує протокол TCP/IP). Для того,
що б як то позбутися невизначеності дій при створенні з'єднання
введені такі поняття як сервер і клієнт. У контексті цієї статті сервером
будемо називати процес, який створює сокет і очікує підключення клієнтів.
Клієнти - це ті процеси, які намагаються ініціювати з'єднання з сервером.
При цьому, не важливо на якій машині в мережі буде знаходитися клієнт: він може
ініціювати з'єднання з сервером на локальній машині або на віддаленій. p>
В
Загалом, процес з'єднання можна розділити на кілька етапів. Насамперед,
необхідно отримати ідентифікатор створюваного сокета (інакше - ім'я) незалежно
від того, клієнт це чи сервер. Далі, і клієнт і сервер повинні створити сокет з
вказаним ім'ям. Після цього сервер переходить в режим очікування вхідних
підключень, а клієнт спробує підключення до сервера. У разі
успішного проходження цих етапів ми маємо готове підключення: дескриптор
читання/запису у сервера і аналогічний у клієнта. Тепер можна приступати до
обміну даними. У процесі розбору принципів Взаємодія між процесами ми
вже стикалися з проблемою відкритих дескрипторів. Так от, з сокетами той же
саме: не забувайте закривати дескриптори. Втім, ми напевно ще зіткнемося
з цією проблемою, адже у нас попереду ціла купа підводних каменів, яку
доведеться розгрібати при роботі з сокетами. p>
Простий приклад
p>
Для
початку розберемо простий приклад - отримання документа по протоколу HTTP.
Існує спеціальний інтерфейс полегшує роботу з сокетами з Perl --
IO:: Socket. Однак, ми не будемо використовувати IO:: Socket, так як ми бажаємо
отримати максимальний контроль над з'єднанням. Хоча реально, використання
IO:: Socket значно полегшує роботу з сокетами, та до того ж значно
знижує ризик допустити помилку. При вирішенні реальних завдань я рекомендую
використовувати IO:: Socket. p>
Отже,
я назвав програму "gethttp.pl". Нам знадобиться модуль Socket, в
якому реалізовані всі необхідні платформозавісімие функції. Перш за все,
це стосується функції ідентифікації сокетів. Але, все по порядку. p>
#!/usr/bin/perl-w p>
# gethttp.pl p>
use Socket; p>
use strict; p>
unless ($ # ARGV == 1) ( p>
die "Usage: gethttp.pl [host] [document] n"; p>
) p>
my $ sock_name =
GetSockName ($ ARGV [0], 80) p>
or die "Couldn't convert $ ARGV [0] into an Internet address:
$! n "; p>
socket (CONN, PF_INET, SOCK_STREAM, getprotobyname ( 'tcp')); p>
connect (CONN, $ sock_name) p>
or die "Couldn't 'connect to $ ARGV [0]: $! n"; p>
print CONN "GET $ ARGV [1]
HTTP/1.0n "; p>
print CONN "Host:
$ ARGV [0] nn "; p>
my @ body =; p>
close (CONN); p>
print
join ("",body); p>
Після
підключення модуля Socket ми перевіряємо чи достатньо аргументів передано на
вхід програми. Відповідно до опису, запуск програми повинен аргументувати
двома значеннями: ім'ям хоста і віртуальним шляхом одержуваного документа. Так
Отож, якщо масив вхідних аргументів не містить необхідних двох аргументів,
тоді програма завершується. p>
Наступний
крок виконує ідентифікацію сокета. Функція GetSockName () приймає в якості
аргументу ім'я хоста (або його IP-адреса) та номер порту. Если кому то незрозумілий
сенс термінів хост і порт, то можна провести таку аналогію: щоб потрапити в
гості до свого знайомого ви повинні знати номер будинку, номер квартири. Ось тут
номер будинку - це IP-адреса комп'ютера, до якого здійснюється підключення, а
квартира - номер порту. p>
Реалізація
функції ідентифікації сокета на мій погляд дуже зручна, оскільки приховує всі
незрозумілості. До того ж, як я вже казав, визначення імені сокета це
обов'язковий процес як для сервера, так і для клієнта. З цього ми з
легкістю зможемо використовувати функцію GetSockName () в наших наступних
програмах. p>
Після
ідентифікації сокета ми створюємо сокет за допомогою вбудованої функції socket (). У
Як аргументи ця функція приймає дескриптор з'єднання (на замітку:
звичайний файловий маніпулятор), константу, що визначає область дії сокета
(PF_INET або PF_UNIX), константу, що визначає тип сокета (датаграмний або
потоковий) і ідентифікатор протоколу. Ідентифікатор протоколу відповідний
встановленому в системі визначається числовим значенням В.С допомогою функції
getprotobyname (). p>
Після
того, як сокет створений, ми намагаємося з'єднатися з віддаленим сервером. Робиться
це за допомогою функції connect (), як аргументи якої приймається
дескриптор та ідентифікатор сокета. У разі невдалої спроби з'єднання
програма завершується з помилкою. Далі оператори print, які дотримуючись правил
протоколу HTTP оголошують про необхідність видати зазначений документ. У масив
@ body ми зберігаємо дані, отримані від сервера. Закриваємо з'єднання та
виводимо вміст масиву @ body. Ось і наш документ. p>
Функція GetSockName b>
p>
Тепер
розберемо функцію GetSockName (). p>
sub GetSockName ( p>
my ($ nm, $ pt) =_; p>
return undef unless defined ($ nm); p>
return undef unless defined ($ pt); p>
return undef unless $ nm = gethostbyname ($ nm); p>
return sockaddr_in ($ pt, $ nm); p>
) p>
Ось
така нехитра функція рятує нас від головного болю при ідентифікації
сокета. За допомогою функції gethostbyname () ми отримуємо IP-адреса хоста
призначення. Інакше це називається роздільною здатністю імені. Функція sockaddr_in () входить
до складу модуля Socket. У скалярному контексті sockaddr_in упаковує номер
порту і адреса хоста в структуру SOCKADDR_IN. p>
Тепер
давайте спробуємо, що ж у нас вийшло. Запускаємо програму і ... Ха-ха-ха.
Що, знову зависли? Ну і де ж у нас помилка? Правильно, ми забули відключити
буфферізацію. Дані ніби відправлені, але їх занадто мало для заповнення
буферу. Тому фактично, наші ви надали є застрягли в сокеті. А
сервер чекає і чекає, коли ж клієнт заявить про своє бажання. Давайте виправимо
становище і додамо наступний код між рядків: p>
connect (CONN, $ sock_name) p>
or die "Couldn't 'connect to $ ARGV [0]: $! n"; p>
select (CONN); $ | = 1; select (STDOUT); p>
print CONN "GET $ ARGV [1]
HTTP/1.0n "; p>
Ось
тепер все має працювати. p>
Висновок
p>
Наша
програма, звичайно, не супер-пупер, але на дещо вона все-таки згодиться.
Наприклад, якщо відокремити заголовки відповіді від, безпосередньо, тіла відповіді, то
можна викачувати файли по HTTP. Давайте так і зробимо. p>
Як
нам відомо, відповідь сервера складається із заголовка і тіла. При чому, заголовок
відповіді відділяється від тіла двома перекладами рядка. От і знайдемо ці два
перекладу рядки: p>
print
CONN "GET $ ARGV [1] HTTP/1.0n"; p>
print CONN "Host:
$ ARGV [0] nn "; p>
my $ body = ""; p>
$ body .= $ _ while; p>
close (CONN); p>
$ body = ~ s /^.+? nr? nr?// s; p>
print
$ body; p>
Тепер
ми можемо виконати команду: p>
[root @ avalon scripts] #./gethttp.pl
whirlwind.ru/index.cgi> index.html p>
для
отримання HTML-коду індексного сторінки нашого сайту. p>
Ну
і все, для початку. У вас вже досить навичок для метушні з протоколами
високого рівня. От і спробуйте попрацювати з FTP, або SMTP. p>
Список літератури h2>
Для
підготовки даної роботи були використані матеріали з сайту http://prolib.ru/
p>