yakoffka.ru
    грабли, костыли и велосипеды php, css, html, js и прочего

    PHP 2016. Уровень 3.5: Сокеты и сетевые службы

    ошибки phpИспользование сетевых протоколов. конспект-памятка видеокурса 'Программирование на PHP 2016'. Лектор Борисов Игорь Олегович. Учебный Центр «Специалист» при МГТУ им. Н.Э.Баумана.

    конспектировал Капустин Яков

    оглавление

    При вызове функций файловой системы, таких как fopen(), copy(), file_exists() и filesize(), именованный ресурс, указанный в аргументе filename, закрепляется за потоком. Иными словами, устанавливается соединение с файлом (так как ВСЁ ЕСТЬ ФАЙЛ!). Локальным или удалённым.

    Для установления соединения интерпретатору необходимо выяснить тип ресурса (различные типы требуют различных телодвижений).

    Если filename передан в форме 'scheme://...', он считается URL'ом и PHP проведёт поиск обработчика протокола (также известного как 'wrappers' - обертка) для этой схемы.

    • file:// — Доступ к локальной файловой системе
    • http:// — Доступ к URL-адресам по протоколу HTTP(s)
    • ftp:// — Доступ к URL-адресам по протоколу FTP(s)
    • php:// — Доступ к различным потокам ввода-вывода
    • zlib:// — Сжатые потоки
    • data:// — Схема Data (RFC 2397)
    • glob:// — Нахождение путей, соответствующих шаблону
    • phar:// — PHP-архив
    • ssh2:// — Secure Shell 2
    • rar:// — RAR
    • ogg:// — Аудиопотоки
    • expect:// — Потоки для взаимодействия с процессами

    В дополнение к этим оберткам, можно регистрировать собственные обертки, используя функцию stream_wrapper_register().

    Если ни одна обёртка не закреплена за протоколом, PHP выдаст замечание, чтобы помочь вам отследить потенциальную проблему в вашем скрипте и затем продолжит выполнение, как если бы filename указывал на обыкновенный файл.

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

    «Для этого есть сокет - непосредственно само соединение как таковое.» (с) ОлегБорисыч

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

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

    Расширение socket реализует низкоуровневый интерфейс для функций связи сокетов, основанный на популярных сокетах BSD для обеспечения возможности взаимодействия сервера и клиента.

    Для более общего клиентского интерфейса сокетов используются stream_socket_client(), stream_socket_server(), fsockopen() и pfsockopen(). При использовании этих функций, важно помнить, что хотя многие из них имеют имена, похожие на их аналоги в C, они часто имеют другой интерфейс использования.

    При подключении посредством вебсокетов происходит обмен заголовками наподобие заголовков HTTP, так называемый handshake или по-нашему «рукопожатие». Любой код статуса, отличный от 101 будет означать что «рукопожатие» не завершено.

    Функции сокета можно посмотреть здесь.

    Хотя существует множество типов сокетов, все функции сокетов основаны на одном и том же базовом принципе — получении данных программой В от программы А. Эти программы могут работать на одной и той же машине с применением межпроцессного взаимодействия (Interprocess Communication — IPC), либо на удаленных машинах (таких как Web-сервер и браузеры).

    Сокеты могут быть надежными, выполняющими все необходимое для обеспечения передачи данных из точки А в точку В (TCP), либо ненадежными, когда данные передаются без гарантии доставки (UDP).

    Все сокеты двунаправлены, но существует разница между сокетами клиента и сервера. Независимо от типа создаваемого сокета (клиентский или серверный), все они инициализируются одинаковым способом — с помощью функции socket_create().

    Для использования сокетов РНР потребуется расширения поддержки сокетов.

    Проверка возможности использования сокетов РНР:
    1
    2
    3
    4
    5
    6
    7

      
    if(extension_loaded('sockets')){
        echo 
    "WebSockets AVAILABLE";
      }else{
        echo 
    "WebSockets UNAVAILABLE";
      }
      
    Result:
    WebSockets AVAILABLE
    Использование сокетов:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

      
    // Открытие соединения с интернет-сокетом или доменным сокетом Unix
      // fsockopen(string $hostname [, int $port = -1 [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout") ]]]] ) : resource
      
    $socket fsockopen("mysite.local"80$errno$errmsg30);

      if(!
    $socket){
        echo 
    "$errno : $errmsg";
      }else{
        
    // Поготовка запроса
        
    $output "HEAD /server.php HTTP/1.1\r\n";
        
    $output .= "Host: mysite.local\r\n";
        
    $output .= "Connection: close\r\n\r\n";

        
    // Посылаем запрос
        
    fwrite($socket$output);

        
    // Читаем ответ
        
    while(!$feof($socket)){echo $fgets($socket);}

        
    // Закрываем сокет
        
    $fclose($socket);
      }
      

    Сокеты позволяют осуществить доступ к используемым сетевым протоколам. Одним из вариантов соединения с передачей параметров является рассмотренный ниже пример.

    Создадим некоторый файл, ожидающий входящие данные, переданные методом POST.

    Создание файла '_php35/example_socket.php':
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <?php
      $name
    =strip_tags($_POST["name"]);
      
    $age=$_POST["age"]*1;

      if(
    $_SERVER["REQUEST_METHOD"]=="POST"){
        if(
    $name and $age){
          
    $res="<h1>Привет, $name, мы не виделись $age лет.</h1>";
        }
      }else{
    $res="кто здесь?";}
      
    ?>
      <!DOCTYPE html>
    <html lang="ru">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
    </head>
    <body>
      <?php
        
    // выводим запрос
        // echo "\$GLOBALS=<pre>".print_r($GLOBALS,true)."</pre><hr>";
        
    echo $res;
      
    ?>
    </body>
    </html>
      

    Выведем его во фрейме:

    PHP Code:
    1
    2
    3

      
    echo '<iframe src="https://yakoffka.ru/src/conspects/_php35/example_socket.php" width="100%" height="100"></iframe>';
      
    Result:

    Передадим требуемые параметры с использованием сокетов:

    PHP Code:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

      $errno
    ="";  // в случае ошибки системного вызова функции connect()
                  // будет принимать номер этой ошибки.

      
    $errstr="";  // сообщение об ошибке в виде строки.

      
    $socket=fsockopen(
        
    "yakoffka.ru",
        
    80,
        
    $errno,
        
    $errstr,
        
    30
      
    );

      if(
    $socket){

        
    // Создаём POST-строку
        
    $str_query="name=Jakob&age=7";

        
    // Собираем запрос
        
    $out="POST /src/conspects/_php35/example_socket.php HTTP/1.1\r\n";
        
    $out.="Host: yakoffka.ru\r\n";
        
    $out.="Content-Type: application/x-www-form-urlencoded\r\n";
        
    $out.="Content-lenght: ".strlen($str_query)."\r\n\r\n";
        
    $out.=$str_query;

        
    // Посылаем запрос
        // fwrite($socket,$out);
        
    fputs($socket,$out);

        
    // выводим запрос
        
    echo "\$out=<pre>$out;</pre><hr>";

        
    // Получаем и выводим ответ
        
    echo "<pre>";
        while(!
    feof($socket)){
          echo 
    fgets($socket);
        }
        echo 
    "</pre>";

      }else{
        echo 
    "Произошла ошибка №$errno$errstr.";
      }

      
    // Закрытие соединения
      
    fclose($socket);
      
    Result:
    $out=
    POST /src/conspects/_php35/example_socket.php HTTP/1.1
    Host: yakoffka.ru
    Content-Type: application/x-www-form-urlencoded
    Content-lenght: 16
    
    name=Jakob&age=7;

    HTTP/1.1 301 Moved Permanently
    Server: openresty
    Date: Tue, 15 Jan 2019 03:23:05 GMT
    Content-Type: text/html; charset=iso-8859-1
    Content-Length: 334
    Connection: keep-alive
    Location: https://yakoffka.ru/src/conspects/_php35/example_socket.php
    
    
    
    301 Moved Permanently
    
    

    Moved Permanently

    The document has moved here.


    Apache/2.4.6 Server at yakoffka.ru Port 80
    HTTP/1.1 400 Bad Request Server: openresty Date: Tue, 15 Jan 2019 03:23:05 GMT Content-Type: text/html Content-Length: 170 Connection: close 400 Bad Request

    400 Bad Request


    openresty

    Приведённый фрагмент является замороженным выводом, тк выполнение данного кода занимает слишком много времени.

    PHP предоставляет различные функции для работы с сетью. Для использования этих функций не требуется проведение установки, поскольку они являются частью ядра PHP. Поведение этих функций зависит от установок в php.ini.

    Сетевые функции:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

      
    // Получаем имя хоста по ip-адресу
      
    $host_name gethostbyaddr("127.0.0.0");

      
    // Получаем ip-адрес по имени хоста
      
    $ip_address gethostbyname("mysite.local");

      
    // Получаем массив ip-адресов по имени хоста
      
    $ip_addresses gethostbynamel("mysite.local");

      
    // Получаем номер порта по имени службы
      
    $port getservbyname("http""tcp");

      
    // Получаем имя службы по номеру порта
      
    $service getservbyport(80"tcp");

      
    // Получаем DNS запись для указанного хоста
      
    $dns_record dns_get_record("mysite.local");

      
    // Получаем MX запись для указанного хоста
      
    $dns_record getmxrr("mysite.local");

      
    // Проверяем имя хоста на существование
      
    $exists checkdnsrr("mysite.local");
      
    Сетевые функции:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16

      
    // Получаем ip-адрес по имени хоста
      
    $ip_address gethostbyname("yakoffka.ru");

      
    // Получаем имя службы по номеру порта
      
    $service getservbyport(80"tcp");

      
    // Проверяем имя хоста на существование
      
    $exists checkdnsrr("yakoffka.ru");

      echo 
    "
        ip_address=
    $ip_address;<br>
        service=
    $service;<br>
        exists=
    $exists;<br>
      "
    ;
      
    Result:
    ip_address=141.8.192.40;
    service=http;
    exists=1;