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

Самоучитель PHP 4 - Котеров Д. В.

.pdf
Скачиваний:
99
Добавлен:
24.05.2014
Размер:
4 Мб
Скачать
☆

378

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

жем, id. Этот самый id уникален у каждой записи, поэтому мы можем, зная id нужного нам человека, тут же получить его данные. Кроме того, если нам понадобится, например, зафиксировать в таблице еще и родственные связи людей (кто является чьим отцом, например), мы можем завести в ней еще одно поле — parent_id, в котором будет храниться id родителя конкретного человека. Таким образом, описанная техника оказывается довольно удобной.

Пусть теперь мы хотим добавить в таблицу сведения о еще одном человеке. Логично было бы, чтобы его id проставлялся автоматически. Возникает вопрос: как нам этот id вычислить? В самом деле, мы же не знаем, какие id в таблице в данный момент "свободны"... Можно использовать для этой цели текущее время в секундах. Но вдруг именно в данную секунду кто-то еще точно так же добавляет в базу данных запись? Можно, конечно, взять максимальный id, прибавить к нему единичку и занести в таблицу. Но где гарантия, что, опять же в этот момент другой администратор не проделал ту же операцию — до того, как вы добавили свою информацию, но после того, как определили максимальный id?

Как раз для решения этой проблемы и предназначена в MySQL возможность под названием AUTO_INCREMENT. А именно, при создании таблицы мы можем какоенибудь ее поле (в нашем случае как раз id) объявить так:

ИмяПоля int auto_increment primary key

Немного длинновато, но это стоит того! Теперь любая операция INSERT автоматически проставит у добавленной записи поле ИмяПоля так, чтобы оно было уникально во всей таблице — MySQL это гарантирует. В простейшем случае — просто увеличит на 1 некий внутренний счетчик, глобальный для всей таблицы, и занесет его новое значение в нужное поле записи. Причем гарантируется, что никакие проблемы с совместным доступом к таблице просто не могут возникнуть, как это произошло бы, используй мы "кустарные" способы.

Получить только что вставленный идентификатор можно при помощи функции mysql_insert_id().

int mysql_insert_id([int $link_identifier])

Функция возвращает непосредственно перед ее вызовом сгенерированный идентификатор записи для автоинкрементного поля после выполнения команды insert. Вызывать ее разумно только сразу после выполнения инструкции insert, например, в таком контексте:

mysql_query("insert into Таблица(поле1, поле2) values('aa','bb')"); $id=mysql_insert_id();

Теперь к только что вставленной записи можно обратиться, используя идентификатор $id:

$r=mysql_query("select * from Таблица where id=$id"); $Row=mysql_fetch_array($r);

Глава 26. Работа с базой данных MySQL

379

Работа с таблицами

Вот и подходит к концу наш разговор о функциях поддержки MySQL в PHP. Мы познакомились с большинством функций, которые встроены в PHP для взаимодействия с этой СУБД. Мы еще вернемся к MySQL в части V книги.

Подводя черту, имеет смысл рассмотреть несколько функций, имеющих отношение к работе с таблицами в целом.

int mysql_list_fields(string $dbname, string $tblname [,int $link])

Функция mysql_list_fields() возвращает информацию об указанной таблице $tblname в базе данных $dbname, используя идентификатор соединения $link, если он задан (в противном случае — последнее открытое соединение). Возвращаемое значение — идентификатор результата, который может быть проанализирован обычными средствами, либо при помощи функций mysql_field_flags(), mysql_field_len(), mysql_field_name() и mysql_field_type(). В случае ошибки возвращается –1, текст сообщения ошибки может быть получен обычным способом.

int mysql_list_tables(string $database [,int $link_identifier])

Функция возвращает идентификатор результата (одна колонка), в котором содержатся имена всех таблиц, присутствующих в базе данных. Для извлечения этих имен можно использовать функцию mysql_result() с номером колонки, равным 0.

Глава 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