Сетевые функции 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()

Функция mail() позволяет быстро отправить сообщение, используя PHP. Формат этой функции таков:


mail(string $email, string $subject, $string $message [,string $haders]);

Предположим, что нам нужно отправить письмо по адресу ivan@ivanov.org, тема сообщения "Привет" и само сообщение - "Как дела?" Воспользуемся функцией mail():


mail("ivan@ivanov.org","Привет","Как дела?");

Если ваша система правильно настроена, сообщение будет отправлено. Если же сообщения не отправляются, откройте файл php.ini и найдите секцию [mail function]. Вам нужно изменить следующие параметры:

Первые два параметра нужно редактировать, если у вас установлена операционная система Windows, а последний - если вы работаете под Unix (Linux, FreeBSD).

DNS-функции

Для работы с системой имен (DNS - Domain Name System) вы можете использовать три функции: