Для чего нужны веб-сокеты

Веб-сокеты (1) — это технология, позволяющая устанавливать непрерывное соединение между клиентом и сервером. Особенность такой системы также в том, что сервер может по своей инициативе отправлять данные одному или нескольким клиентам. Это позволяет создавать real-time мессенджеры, онлайн-игры и прочие проекты.

PHP, казалось бы, не подходит для такой цели. Он обычно используется для создания динамических веб-страниц и работает по принципу «открыл-ответил-закрыл». Что же, стереотипы пора ломать…

Пример работы с веб-сокетами

см. в (1) пример библиотеки по вебсокетам:

Данный класс избавляет вас от необходимости скачивания сторонних демонов или написания сервера самостоятельно. Всё, что вам потребуется — подключить класс и написать функции приёма сообщений, при желании ещё и heartbeat. Чтобы использовать класс, просто включите его в ваш скрипт

В (2) см. пример работы с сокетами — реализация чата.

Будем изучать простейший пример по мануалу (3), скачав исходники (4):

Проверка поддержки сокетов на хостинге

– Что нужно сделать в первую очередь для начала работы с WebSocket?

– Проверить поддержку сокетов на хостинге и в Денвере.

Для этого создаём простенький файлик sockettest.php

<?php

/**
 * Проверка поддержки сокетов на хостинге
 */

if(extension_loaded('sockets')) {
    echo "WebSockets OK";
} else {
    echo "WebSockets UNAVAILABLE";
}

?>

Ответ:

php sockettest.php
WebSockets UNAVAILABLE
  • ошибка

Та же проверка в браузере: http://exp/websockets/petukhovsky/sockettest.php

WebSockets OK
  • ОК (т.е. в консоли сокет не работает, а браузере по http норм)

Исходники

socket.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Simple Web-Socket Client</title>
</head>
<body>
<br/><br/>

http://socket.js

Server address:
<input id="sock-addr" type="text" value="ws://echo.websocket.org">
<br/><br/>

Message:
<input id="sock-msg" type="text">

<input id="sock-send-butt" type="button" value="send">
<br/>
<br/>
<input id="sock-recon-butt" type="button" value="reconnect">
&nbsp;&nbsp;&nbsp;
<input id="sock-disc-butt" type="button" value="disconnect">
<br/>
<br/>

Messages from web-socket:
<div id="sock-info" style="border: 1px solid"></div>

</body>
</html>

socket.js

"use strict";

(function () {

    // private vars
    var socket;

    ////////////////////////////////////////////////////////////////////////////
    var init = function () {

        socket = new WebSocket(document.getElementById("sock-addr").value);

        socket.onopen    = connectionOpen;
        socket.onmessage = messageReceived;
        socket.onerror   = errorOccurred;

        /**
         * Send button click
         */
        document.getElementById("sock-send-butt").onclick = function () {
            socket.send(document.getElementById("sock-msg").value);
        };

        /*
         * Disconnect button click
         */
        document.getElementById("sock-disc-butt").onclick = function () {
            connectionClose();
        };

        /**
         * Reconnect button click
         */
        document.getElementById("sock-recon-butt").onclick = function () {
            socket = new WebSocket(document.getElementById("sock-addr").value);
            socket.onopen = connectionOpen;
            socket.onmessage = messageReceived;
        };
    };

    // open socket connection
    function connectionOpen() {
        socket.send("Connection with "" + document.getElementById("sock-addr").value + "" Connection SUCCESS.");
    }

    // get message
    function messageReceived(e) {
        console.log("Server response: " + e.data);
        document.getElementById("sock-info").innerHTML += ("Message: " + e.data + "<br />");
    }

    // error message
    function errorOccurred(e) {
        console.log("Server error: " + e.data);
        document.getElementById("sock-info").innerHTML += ("Error: " + e.data + "<br />");
    }

    // close socket connection
    function connectionClose() {
        socket.close();
        document.getElementById("sock-info").innerHTML += "Connection closed<br />";
    }

    return {
        // ---- onload event ----
        load: function () {
            window.addEventListener('load', function () {
                init();
            }, false);
        }
    }
})().load();

simpleworking.php

<?php

/**
 * WebSocket server
 * @source https://petukhovsky.com/simple-web-socket-on-php-from-very-start/
 * @hint поправолено под современную версию php 7.3+
 * @return false|resource
 */

/**
 * Run websocket server
 * @return false|resource
 */
function go()
{
    $starttime = round(microtime(true), 2);
    echo "GO() ... <br />rn";

    echo "socket_create ...";
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    if (!$socket) {
        echo "Error: " . socket_strerror(socket_last_error()) . "<br />rn";
        exit();
    } else {
        echo "OK <br />rn";
    }

    echo "socket_bind ...";

    // привязываем его к указанным ip и порту
    // !! если возникает ошибка коннекта, меняет тут порт на любой другой
    $bind = @socket_bind($socket, '127.0.0.1', 8890);
    if (!$bind) {
        echo "Error: " . socket_strerror(socket_last_error()) . "<br />rn";
        exit();
    } else {
        echo "OK <br />rn";
    }

    // разрешаем использовать один порт для нескольких соединений
    socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);

    echo "Listening socket... ";
    $listen = socket_listen($socket, 5);    // слушаем сокет

    if (!$listen) {
        echo "Error: " . socket_strerror(socket_last_error()) . "<br />rn";
        exit();
    } else {
        echo "OK <br />rn";
    }

    // Бесконечный цикл ожидания подключений
    while (true) {
        echo "Waiting... ";
        // Зависаем пока не получим ответа
        $accept = @socket_accept($socket);
        if ($accept === false) {
            echo "Error: " . socket_strerror(socket_last_error()) . "<br />rn";
            usleep(100);
        } else {
            echo "OK <br />rn";
            echo "Client "" . $accept . "" has connected<br />rn";
        }

        $msg = "Hello, Client!";
        echo "Send to client "" . $msg . ""... ";
        socket_write($accept, $msg);
        echo "OK <br />rn";

        if ((round(microtime(true), 2) - $starttime) > 100) {
            echo "time = " . (round(microtime(true), 2) - $starttime);
            echo "EXIT.<br />rn";
            return $socket;
        }
        sleep(10);   // засыпаем на 10 сек
    }
}

error_reporting(E_ALL);        // Выводим все ошибки и предупреждения
set_time_limit(0);          // Время выполнения скрипта не ограничено
ob_implicit_flush();                // Включаем вывод без буферизации

$socket = go();                     // Функция с бесконечным циклом, возвращает $socket по запросу выполненному по прошествии 100 секнуд.

echo "go() ended<br />rn";

if (isset($socket)) {
    echo "Closing connection... ";
    @socket_shutdown($socket);
    socket_close($socket);
    echo "OK <br />rn";
}

Тестируем

Открываем веб форму:

http://exp/websockets/petukhovsky/socket.html

Шлем сообщения и получаем их тут же:

Слушаем сервер: http://exp/websockets/petukhovsky/simpleworking.php

Во вкладке клиента в поле Server address вводим ws://127.0.0.1:889 и нажимаем reconnect, мы видим что на клиенте ничего не происходит:

а на сервере появляются сообщения вида:

Чтобы окончательно убедиться в том, что сервер отвечает, что его сообщения не блокируются файрволом, запустите скрипт сервера http://exp/websockets/simpleworking.php запустите telnet и попытайтесь подключиться к 127.0.0.1:889, только это нужно сделать не позднее 100 секунд, с момента запуска сервера, пока он не закрыл соединения и не завершил скрипт.

По telnet должен придти ответ “Hello, Client!”, что свидетельствует о том, что всё работает в штатном режиме и связь с сервером двухсторонняя.

Фиксим ошибки

У меня выдается ошибка при коннекте:

ошибка запуска теперь: http://exp/websockets/petukhovsky/simpleworking.php

также теперь ошибка коннкета в клиенте:

Все сообщения отправляемые по протоколу WebSocket можно разделить на несколько видов: handsnake (рукопожатие при установлении связи), ping-pong(проверка связи) и data-transfer(передача данных). Также есть более краткое описание протокола в общих чертах на русском в (5)

Fix: меняем порт в simpleworking.php:

Теперь вроде работает, сервер получает сообщения:

C этим разобрались , можно еще изучить расширенный урок по демонам в (6)


Источники:

(1) https://habr.com/ru/post/209864/

(2) https://itnan.ru/post.php?c=1&p=266931

(3) https://petukhovsky.com/simple-web-socket-on-php-from-very-start/

(4) Исходники клиента и сервера: https://petukhovsky.com/f/simple-web-socket-on-php-from-very-start/simpleworking.zip

(5) https://learn.javascript.ru/websockets

(6) https://petukhovsky.com/simple-web-socket-on-php-daemon/

Tags

Нет комментариев

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.