en | ru

WSPHP

WSPHP — это крошечный и быстрый сервер, написанный на C++. С помощью WSPHP разработчики могут создавать полнодуплексные клиент-серверные приложения, использующие протокол Websockets.

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

Привет, мир

Все действительно очень просто. Смотрите сами:

test1.js
ws = new WebSocket( "ws://127.0.0.1:30403" );
/* Вы можете скрыть адрес и порт с помощью Nginx */

ws.onopen = onWebsocketConnected;
ws.onmessage = onWebsocketReceived;

function onWebsocketConnected(e)
{
ws.send("my_name_is=Вася");
}

function onWebsocketReceived(e)
{
alert(e.data);
}
app1.php
if(isset($_GET['my_name_is']))
{
echo "<WSReply>Привет, " . $_GET['my_name_is'] . "</WSReply>";
}

С помощью javascript мы соединились с сервером, и послали ему строку my_name_is=Вася.
Сервер ответил на запрос, причем свой ответ он обернул тэгами <WSReply>.
Как только броузер получит ответ сервера, высочит окно (alert) с тестом "Привет, Вася".

Вы ожидали сложный код с тяжелыми подключаемыми классами и прочими ужасами конкурирующих продуктов? Извините за разочарование...

Как это работает

Сразу после запуска, сервер WSPHP открывает TCP порт и ожидает входящие соединения.
Мы рекомендуем использовать Nginx или какой-то другой прокси-сервер между интернетом и WSPHP.
После получения запроса по протоколу Websocket, сервер WSPHP выполняет единственный PHP скрипт app.php и передает ему дополнительные параметры.
Заметьте, что остальные PHP скритпы вашего сайта продолжают работать в обычном режиме (other.php на картинке).
О файле cron.php мы поговорим чуть позже.

Все переданные клиентом даные, могут быть получены PHP скриптом как обычно - через переменную $_GET (или $_REQUEST).
PHP скрипт выполняется сразу же после получения данных от клиента. После выполнения, PHP скрипт выводит в stdout данные в формате XML и "умирает".
Как обычно, вы можете использовать сессии, если вам нужно хранить данные между вызовами PHP.
Соединение по протоколу Websocket остается активным — клиент и сервер могут продолжать обмениваться сообщениями.

Cron job

Сервер WSPHP периодически выполняет скрипт cron.php. Период настраивается в конфигурационном файле.
В этом скрипте удобно рассылать какие-то сообщения всем или некоторым подключенным клиентам, когда в этом появляется необходимость.

cron2.php
echo "<WSSend to='all'>Текущее время:" . date('r') . "</WSSend>";
echo "<WSSend to='W0123456S'>Сегодня " . date('z') . "-й день года.</WSSend>";

В этом примере мы периодически рассылаем текущее время всем подключенным клиентам. А так же, для клиента с идентификатором сессии W0123456S мы шлем дополнительную строку - номер текущего дня в году.

Сессии

Каждому активному соединению сервер WSPHP присваивает уникальную строку-идентификатор.
Эта строка хранится в переменной $_SERVER['WSPHP_SESSION'].
Стандартные сессии PHP работают как обычно. Нужно только перед открытием сессии изменить ее идентификатор:

app3.php
session_id( $_SERVER['WSPHP_SESSION'] );
session_start();

/* Используйте переменную $_SESSION как обычно. */

Сессии работают и в cron job — идентификатор сессии в кроне постоянный от вызова к вызову cron.php, до перезагрузки WSPHP.

Серверные переменные

Из PHP доступны перечисленные ниже переменные. Они хранятся в массиве $_SERVER

VariableTypeDescription
WSPHP_SESSIONstringИдентификатор текущей сессии
WSPHP_IPstringIP адрес клиента
WSPHP_ALIVEintegerВремя жизни соединения в секундах
WSPHP_LAST_OPintegerВремя в секундах, прошедшее с момента получения любых данных от клиента.
WSPHP_HOSTstringПеременная "Host" (извлекается из заголовка HTTP)
WSPHP_ORIGINstringПеременная "Origin" (извлекается из заголовка HTTP)
WSPHP_CRONintegerУстанавливается в единицу для вызовов cron job. В других случаях не определена.
WSPHP_FIRST_CALLintegerУстанавливается в единицу для первого вызова скрипта после запуска сервера WSPHP. В других случаях не определена.

Замечания:
IP адрес извлекается из следующих источников, в указанной последовательности: заголовок X-Forwarded-For, заголовок X-Real-IP, информация из протокола TCP.
WSPHP_FIRST_CALL устанваливается отдельно для app.php и для cron.php. При установленной переменной удобно инициализоровать систему, например, удалить все сессии клиентов из базы данных, которые остались там с момента предыдущего запуска WSPHP.

Формат XML

Сервер WSPHP понимает следующий формат XML.

XML
<WSContent>
<WSReply>Строка, которую получит клиент, который был инициатором вызова PHP скрипта.</WSReply>
<WSSend to="список клиентов">Строка, которую получат клиенты, перечисленные в параметре "to"</WSSend>
<WSClose>Список слиентов</WSClose>
</WSContent>
WSReplyТекст между этими тегами будет отослан клиенту - инициатору запроса, как есть. Этот тэг не должен встречаться в cron job, т.к. там нет клиента-инициатора запроса.
WSSendТекст между этими тегами будет разослан всем клиентам с перечисленными идентификаторами сессий. Чтобы отослать текст абсолютно всем клиентам, используйте ключевое слово All. В одном XML файле может встречаться несколько тегов WSSend
WSCloseОтключает клиентов с указанными идентификаторами сессий от сервера. Вы можете использовать слючевое слово All для отключения от сервера всех клиентов. Несколько подобных тегов может встречаться в одном XML файле.

В списке клиентов (точнее в списке идентификаторов сессий клиентов) разделителем является запятая.

События

connected: отсылается в PHP, когда клиент соединился с сервером.
disconnected: отсылается в PHP, когда связь клиента и сервера оборвалась.

app4.php
if( isset( $_GET['client_notification'] ) )
{
if( $_GET['client_notification'] == 'connected' )
{
// Сохраните идентификатор сессии клиента в базе данных.
// Сделайте еще что-то полезное, отошлите клиенту строку-приветствие, например.
}

if( $_GET['client_notification'] == 'disconnected' )
{
// Удалите идентификатор сессии клиента из базы данных.
// Очевидно, что нет смысла что-то отсылать клиенту,
// т.к. связь между ним и сервером оборвалась.
}
}

Строку client_notification можно изменить в конфигурационном файле. Смотрите параметр notification_param.
Этот вызов осуществляется в контексте клиента (переменная $_SERVER['WSPHP_SESSION'] содержит идентификатор сессии клиента).

Административные команды

Предположим, что в конфигурационом файле записаны такие строки:
control_name=wsphp_control
control_password=wsphp

Авторизация.
Запрос wsphp_control=authenticate&password=wsphp&output_format=json|xml|html выполняет авторизацию.
Два возможных варианта ответа сервера (в случае output_format=json):
{"wsphp_control":{"Success":"Authenticated","request":"authenticate"}}
{"wsphp_control":{"Error":"Authentication error","request":"authenticate"}}
Следующие запросы будут обработаны сервером только после успешной авторизации.

Список всех подключенных клиентов.
Запрос wsphp_control=get_endpoints.
Пример ответа:
{ "wsphp_control": {"endpoint_list":[ ["SESS1111", "WS", "1.2.3.4", "10", "10", "host.com", "/origin"], [...], ... ],
"endpoint_list_fields": ["session", "type", "ip", "alive", "last_op", "host", "origin"],
"endpoint_this": "SESS012345678", "request": "get_endpoints" } }

Где endpoint_this - идентификатор сессии клиета - инициатора запроса.

Передача сообщения.
Запрос wsphp_control=send_to&endpoint=SESSIONID&message=Hello,+world! отсылает сообщение клиенту, с идентификатором сессии, указанной в endpoint.
Два возможных варианта ответа сервера (в случае output_format=json):
{"wsphp_control":{"Success":"Message was delivered","request":"send_to"}}
{"wsphp_control":{"Error":"Endpoint not found","request":"send_to"}}

Завершение соединения клиента и сервера.
Запрос wsphp_control=disconnect&endpoint=SESSIONID.
Два возможных варианта ответа сервера (в случае output_format=json):
{"wsphp_control":{"Success":"Endpoint disconnected","request":"disconnect"}}
{"wsphp_control":{"Error":"Endpoint not found","request":"disconnect"}}

Информация о состоянии сервера.
Запрос wsphp_control=get_info.
Ответ сервера (в случае output_format=json):
{ "wsphp_control": { "connections": {"active":100, "dropped":0, "accepted":1500, "fastcgi":2600}, "server": { "version": "1.1", "build":123, "listen":{"ip":"127.0.0.1","port":30403}, "uptime":132456}, "request": "get_info" } }

Исходный код простой панели управления можно посмотреть здесь.

Опции конфигурационного файла

В конфигурационном файле можно определить следующие опции:

ОпцияОписание
verboseУровень отладки. Возможные значения: 1,2,3. 1 - много информации, 3 - показывать только ошибки. По умолчанию 2.
listen_portНомер TCP порта, на который будут приниматься входящие соединения. По умолчанию 30403.
fastcgi_serverСервер и порт сервера Fast CGI (в формате сервер:порт)
dicsonnect_timeoutВремя ожидания любых данных от клиента. Если данные за указанное время не получены, сервер обрывает связь с клиентом.
cron_pathПуть к скрипту, который будет периодически выполняться (cron job).
cron_timeoutПериод в секундах выполнения cron job.
app_pathПуть к скрипту, который будет выполняться после каждого запроса клиента.
notification_paramНазвание параметра, используемого для нотификации о том, что клиент подключился к серверу (connected) или связь с клиентом оборвалась (disconnected).
control_nameНазвание параметра, в котором передается тип запроса.
control_passwordПароль, используется для авторизации.

Текстовый протокол

Вы разрабатываете клиентское приложение на языке, который не имеет нативной поддержки протокола Websocket?
Используя сервер WSPHP вам не нужно заботиться о рукопожатии (handshake), кодировании фреймов Websocket,и т.п.

Client —> Server
GET /uri HTTP/1.1
Host: yourwebsite.com
Content-Type: CRLF

Здесь клиент соединился с сервером и передал ему стандартный HTTP заголовок. Вы можете добавить дополнительные поля, такие как X-Forwarded-For header, X-Real-IP, или другие.
Обратите вримание, на поле content-type. Оно должно быть CRLF.

Server —> Client
HTTP/1.1 200 OK
Content-Type: CRLF

Сервер так же ответил стандартным HTTP заголовком. Непрерывное соединение установлено.

Теперь клиентское приложение может отсылать текстовые строки с символами [CR+LF] (или даже только [LF]) в конце строки.
На стороне сервера будет выполнен PHP скрипт, а клиент получит ответ так же в текстовом виде.
С точки зрения PHP ничего не изменилось.

переменная $_SERVER['SERVER_PROTOCOL'] установлена в WS вовремя работе клиента в режиме Websocket, и в CRLF при работе в режиме текстового протокола.

Для пользователей Windows

Так как PHP не предоставляет механизма автоматического запуска php-cgi.exe, мы добавили такую опцию в конфигурационный файл:
windows_autorun="C:/Program Files/PHP/php-cgi.exe" -b 127.0.0.1:9010
Благодаря этому, php-cgi будет запущен в скрытом окне сразу же после запуска WSPHP.
Даже больше, WSPHP следит за php-cgi, и перезапустит его, если он остановился.

Пример конфикурации Nginx

Nginx config
server
{ listen 80;
server_name example.com www.example.com;
root /var/web/example;

#error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

location ~ \.php$
{ try_files $uri =404;
fastcgi_pass 127.0.0.1:9999;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

location /
{ index index.php index.html;
try_files $uri $uri/ /index.php?$args;
}

location /websocket
{ proxy_pass http://127.0.0.1:30403; ## Номер порта, который слушает WSPHP
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
}

Теперь вы можете соединиться с WSPHP сервером с помощью следующего кода на javascript:
var ws = new WebSocket( "ws://example.com/websocket" )

Вы можете сконфигурировать Nginx использовать SSL шифрование. В этом случае приведенный выше код немного изменится:
var ws = new WebSocket( "wss://example.com/websocket" )

Загрузка

wsphp2.17.147-linux-amd64.tar.gz
wsphp2.17.147-windows-amd64.zip
wsphp2.17.147-windows-i386.zip

Установка и запуск

Извлеките файлы из архива в любую папку. Пользователям Linux нужно сделать chmod исполняемому файлу.:

Running WSPHP
/path/to/wsphp /path/to/wsphp.cfg

Убедитесь, что вы используете правильный путь и имя конфигурационного файла.
Выможете добавить WSPHP системный crontab. Только один экземпляр программы будет запущен:

Crontab
* * * * * /path/to/wsphp /path/to/wsphp.cfg

Вот и все. Пишите нам, если у вас еще остались вопросы.

Примеры использования WSPHP

Чат
Панель управления

Обратная связь

Пожалуйста, предоставьте нам как можно больше технических подробностей, если вы сообщаете нам о проблеме.
wsphp.net@gmail.com