Сокеты в PHP

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

Итак, рассмотрим, как реализованы сокеты в PHP, хотя принцип работы с ними одинаковый во всех языках.

Сначала напишем простые клиент и сервер: сервер будет запускаться и ждать соединения, а клиент соединяться к нему и посылать какую-то строку (информацию). А затем я покажу на примере, как отправлять письмо, используя почтовый SMTP-сервер.

Не буду приводить здесь список сокетных функций в PHP. По мере чтения примеров далее Вы сами увидете их.

Пишем простой echo-сервер

Что значит echo-сервер? Это значит, что строка, посланная серверу, возвращается в ответ тому, кто ее послал - клиенту. Т.е. эхо получается по сути.

Сначала нам нужно создать дескриптор сокета:

echo 'Create socket ... ';
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
    throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
} else {
    echo "OK\n";
}

Заметим, что я здесь использовал вызов socket_strerror(socket_last_error()), чтобы определить, в чем была причина ошибки, если вдруг возникла ошибка. Т.е. сначала мы определили номер ошибки с помощью socket_last_error(), а затем этот номер передали в socket_strerror, чтобы превратить его в текстовое описание ошибки.

Теперь нам нужно привязать созданный дескриптор к определенным адресу и порту машины, на которой он будет запущен. Обычно локальный адрес - 127.0.0.1, или localhost. Возьмем порт с номером 10001:

$address = 'localhost';
$port    = 10001;

echo 'Bind socket ... ';
if (($ret = socket_bind($sock, $address, $port)) < 0) {
    throw new Exception('socket_bind() failed: '.socket_strerror(socket_last_error())."\n");
} else {
    echo "OK\n";
}

Затем нам нужно включить прослушивание этого сокета:

echo 'Listen socket ... ';
if (($ret = socket_listen($sock, 5)) < 0) {
    throw new Exception('socket_listen() failed: '.socket_strerror(socket_last_error())."\n");
} else {
    echo "OK\n";
}

Цифра 5 здесь означает, что мы разрешим встать в очередь на подключение к этому адресу максимум пяти клиентам.

Когда клиент попытается установить с нами соединение, нам нужно его принять:

echo 'Accept socket ... ';
if (($msgsock = socket_accept($sock)) < 0) {
    throw new Exception('socket_accept() failed: '.socket_strerror(socket_last_error())."\n");
} else {
    echo "OK\n";
}

После этого дескриптор принятого соединения сохраняется в переменной $msgsock, с которой мы далее будем работать.

Ну а дальше мы просто общаемся с клиентом, отправляя ему данные …

$msg = "Hello, Client!";
echo "Say to client ($msg) ... ";
socket_write($msgsock, $msg, strlen($msg));
echo "OK\n";

… или принимая их от него:

echo 'Client said: ';
if (false === ($buf = socket_read($msgsock, 1024))) {
    throw new Exception('socket_read() failed: '.socket_strerror(socket_last_error())."\n");
} else {
    echo $buf."\n";
}

После всего этого «разговора» двух программ, нужно освободить ресурсы, вызвав socket_close:

echo 'Close socket ... ';
socket_close($sock);
echo "OK\n";

Приведу полный исходный текст нашего простенького сервера:

server.php

<?
    header('Content-Type: text/plain;');
    error_reporting(E_ALL ^ E_WARNING);
    set_time_limit(0);
    ob_implicit_flush();
   
    echo "-= Server =-\n\n";

    $address = 'localhost';
    $port    = 10001;

    try {
   
        echo 'Create socket ... ';
        if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
            throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
        } else {
            echo "OK\n";
        }

        echo 'Bind socket ... ';
        if (($ret = socket_bind($sock, $address, $port)) < 0) {
            throw new Exception('socket_bind() failed: '.socket_strerror(socket_last_error())."\n");
        } else {
            echo "OK\n";
        }

        echo 'Listen socket ... ';
        if (($ret = socket_listen($sock, 5)) < 0) {
            throw new Exception('socket_listen() failed: '.socket_strerror(socket_last_error())."\n");
        } else {
            echo "OK\n";
        }

        do {
            echo 'Accept socket ... ';
            if (($msgsock = socket_accept($sock)) < 0) {
                throw new Exception('socket_accept() failed: '.socket_strerror(socket_last_error())."\n");
            } else {
                echo "OK\n";
            }
           
            $msg = "Hello, Client!";
            echo "Say to client ($msg) ... ";
            socket_write($msgsock, $msg, strlen($msg));
            echo "OK\n";

            do {
           
                echo 'Client said: ';
                if (false === ($buf = socket_read($msgsock, 1024))) {
                    throw new Exception('socket_read() failed: '.socket_strerror(socket_last_error())."\n");
                } else {
                    echo $buf."\n";
                }
               
                if (!$buf = trim($buf)) {
                    continue;
                }
               
                if ($buf == 'shutdown') {
                    socket_close($msgsock);
                    break 2;
                }
               
                echo "Say to client ($buf) ... ";
                socket_write($msgsock, $buf, strlen($buf));
                echo "OK\n";
               
            } while (true);
           
        } while (true);

    } catch (Exception $e) {
        echo "\nError: ".$e->getMessage();
    }
   
    if (isset($sock)) {
   
        echo 'Close socket ... ';
        socket_close($sock);
        echo "OK\n";
   
    }
?>

Вызовом функции set_time_limit(0), мы говорим интерпретатору, что скрипт может выполняться бесконечно, а не 30 секунд максимум, как по-умолчанию прописано в php.ini.

А вызовом функции ob_implicit_flush мы говорим ему, что выводить строки с помощью echo нужно сразу при их выводе, а не после полной загрузки страницы, как это делается по-умолчанию.

Как видите, в этом простом скрипте мы сохраняем строку, которую нам пришлет клиент, в переменной $buf, затем ее же отправляем обратно клиенту. При этом мы сравниваем эту строку с ‘shutdown’. Если это ‘shutdown’, то корректно завершаем работу сервера.

Теперь запустите этот скрипт в браузере и Вы должны увидеть следующее:

-= Server =-

Create socket … OK
Bind socket … OK
Listen socket … OK
Accept socket …

Т.е. сервер ждет подключения к нему клиента. Ну что ж, не будем заставлять его долго ждать и напишем для него клиента.

Пишем клиента

client.php

<?
    header('Content-Type: text/plain;');
    error_reporting(E_ALL ^ E_WARNING);
    set_time_limit(0);
    ob_implicit_flush();
   
    echo "-= Client =-\n\n";

    $address = 'localhost';
    $port    = 10001;

    try {
   
        echo 'Create socket ... ';
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($socket < 0) {
            throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
        } else {
            echo "OK\n";
        }

        echo 'Connect socket ... ';
        $result = socket_connect($socket, $address, $port);
        if ($result === false) {
            throw new Exception('socket_connect() failed: '.socket_strerror(socket_last_error())."\n");
        } else {
            echo "OK\n";
        }

        echo 'Server said: ';
        $out = socket_read($socket, 1024);
        echo $out."\n";
       
        $msg = "Hello, Server!";
        echo "Say to server ($msg) ...";
        socket_write($socket, $msg, strlen($msg));
        echo "OK\n";
       
        echo 'Server said: ';
        $out = socket_read($socket, 1024);
        echo $out."\n";
       
        $msg = 'shutdown';
        echo "Say to server ($msg) ... ";
        socket_write($socket, $msg, strlen($msg));
        echo "OK\n";
       
    } catch (Exception $e) {
        echo "\nError: ".$e->getMessage();
    }
   
    if (isset($socket)) {
   
        echo 'Close socket ... ';
        socket_close($socket);
        echo "OK\n";
       
    }
?>

Т.е. наш клиент соединяется к серверу по адресу localhost:10001. Далее после вывода того, что ему сказал сервер («Hello, Client!»), он приветствует сервера: «Hello, Server!». Потом сервер отвечает ему тем же (он же echo-сервер :)), а клиент отсоединяется от него, посылая команду shutdown.

После запуска этого скрипта, браузер у меня вывел следующие строчки:

-= Client =-

Create socket … OK
Connect socket … OK
Server said: Hello, Client!
Say to server (Hello, Server!) …OK
Server said: Hello, Server!
Say to server (shutdown) … OK
Close socket … OK

При этом вывод сервера выглядел следующим образом:

-= Server =-

Create socket … OK
Bind socket … OK
Listen socket … OK
Accept socket … OK
Say to client (Hello, Client!) … OK
Client said: Hello, Server!
Say to client (Hello, Server!) … OK
Client said: shutdown
Close socket … OK

Итоги

Фух, что-то статья получилась больше, чем я думал. Знаете, давайте я расскажу об отправке email с использованием сокетов в другой статье. Я знаю, что многие интересуются отправкой по SMTP с авторизацией, вот о ней и расскажу. А пока все. Удачных экспериментов с сокетами!

Популярность: 67%

Читайте также:


 #  #  #  #  #  #  #  #  #  #

9 Ответов на “Сокеты в PHP”

  1. Skill00
    Июль 14th, 2008

    Даже не думал, что про сокеты можно вот так вот легко и в то же время подробно расписать) Отличная статья.

    P.S. Держи еще одну идею :). С постов в этом блоге получился бы очень неплохой мануал, если собрать все это дело, например в pdf )

  2. novice
    Июль 14th, 2008

    Спасибо)

    Насчет pdf - эта идея мне пришла в голову еще неделю назад - выпускать некое подобие ежемесячного электронного журнала. Обязательно воплощу ее в реальность в ближайшем будущем)

  3. Tyler
    Июль 20th, 2008

    спасибо, познавательно :)

  4. Atrius
    Июль 25th, 2008

    Я с вами полностью согласен.

  5. porfeus
    Сентябрь 18th, 2008

    Автор молодец! 5 баллов, нигде еще не встречал такой подробный и рабочий manual про сокетов в php -

  6. novice
    Сентябрь 18th, 2008

    2 porfeus: Спасибо! Рад такое слышать, т.е. читать :)

  7. kycuk
    Сентябрь 20th, 2008

    Спасибо, очень полезная статья.

  8. Prost
    Сентябрь 29th, 2008

    Статья действительно очень доступно написана. Жаль только, что у меня не получается на практике поработать с сокетами. Даже не знаю почему. У меня стоит Денвер-3. Может кто-то знает как включить поддержку сокетов?

  9. Porfeus
    Октябрь 10th, 2008

    Prost: “Может кто-то знает как включить поддержку сокетов” -Заходишь в php.ini, ищешь через поиск строчку ;extension=php_sockets.dll и разкоментируешь ее те убираешь впереди знак-;

Оставить комментарий