Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель по PHP 4

.pdf
Скачиваний:
82
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 27

Сетевые функции

Здесь я коротко рассмотрю некоторые сетевые функции, предоставляемые PHP. За более детальной информацией обращайтесь к сопроводительной документации.

Работа с сокетами

Помните, до этого мы обсуждали функцию fopen() и замечали, что ее можно использовать и для открытия сетевых соединений с файлами на других хостах в Сети. Однако функция fopen() позволяла работать лишь с содержимым файла, переданного по протоколу HTTP. Но ведь по HTTP, кроме "тела" документа, передаются также некоторые заголовки, посланные сервером. "Добраться" до них всех и позволяет функция fsockopen().

int fsockopen(string $host, int $port [,int &$errno] [,string &$errstr])

Эта функция работает аналогично fopen(), но только устанавливает сетевое соединение с указанным хостом $host и программой, закрепленной на нем за портом $port. Она возвращает файловый дескриптор, с которым затем могут быть выполнены обычные операции: fread(), fwrite(), fgets(), feof() и т. д. В случае ошибки, как обычно, возвращается false и, если заданы параметры-переменные $errno и $errstr, в них записываются соответственно номер ошибки (не равный нулю) и текст сообщения об ошибке. Если функция вернула false, но $errno тем не менее сбросилась в 0, это скорее всего означает, что произошла ошибка инициализации сокета. Например, такое может произойти, если в Windows не установлен требуемый протокол TCP/IP.

Функция fsockopen() поддерживает и так называемые сокеты домена Unix, которые представляют собой в этой системе специальные файлы, наподобие каналов. Для использования такого режима нужно установить $port в 0 и передать в $host имя файла сокета. Мы не будем останавливаться на этом режиме, т. к. он специфичен для ОС Unix.

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

если вызовете функцию socket_set_blocking() (см. ниже).

В примере из листинга 27.1 мы "проэмулировали" браузер, послав в порт 80 удаленного хоста HTTP-запрос GET и получив весь ответ вместе с заголовками. Мы используем функцию HtmlSpecialChars(), чтобы вывести HTML-код документа в текстовом формате.

382

Часть IV. Стандартные функции PHP

Листинг 27.1. "Эмуляция" браузера

<?

//Соединяемся с Web-сервером www.php.net $fp = fsockopen("localhost", 80);

//Посылаем запрос главной страницы сервера fputs($fp,"GET / HTTP/1.0\n\n");

//Теперь читаем по одной строке и выводим ответ echo "<pre>";

while(!feof($fp))

echo HtmlSpecialChars(fgets($fp,1000)); echo "</pre>";

//Отключаемся от сервера

fclose($fp); ?>

Разумеется, никто не обязывает нас использовать именно 80-й порт. Даже наоборот: функция fsockopen() универсальна. Мы можем использовать ее и для подключения к telnet-порту, и к FTP — словом, для чего угодно.

int socket_set_blocking(int $sd, int $mode)

Эта функция устанавливает блокирующий или неблокирующий режим для соединения, открытого ранее при помощи fsockopen(). В режиме блокировки ($mode=true) функции чтения будут "засыпать", пока передача данных не завершится. Таким образом, если данных много, или же произошел какой-то "затор" в сети, ваша программа остановится и будет дожидаться выхода из функции чтения. В режиме запрета блокировки ($mode=false) функции наподобие fgets() будут сразу же возвращать управление в программу, даже если через соединение не было передано еще ни одного байта данных. Таким образом, считывается ровно столько информации, сколько доступно на данный момент. Определить, что данные кончились, можно с помощью функции feof(), как это было сделано в примере из листин-

га 27.1.

Функции для работы с DNS

Здесь мы рассмотрим несколько очень полезных функций для работы с DNSсерверами и IP-адресом.

Глава 27. Сетевые функции

383

Разрешение IP-адреса в доменное имя и наоборот

string gethostbyaddr(string $ip_address)

Функция возвращает доменное имя хоста, заданного своим IP-адресом. В случае ошибки возвращается $ip_address.

Функция не гарантирует, что полученное имя будет на самом деле соответ- ствовать действительности. Она лишь опрашивает хост по адресу $ip_address и просит его сообщить свое имя. Владелец хоста, таким образом, может передать все, что ему заблагорассудится. Как обойти эту проблему, см. чуть ниже.

string gethostbyname(string $hostname)

Функция получает в параметрах доменное имя хоста и возвращает его IP-адрес. Если адрес определить не удалось, возвращает $hostname.

array gethostbynamel(string $hostname)

Эта функция очень похожа на предыдущую, но возвращает не один, а все IP-адреса хоста с именем $hostname. Как мы знаем, одному доменному имени может соответствовать сразу несколько IP-адресов, и в случае сильной загруженности серверов DNS-сервер сам выбирает, по какому IP-адресу перенаправить запрос. Он выбирает тот адрес, который использовался наиболее редко.

Обратите внимание на то, что в Интернете существует множество виртуальных хостов, которые имеют различные доменные имена, но один и тот же IP-адрес. Таким образом, если следующая последовательность команд для существующего хоста с IPадресом $ip всегда печатает этот же адрес:

$host=gethostbyaddr($ip); echo gethostbyname($host);

то аналогичная последовательность для домена с DNS-именем $host, наоборот, может напечатать не то же имя, а другое:

$ip=gethostbyname($host); echo gethostbyaddr($ip);

Корректный перевод IP-адреса в доменное имя

Функция gethostbyaddr() на первый взгляд проста и привлекательна, но с ней связан один нюанс, который до недавнего времени было принято игнорировать. Дело в том, что при поиске доменного имени машины по заданному IP-адресу PHP обращается к хосту по этому адресу и запрашивает у него доменное имя. Если хостом

384

Часть IV. Стандартные функции PHP

владеет злоумышленник, он может перехватить эту операцию и возвратить вам все, что ему (а не вам) будет угодно!

Рассмотрим это на примере. Пусть вам надо определить доменное имя компьютера, расположенного по адресу 195.84.12.34. Давайте предположим, что эта машина принадлежит симпатичному хакеру, который настроил свой DNS-сервер так, чтобы он говорил: "Я являюсь хостом whitehouse.gov", если его об этом спросят по адресу 195.84.12.34. Так что, выполнив код:

echo gethostbyaddr("195.84.12.34");

мы получим вывод whilehouse.gov. Произошла подмена!

Как же нам быть? А вот как. Предположим, мы получили от хоста с некоторым IPадресом информацию, что его "зовут" whitehouse.gov. Обратимся же к нему и получим его IP-адрес, а потом сравним, тот ли это адрес, который мы запрашивали вначале:

$ip="195.84.12.34"; $host=gethostbyaddr($ip);

// Если была ошибка, $host==$ip if($host==$ip) die("Неверный ip-адрес $ip!"); $check_ip=gethostbyname($host);

// Если была ошибка, $check_ip==$host if($check_ip==$host) die("Неверное доменное имя $host!"); // Ну вот, теперь сверяем данные

if($ip==$check_ip)

echo "По адресу $ip расположен хост $host"; else

echo "По адресу $ip расположен хост злоумышленника!!!";

Длинно? Да. Но это еще, к сожалению, не все. Ведь у одного и того же доменного имени могут быть сразу несколько IP-адресов. Нас устроит, если хотя бы один из них совпадет с затребованным нами. Так что придется воспользоваться функцией gethostbynamel() и циклом перебора списка IP-адресов. Вот что у нас получится:

Листинг 27.2. Безопасная функция получения доменного имени

<?

//Аналог функции gethostbyaddr(), но всегда проверяет,

//не подменил ли злоумышленник по адресу $ip имя своего

//хоста на чужое. В последнем случае просто возвращает false. function safe_gethostbyaddr($ip)

{// Получаем предполагаемое имя $host=gethostbyaddr($ip);

Глава 27. Сетевые функции

385

//Адреса не существует? Не фатально — вернем то, что есть. if($host==$ip) return $host;

//Теперь спрашиваем $host, кто он такой.

$check_ips=gethostbynamel($host);

// Есть ли среди адресов, которые он вернул, затребованный? foreach($check_ips as $check_ip) {

// Если нашли, то $host достоверен — возвращаем его. if($ip==$check_ip) return $host;

}

// Иначе, если ни один адрес не совпал, выходим return false;

}

// Теперь посмотрим, что из себя представляет наш адрес...

echo safe_gethostbyaddr("195.84.12.34"); ?>

Вот теперь все будет работать корректно. Однако за все приходится платить: safe_gethostbyaddr() требует гораздо больших затрат времени, чем gethostbyaddr(), потому что нам приходится дополнительно обращаться еще как минимум к одной машине. Если безопасность для вас важнее, чем какие-то пара секунд простоя, используйте safe_gethostbyaddr().

ЧАСТЬ V.

ПРИЕМЫ

ПРОГРАММИРОВАНИЯ НА PHP

Глава 28

Загрузка файлов на сервер

Иногда бывает просто необходимо позволить пользователю не только заполнить текстовые поля формы и установить соответствующие флажки и радиокнопки, но также и указать несколько файлов, которые будут впоследствии загружены с компьютера пользователя на сервер. Для этого в языке HTML и протоколе HTTP предусмотрены специальные средства.

Чтобы не применять двусмысленной терминологии, я буду использовать слово "закачать" для обозначения загрузки файла клиента на сервер, и термин "ска- чать" для иллюстрации обратного процесса (с сервера клиенту). Я уже предчувствую, как будет недоволен, услышав об этом, редактор книги, и он в чем-то прав. Так что, уважаемый читатель, если вы читаете здесь эти рассуж- дения, — значит, я победил в споре, а если не читаетеЧто ж, вы об этом и не догадаетесь.1

Мы уже рассматривали механизм, который применяется при закачке файлов, в главе 3. Вы, возможно, помните, что он выглядел не очень-то привлекательно. На мой взгляд, закачка файлов и вообще работа с multipart-методом передачи формы — до-

1 Русский язык, изначально обладающий гигантской свободой в выборе слова, постоянно развивается. То, что казалось неприемлемым вчера, сегодня становится нормой, и наоборот. Безусловно, у любого обратившего внимание на эти строки при прочтении слов "закачать" и "скачать" вряд ли возникнут ассоциации с бригадой мускулистых администраторов, придающих передаваемым по сети файлам необходимую кинетическую энергию для последующего перемещения под напором, или другие неверные мысли, несмотря на большое количество смысловых оттенков употребления этих слов (убаюкивать, вызвать головокружение, или же в понимании "подлого приема садовников, торговцев присадками (раскачивать деревце, не давая ему укорениться)"). Вообще говоря, о твердых правилах в условиях возрастающего слияния разговорных терминов и литературного языка говорить не приходится. В толковом словаре С. И. Ожегова и Н. Ю. Шведовой дано пояснение идиоме "Закачаешься!" как выражения высокой оценки чего-либо. Редакторы вовсе не стремятся убивать живое изложение, и поэтому если вы, уважаемые читатели, также видите эти строки, значит было решено — "быть закачиваемому" (из примеров к статье "Закачать" толкового словаря живого великорусского языка В. И. Даля). — Ред.

390

Часть V. Приемы программирования на PHP

вольно нетривиальные задачи. Однако спешу обрадовать: в PHP все это давно реализовано и отлажено. Но обо всем по порядку.

Multipart-формы

Мы помним, что в большинстве случаев данные из формы в браузере, передающиеся методом GET или POST, приходят к нам в одинаковом формате:

поле1=значение1&поле2=значение2&...

При этом все символы, отличные от "английских" букв и цифр (и еще некоторых) URL-кодируются: заменяются на %XX, где XX — шестнадцатеричный код символа. Это сильно замедляет закачку больших файлов.

В принципе, multipart-формы призваны одним махом решить эту проблему. Нам нужно в соответствующем тэге <form> задать параметр:

enctype=multipart/form-data

После этого данные, полученные от нашей формы, будут разбиты на несколько блоков информации (по одному на каждый элемент формы). Каждый такой блок очень похож на обычную посылку "заголовки-данные" протокола HTTP:

-----------------Идентификатор_начала\n

Content-Disposition: form-data; name="имя" [;другие параметры]\n \n

значение\n

Браузер автоматически формирует строку Идентификатор_начала из расчета, чтобы она не встречалась ни в одном из передаваемых файлов (и ни в одном из других полей формы). Это означает, что сегодня идентификатор будет одним, а завтра, возможно, совсем другим.

Тэг выбора файла

Давайте просмотрим, какой тэг надо вставить в форму, чтобы в ней появился элемент управления загрузкой файла — текстовое поле с кнопкой Browse справа. Таким тэгом является разновидность <input>:

<input type=file name=имя_элемента [size=размер_поля]>

Сценарию вместе с содержимым файла передается и некоторая другая информация, а именно:

rразмер файла;

rимя файла в системе клиента;

rтип файла.