Колисниченко ДенисСетевые функции PHP
Сетевых функций PHP не так уж и много: в основном работа с сетевыми сервисами осуществляется через сокеты. Сокет во многом похож на дескриптор файла (то есть с сетевым соединением можно работать как с обычным файлом), но служит для несколько иных целей, а именно для передачи информации по сети.
Примечание. Сокетом называется двунаправленный канал, используемый для передачи входящих и исходящих данных между двумя компьютерами в сети.
Функция fopen() позволяет открывать удаленные файлы, которые находятся на Web-серверах. Но для многих задач этого явно не достаточно. Получить файл по протоколу HTTP просто, но как реализовать отправку электронных сообщений, используя протокол SMTP, или любую другую сетевую функцию на каком-либо другом, отличном от НТТР протоколе? Конечно, в случае отправки письма можно вызвать программу sendmail (или mail) и передать ей письмо, чтобы она, в свою очередь, его отправила. Но это не очень удобно, потому что:
А что делать, если программа не установлена или мы используем Windows? Как отправить сообщение? РНР позволяет использовать более гибкий инструмент - сокеты, которые позволяют установить соединение по любому протоколу. Это значит, что мы можем "общаться" с любой сетевой программой (нам только нужно знать протокол обмена) и реализовать любую сетевую задачу.
В этой статье мы будем работать с протоколом SMTP (Simple Mail Transfer Protocol - Простой протокол отправки сообщения) - он не очень сложен, да и не будет необходимости писать еще одну статью - по отправке сообщений через сокеты.
Для открытия сокета используется функция fsockopen ():
int fsockopen(string $host, int $port [, int $err] [, string $err_msg])
Первый параметр функции - это имя узла, к которому мы хотим подключиться, или его IP-адрес. Второй параметр - это порт службы. В нашем случае с отправкой письма - это 25 (SMTP). При возникновении ошибки в переменные $err и $err_msg, если они заданы (они являются необязательными), будут записаны соответственно код ошибки и текст сообщения об ошибке.
После открытия сокета мы можем работать с ним как с обыкновенным файлом, то есть использовать функции fwrite(), fread(), fgets(), fputs() и другие. В отличие от канала, сокет открывается как для чтения, так и для записи.
Работу с протоколом SMTP через сокет мы рассмотрим немного позже, а сейчас рассмотрим пример, демонстрирующий работу через сокет с протоколом HTTP:
Листинг 1. Работа с протоколом HTTP - эмуляция браузера <? // Открываем сокет $s = fsockopen("localhost",80); // Отправляем команду GET - получить index.html (/) fputs($s, "GET / HTTP/1.0\n\n"); // Читаем ответ while (!feof($s)) echo fgets($s,1000); // Закрываем сокет fclose($s); ?>
Данный сценарий подключится к нашему локальному Web-серверу (имя сервера вы можете изменить), отправит команду GET протокола HTTP, которая дословно читается так: получить файл / по протоколу HTTP версии 1.0. Потом наш сценарий читает ответ сервера. В любом случае ответом будет Web-страница: или сообщение сервера об ошибке или запрашиваемая нами Web-страница.
Функция soket_set_blocking() является псевдонимом для функции stream_set_blocking( resource stream, int mode). Если параметр mode принимает значение FALSE, поток переводится в неблокирующий режим, если TRUE - выполняется блокировка потока. Функция stream_set_blocking() влияет на работу функций fread() и fgets(). В блокирующем режиме функция fgets() будет ждать до тех пор, пока не прочитает данные из потока (а этот момент может и не настать). В неблокирующем режиме функция fgets(), не прочитав данные, завершает свою работу.
Примечание. Функция soket_set_blocking() (как и stream_set_blocking()) появилась в версии PHP 4.3.0. Если у вас версия PHP младше, вы не сможете использовать режим блокировки потока.
Чтобы отправить письмо, мы должны знать, как сделать это вручную. Нет, не нажать кнопку "Новое сообщение", а затем - "Отправить". Мы должны знать сам протокол SMTP, то есть что почтовая программа отправляет серверу, и какие данные от него получает. Не зная этого, мы не сможем написать сценарий для работы с протоколом SMTP.
Запустите программу telnet:
telnet SMTP-узел 25
Если у вас Linux и программа sendmail (или другой агент MTA, например, postfix) установлена, можно использовать локальный хост - так будет даже лучше. Введите:
telnet localhost 25
Вы должны увидеть приветствие программы MTA (Mail Transfer Agent - Агент передачи почты).
220 localhost.localdomain ESMTP Sendmail 8.11.6/8.11.6; Wed, 16 Apr 2003 13:24:56 +0300
Программа поприветствовала нас и ждет нашей команды. Поздороваемся с программой:
HELO localhost
Обратите внимание, что слово "привет" пишется HELO, а не HELLO. Вместо localhost нужно указать ваш узел, если у вас установлен агент MTA.
На наше приветствие программа ответит:
250 localhost.localdomain Hello localhost.localdomain [127.0.0.1], pleased to meet you
Данное сообщение означает, что нам разрешена отправка почты.
Примечание. Что делать, если вы работаете в Windows и у вас не установлен MTA? Тогда запустите программу telnet (она есть в любой операционной системе) и подключитесь к серверу провайдера: telnet mail.isp.ru 25. В команде helo нужно ввести имя своей машины, узнать которое поможет программа ipconfig (или ее аналог - winipcfg).
Рис. 1. Программа winipcfg
После взаимного приветствия нужно представиться:
MAIL FROM: den@localhost
В команде MAIL FROM нужно указать свой адрес электронной почты. Ответ программы выглядит так:
250 2.1.0 den@localhost... Sender ok
Программа сообщает, что отправитель допустим. Потом нужно указать адрес реципиента:
RCPT TO: evg@localhost Ответ программы: 250 2.1.5 evg... Recipient ok
Вот теперь можно послать команду DATA, что означает, что мы будем передавать письмо.
DATA
Программа сообщит нам, что для окончания ввода нужно нажать "Enter . Enter":
354 Enter mail, end with "." on a line by itself
Введите сообщение, например, Test Message. Потом нажмите "Enter . Enter" и увидите сообщение о том, что наше письмо поставлено в очередь на отправку:
250 2.0.0 h3GAOup01391 Message accepted for delivery
Сейчас можно заново повторить команды MAIL FROM, RCPT TO, DATA для создания нового сообщение, но мы этого делать не будем, поэтому введите команду QUIT.
Приведенный пример демонстрирует обмен почтовой программой и сервером по протоколу SMTP. Конечно, пример очень тривиален, потому что мы не использовали ни заголовком, ни аутентификации, но сейчас речь не о них. Главное, чтобы вы уловили идею работы с сокетом: нужно отправить команду, прочитать результат, отправить следующую команду, опять прочитать результат и т.д. Следующий сценарий автоматизирует проделанную выше работу:
Листинг 2. Сценарий для отправки сообщения через сокеты <? $CRLF = "\r\n"; $r=$loops = 0; $m =$line = ""; // Открываем сокет $s = fsockopen("localhost","25", $r, $m); // Функция чтения ответа function get_data() { $return = ""; global $line, $CRLF, $loops, $s; while((strpos($return, $CRLF) === FALSE OR substr($line,3,1) !== ' ') AND $loops < 100){ $line = fgets($s, 512); $return = $line; echo $line; // loops++; } } // Функция отправки команды function send_data($data) { global $s, $CRLF; fwrite($s, $data.$CRLF, strlen($data)+2); } // Читаем приветствие get_data(); // Здороваемся send_data("HELO localhost"); // Читаем ответ get_data(); // Представляемся send_data("MAIL FROM: den@localhost"); // Можно ли нам отправлять почту? get_data(); // Указываем адрес реципиента send_data("RCPT TO: evg"); // Правильный ли адрес? get_data(); // Сейчас будем отправлять сообщение send_data("DATA"); // Читаем ответ сервера get_data(); // А это само сообщение, после которого следует нажатие Enter . Enter send_data("Message".$CRLF.".".$CRLF); // Поставлено ли сообщение в очередь get_data(); // Закрываем соединение send_data("QUIT"); // Читаем ответ get_data(); // закрываем сокет fclose($s); ?>
Вывод программы вы можете увидеть на рисунке 2.
Рис. 2. Отправление письма (ответы сервера)
Обратите внимание, что при работе с MTA нажатие "Enter" - это не только один символ \n как в Unix, а целых два символа - \r\n - как в Windows.
Функция mail() позволяет быстро отправить сообщение, используя PHP. Формат этой функции таков:
mail(string $email, string $subject, $string $message [,string $haders]);
Предположим, что нам нужно отправить письмо по адресу ivan@ivanov.org, тема сообщения "Привет" и само сообщение - "Как дела?" Воспользуемся функцией mail():
mail("ivan@ivanov.org","Привет","Как дела?");
Если ваша система правильно настроена, сообщение будет отправлено. Если же сообщения не отправляются, откройте файл php.ini и найдите секцию [mail function]. Вам нужно изменить следующие параметры:
SMTP = mail.isp.ru
Первые два параметра нужно редактировать, если у вас установлена операционная система Windows, а последний - если вы работаете под Unix (Linux, FreeBSD).
Для работы с системой имен (DNS - Domain Name System) вы можете использовать три функции:
Первая функция возвращает доменное имя узла по IP-адресу:
echo gethostbyaddr("192.168.1.1"); echo gethostbyaddr($REMOTE_ADDR);
Функция gethostbyname() является обратной к первой: она возвращает IP-адрес по имени:
echo gethostbyname("www.mail.ru");
Одному доменному имени могут соответствовать несколько IP-адресов. Для чего это нужно? Если нагрузка на сервер большая, то правильно настроенный сервер DNS будут каждый раз возвращать другой IP-адрес узла - IP-адрес зеркала. Выходит, что один клиент работает с сайтом www.big_site.net, подключившись по адресу 192.168.1.1, а второй работает с зеркалом этого сайта (но для клиента это совершенно прозрачно), но подключившись по адресу 192.168.1.2. Нагрузка на серверы распределяется равномерно, вследствие чего повышается производительность работы сайта.
Третья функция gethostbynamel() возвращает список (массив) IP-адресов, которые ассоциируются с определенным доменным именем. Вот пример использования данной функции:
$ips = gethostbynamel("www.big_site.net"); foreach ($ips as $ip) echo "$ip
";
В качестве вывода мы получим длинный список IP-адресов. Не знаете, какое имя передать функции gethostbynamel()? В целях эксперимента укажите www.yahoo.com. Вот список IP-адресов узла www.yahoo.com:
216.109.118.68 216.109.118.70 216.109.118.71 216.109.118.74 216.109.118.76 216.109.118.77 216.109.118.64 216.109.118.66
Попробуем разрешить IP-адрес 111.111.111.333 (я знаю, что такой IP не существует, но я специально использую несуществующий IP, чтобы не произошло недоразумений с владельцем этого IP) в доменное имя.
<? echo gethostbyaddr("111.111.111.333"); ?>
Функция gethostbyaddr() работает следующим образом: она обращается к узлу 111.111.111.333 и запрашивает у него доменное имя. Плохой дядька-владелец компьютера с адресом 111.111.111.333 настроил свой компьютер так, чтобы он сообщал функции gethostbyaddr(), что имя его компьютера www.google.com. В результате вы получите вывод www.google.com, что не является истиной. Вот вам и подмена доменного имени!
Для корректного преобразования IP-адреса в имя функции gethostbyaddr() и gethostbyname() должны работать в паре:
$ip = "192.168.1.1"; $domain_name = gethostbyaddr($ip); // произошла ошибка и в результате мы получили, что $domain_name = $ip if ($domain_name == $ip) die("Неверный IP!"); $ip2 = gethostbyaddr($domain_name); // Если была ошибка, $ip2 = $domain_name if ($ip2 === $domain_name) die("Неверное доменное имя"); if ($ip === $ip2) echo "$ip = $domain_name"; else "по адресу $ip находится хакер!!!";
Сначала мы преобразуем заданный IP-адрес в доменное имя, потом пытаемся узнать IP-адрес полученного доменного имени. Если IP-адреса совпадают, значит, все нормально, а если нет - скорее всего, по IP-адресу находится злоумышленник. Хотя данное утверждение нельзя считать окончательным, потому что мы знаем, что за одним именем может быть закреплено несколько IP-адресов. Помните пример с www.yahoo.com?
На этом обзор сетевых функций PHP можно считать завершенным. Конечно, мы не рассмотрели функции для работы с Cookies и механизм сессий, но это уже тема для отдельной статьи. Все ваши вопросы и комментарии рад буду выслушать по адресу dhsilabs@mail.ru.