чат на php websocket
WebSocket сервер на PHP
Протокол WebSocket предназначен для решения разных задач и снятия ограничений обмена данными между браузером и сервером. Он позволяет пересылать любые данные, на любой домен, безопасно и почти без лишнего сетевого трафика. Для установления соединения WebSocket клиент и сервер используют протокол, похожий на HTTP. Клиент формирует особый HTTP-запрос, на который сервер отвечает определенным образом.
Простой сокет-сервер
В первую очередь надо в файле php.ini расскомментировать строку, позволяющую работать с сокетами и перезапустить сервер:
Вот как выглядит простейший сокет-сервер:
Запустим его в работу:
Попробуем пообщаться с сервером с помощью telnet :
И видим сообщение от сервера:
Наш сервер в другом окне тоже встрепенулся:
WebSocket сервер
Протокол WebSocket работает над TCP. Это означает, что при соединении браузер отправляет по HTTP специальные заголовки, спрашивая: «Поддерживает ли сервер WebSocket?». Если сервер в ответных заголовках отвечает «Да, поддерживаю», то дальше HTTP прекращается и общение идёт на специальном протоколе WebSocket, который уже не имеет с HTTP ничего общего.
Здесь GET и Host — стандартные HTTP-заголовки, а Upgrade и Connection указывают, что браузер хочет перейти на WebSocket.
Для тестирования работы сервера нам нужен клиент:
Проверим его в работе. Открываем HTML-страницу в браузере и заполняем первое поле «Сервер»:
Это гарантированно работающий WebSocket echo-сервер, которые отправляет все сообщения обратно. Жмем кнопку «Установить соединение», набираем текст сообщения в поле «Сообщение», жмем кнопку «Отправить сообщение»:
А теперь код WebSocket сервера на PHP:
Для тестирования напишем небольшой PHP-скрипт, который запускает в работу сервер и все сообщения клиента отправляет обратно (echo-сервер):
Запускаем скрипт в работу:
Еще один пример использования сервера — клиент отправляет команды, а сервер их выполняет:
Альтернативная реализация WebSocket сервера с использованием функций для работы с потоками:
Делаем вебсокеты на PHP с нуля
Я изучал библиотеки phpdaemon и ratchet, они достаточно монструозны (причём используя ratchet для отправки сообщения конкретному пользователю рекомендовано дополнительно использовать wamp). Мне не совсем было понятно для чего использовать таких монстров, которые требуют установку других монстров. Почитав исходники этих, а также других библиотек, я разобрался как всё устроено и мне захотелось написать простой вебсокет-сервер на php самостоятельно. Это помогло мне закрепить изученный материал и наткнуться на некоторые подводные камни, о которых я не имел представления.
Так я решил написать необходимый для меня функционал с нуля.
Получившийся код и ссылка на демонстрационный чат в конце статьи.
Поставленные цели:
1) разобраться с серверными сокетами в php
2) разобраться с протоколом вебсокетов
3) написать с нуля простой сервер вебсокетов
1) Серверные сокеты в php
До этого момента я имел смутные представления о серверных сокетах. Почитав исходники нескольких библиотек для работы с вебсокетами я столкнулся с двумя схемами их реализаций:
используя расширение php «socket»:
или используя расширение php «stream»:
Я предпочёл второй вариант ввиду его краткости.
Итак, мы создали серверный сокет и теперь хотим обрабатывать новые соединения к нему, для этого опять же есть два варианта
или с использованием stream_select
Т.к. нам в дальнейшем нужно будет одновременно обрабатывать и серверный сокет на предмет новых соединений, и уже существующие подключения, на предмет новых сообщений, то остановимся на втором варианте.
2) Протокол вебсокетов
В этой статье хорошо описан протокол взаимодействия.
Нас интересует два момента:
«Рукопожатие» или handshake:
Считываем значение Sec-WebSocket-Key из пришедшего заголовка от клиента, рассчитываем на его основе Sec-WebSocket-Accept и отправляем итоговый ответ:
обмен сообщениями
После получения данных из вебсокета нам нужно их раскодировать, а при отправке закодировать.
Всё в той же статье хорошо описано кодирование сообщений, но нам по-сути нужны только две функции: decode и encode.
Простой сервер вебсокетов
Итак, у нас есть вся необходимая информация.
Используя код простого http сервера из первой части, а также функции handshake, decode и encode из второй мы можем собрать простой сервер вебсокетов.
В приведённом примере можно менять пользовательские сценарии onOpen, onClose и onMessage для реализации необходимого функционала.
Поставленные цели достигнуты.
Если этот материал вам покажется интересным, то в следующей статье я опишу как можно запускать несколько процессов для обработки соединений (один мастер и несколько воркеров), межпроцессное взаимодействие, интеграцию с вашим фреймворком на примере компонента yii.
Simple PHP Chat using WebSocket
In this tutorial, we are going to create a simple chat application using WebSocket and PHP socket programming. The WebSocket is used to create a bridge to send or receive messages from the PHP chat server.
In the web world, we generally use HTTP request methods to communicate between the client and server side. In this chat example, we use sockets to communicate with the server.
For establishing a socket connection between the client and the server, we use the WebSocket protocol (ws://) to specify the address of the PHP page where the WebSocket handshake is handled.
After creating WebSocket there are callbacks to handle the events that occur between the client and the server during the chat process.
Creating WebSocket and Callback Event Handlers
The following script is used to create WebSocket in client side and define callback handlers to handle the different chat events. These handlers give acknowledgments about the connection state, chat messages and the errors if any.
The chat message is encoded in JSON format and sent to the server on submit.
The encoded data will be decoded in the PHP endpoint to create the chatbox message instance. Apart from the JSON encode decode, PHP supports heavily to handle JSON data programmatically to read write parse and more.
PHP Socket Programming for the Chat Application
This PHP code checks for the new socket connection request. If any of a new connection request is found, then it will accept and perform the handshake with the new socket resource.
Then, it sends an acknowledgment to the client about the connectivity by sealing the encoded acknowledgment message.
It receives the socket data sent via the existing connections and unseals and decodes it to bundle the received data and send it to the chat client. The handshake, seal, unseal, send functionalities are handled by using the ChatHandler class.
and the chat handler class is
Establish Connection using Command Line
The following image shows the command line screen to establish the connection to start chatting by using this application.
Чат на WebSocket’ах
В статье описывается, как разработать и запустить простейший чат на вебсокетах.
Сервер
Существует много готовых продуктов для запуска websocket-сервера. Наш сервер мы запустим используя библиотеку Workerman, ключевое преимущество которой – это полное отсутствие зависимостей: скачал, запустил – работает. Также в Workerman имеется поддержка таймеров прямо из коробки, они нам пригодятся. И конечно же! Библиотека написана на PHP, а все любят PHP 😉
Клиент
Для разработки приложения-клиента я выбрал фреймворк Qt только потому, что он мне нравится. Qt – кроссплатформенная библиотека (фреймворк), позволяющая создавать программы практически под любую платформу: Windows, Linux, Android, OS X, iOS и др. Разумеется, клиент для полученного сервера можно написать на чем угодно, хоть на HTML+JS, который будет работать прямо из браузера.
1. Настройка VDS-сервера
Сразу после приобретения VDS и установки операционной системы (выбрал свежую версию Ubuntu 18.04 на тарифе «Master») подключаемся к нему. На сервер можно зайти через консоль из панели управления VDS, но это не самый удобный вариант. Предпочтительнее подключаться по SSH.
Если разные способы подключения по SSH из Windows, например:
1. Воспользоваться программой Putty;
2. Воспользоваться терминалом Cygwin;
3. Воспользоваться терминалом Ubuntu из WSL (я выбрал этот способ).
В Linux намного проще, клиент для подключения по SSH, как правило, установлен во всех дистрибутивах по умолчанию, поэтому просто открываем терминал.
Независимо от выбранного способа, команда для подключения будет одна:
где – это IP-адрес вашего сервера, который можно найти в панели управления VDS (блок «Список используемых IP-адресов»).
Установка Workerman
Чтобы скачать Workerman, сначала устанавливаем composer:
Теперь скачиваем Workerman в папку /usr/local/workerman:
И создаём php-файл, в котором будем писать код сервера чата:
Если работаете в Linux, то из рабочего окружения KDE можно подключиться через файловый менеджер Dolphin по протоколу SFTP и открыть файл в любом редакторе или даже в IDE (например, в KDevelop).
Если работаете в Windows, то можете скачать Notepad++ с плагином NppFTP, либо что-то более продвинутое, вроде Sublime / Atom / Visual Studio Code, и так же подключиться по протоколу SFTP.
2. Сервер
Чат будет работать по такому принципу: сообщения между сервером и клиентом передаются в формате JSON с указанием, какое «действие» (action) выполняет это сообщение. Таким образом можно разделить сообщения на типы: служебные, публичные, приватные и т.д., и с лёгкостью дополнять различные типы служебной и иной сопутствующей информацией.
Такой подход позволит в будущем при необходимости произвести «безболезненную» модернизацию чата. Например, если сервер начнёт отправлять сообщения неизвестного действия, то клиент просто не будет на них реагировать, но можно оповестить пользователя о том, что он использует устаревшую версию клиента.
Наш простейший чат будет поддерживать следующие действия:
action = Authorized
При подключении пользователя к чату сервер предварительно проверяет свободность выбранного никнейма. Если никнейм занят, то приписываем к нему номер (2, 3, 4 и т.д.). Если никнейм свободен, то отправляем пользователю сообщение «Authorized», в котором передаются данные, с которыми он был авторизован в чате и список пользователей чата.
Дополнительно пользователь может выбрать цвет отображения своего имени и указать, к какому полу (М/Ж) относится.
action = Connected
После авторизации нового пользователя сервер отправляет всем участникам сообщение «Connected», в котором передаются данные авторизованного пользователя.
action = Disconnected
При выходе пользователя из чата все участники оповещаются сообщением «Disconnected».
action = PublicMessage
Если пользователь отправляет сообщение в чат без указания адресата, то такое сообщение определяется как «Публичное» и рассылается всем участникам.
action = PrivateMessage
Если пользователь отправляет сообщение в чат с указанием адресата, то такое сообщение определяется как «Приватное» и отправляется только адресату.
action = Ping
Сервер рассылает всем участникам служебное сообщение «Ping» с определённым интервалом и ожидает от каждого ответное сообщение «Pong».
action = ConnectionLost
Если сервером несколько раз подряд не будет получено ответное на «Ping» сообщение от участника, то участник считается отключившимся, всем остальным участникам рассылается сообщение «ConnectionLost» с данными “отвалившегося” пользователя.
Инициализация WebSocket-сервера
И это всё. При запуске этого php-скрипта WebSocket-сервер будет запущен на порту 27800 и к нему уже можно будет подключиться.
Но обратите внимание: можно указать любой другой свободный порт, главное не забыть открыть его на VDS-сервере командой:
где – выбранный вами порт для чата.
Запускаем WebSocket-сервер командой:
Для проверки соединения и дальнейшей отладки можно воспользоваться плагином Simple WebSocket Client для браузера Google Chrome.
Окно плагина Simple WebSocket Client
При успешном подключении метка Status: CLOSED будет заменена на OPENED и разблокируется поле Request, которое в дальнейшем можно будет использовать для отправки тестовых запросов как от клиента. По сути, наш браузер уже является клиентом для сервера, просто не имеет визуального оформления и обработчиков сообщений.
Инициализировать сервер было легко, но надо ведь ещё обработать события!
Подключение и авторизация пользователя
Авторизация пользователя будет происходить во время подключения клиента к серверу с передачей параметров. Параметры подключения передаются в адресной строке как в обычном URL-адресе (QUERY STRING), а на сервере их можно прочитать из переменной $_GET.
Предполагается, что при подключении приложение-клиент должно передать все необходимые сведения о пользователе:
— отображаемое имя (ник): параметр userName;
— цвет ника: параметр userColor;
— пол: параметр gender.
Если какой-то из этих параметров не передан, то его значение должно устанавливаться автоматически.
При подключении клиента к websocket-серверу, вызывается функция Worker::onConnect, в которую передается указатель на созданный объект соединения TcpConnection.
Обратите внимание!
Вызов функцииWorker::runAll() запускает цепь обработки событий Workerman. Код, написанный после вызова этой функции, не будет выполнен. Помните об этом при внесении дальнейших изменений.
Сохраним файл на сервере и перезапустим Workerman. Остановить предыдущий запуск можно клавишами CTRL+C, а затем снова запустить той же командой:
Обратите внимание!
Перезапускать WebSocket-сервер нужно после каждого внесения изменений в любой из php-скриптов вашего проекта.
Пробуем подключиться с передачей параметров: ws://
И вот, наконец, мы добрались до Hello World!
Теперь можно написать полный код авторизации пользователя, обеспечивающий выполнение ранее изложенных требований.
Сохраняем файл, перезапускаем сервер, переподключаемся к вебсокету и видим получение двух сообщений:
Первое (Authorized) отправляется только подключившемуся пользователю, чтобы сообщить ему, с какими данными он был подключен.
Второе (Connection) отправляется всем, в том числе и подключившемуся, его мы в дальнейшем будем использовать в клиенте для пополнения списка пользователей.
Отключение пользователя
Когда клиент закрывает соединение с сервером, вызывается функция Worker::onClose, тут обработаем выход пользователя из чата:
Выполняйте перезапуск и подключение к вебсокету после каждого внесения изменений, чтобы проконтролировать работоспособность сервера.
Пинг пользователей
При вызове метода Worker::runAll запускаются все объявленные «работники» (их может быть несколько), а при их запуске вызывается функция Worker::onWorkerStart – здесь добавим код таймера для пинга пользователей.
Примечание
Протокол WebSocket имеет встроенную реализацию ping/pong из коробки, но мы напишем собственную, в которой сможем выполнять дополнительные действия. Однако клиент будет дополнительно оповещать сервер о наличии подключения, используя встроенную реализацию.
Обработка сообщений
На этом с сервером закончили. Полный код файла ChatWorker.php доступен на GitHub: https://github.com/wxmaper/SimpleChat-server
После тестирования websocket-сервер можно запустить в режиме службы, для этого нужно добавить параметр -d:
Перезапуск выполняется командой restart:
3. Клиент
Примитивное приложение-клиент будет реализовано с использованием модуля QtWidgets и написано на C++. Сообщения будут отображаться в обычном текстовом поле в режиме readonly (привет любителям лампового IRC :-)).
Приложение будет иметь возможность подсвечивать разными цветами имена пользователей, а так же вставлять имя пользователя в поле сообщения при клике на его ник.
Режим приватного чата активируется двойным кликом по имени в списке пользователей, а закрывается этот режим специальной кнопкой [x], которая по умолчанию скрыта.
Сообщения отправляются нажатием на кнопку Return (Enter) на клавиатуре.
Прототип окна чата
Диалог авторизации будет вызываться сразу после запуска приложения, а так же при разрыве соединений.
В рамках этой статьи рассмотрим лишь основные моменты.
Установка соединения с сервером
Если же ввести все авторизационные данные, то будет осуществлена попытка подключения к серверу.
Обработка сообщений
Вся логика общения клиента с сервером реализована в слоте Widget::onTextMessageReceived – тут проверяется тип входящего сообщения (какое он имеет «действие») и вызываются соответствующие методы для его обработки.
Оповещение пользователя
Если кликнуть по имени пользователя, то имя вставляется в поле ввода сообщения и заворачивается в фигурные скобки: «<» и «>».
На сервере имеется паттерн обработки такого текста (функция $worker->onMessage), который заменяет фигурные скобки на теги « » и « », выделяя текст жирным шрифтом.
Таким образом, когда пользователь получает сообщение, можно проверить наличие этих тегов и содержимого в них. Если в тегах содержится имя текущего пользователя, значит, в сообщении кто-то упомянул этого пользователя и надо его об этом уведомить. Это реализовано в методе обработки публичных сообщений:
По такой же схеме можно реализовать полноценную поддержку markdown, вставку смайликов и картинок.
Расширяя функционал сервера и клиента можно также добавить:
Скриншот получившегося чата был в начале статьи, дополнительно приведу пример реального чата, реализованного по описанной в статье модели
Вебсокеты на php. Выбираем вебсокет-сервер
Давным-давно я публиковал статью на хабре, как написать свой вебсокет-сервер с нуля. Статья переросла в библиотеку. Несколько месяцев я занимался её развитием, ещё несколько лет — поддержкой и багфиксом. Написал модуль интеграции с yii2. Какой-то энтузиаст написал интеграцию с laravel. Моя библиотека совместима с php7. Недавно я решил отказаться от её дальнейшей поддержки (причины ниже), поэтому хочу помочь её пользователям перейти на другую библиотеку.
Прежде чем начать писать свой вебсокет-сервер, я выбирал из готовых продуктов, и на тот момент их было всего два: phpdaemon и ratchet.
phpdaemon
Ratchet
В итоге я написал библиотеку для себя и поделился ею с сообществом на гитхабе. Сделал несколько демок (в том числе игру «танчики»). Переписал стороннюю игру (с разрешения авторов) с node.js на свою библиотеку. Делал нагрузочное тестирование. Демки работали годами без перезагрузки. Старался отвечать на тикеты в течения дня. Всё это показывало, что моя библиотека может быть использована на продакшене и многие её использовали.
Была единственная проблема. Мне хватало моей библиотеки для использования в своих проектах, а вот другим нет. Они хотели, чтобы я её развивал, а мне это было не нужно. Кому-то требовалась поддержка windows, а кому-то ssl, pg_notify, safari, pthreads и многое другое. Открытые тикеты с запросами на реализацию различного функционала висят годами.
Не так давно, я решил пересмотреть ещё раз, какие продукты могут быть полезны для пользователей моей библиотеки и был приятно удивлён, что кроме двух проектов, описанных выше появился ещё третий. Он полностью удовлетворял моим запросам и даже больше.
Workerman
Если загуглить «php websocket», то первая страница — это моя статья на Хабре, а вторая — «Ratchet», который кому-то может показаться сложным и он выберет из-за этого мою библиотеку или вообще откажется от идеи делать вебсокеты.
Что ж, пришло время исправить эту досадную ошибку и донести до как можно большего количества людей о существовании такой библиотеки как Workerman и привести несколько примеров по её использованию.
На главной странице проекта в гитхабе уже есть несколько примеров. Рассмотрим один из них:
В принципе, используя первый пример можно сделать чат на вебсокетах и других примеров не нужно. Но за несколько лет я понял, что в основном пользователям моей библиотеки был нужен пример того как можно отправить из своего кода на php уведомление выбранному пользователю, а не всем одновременно, как часто бывает в примерах.
код сервера server.php:
код клиента client.html:
код отправки сообщений с нашего сайта send.php:
Справедливости ради я решил написать такой же пример для ratchet, но документация мне не помогла, как 3 года назад. Зато на stackoverflow предложили немного костыльный, но рабочий вариант: соединяться из своего php-скрипта по ws-соединению. Конечно это не так же просто как соединиться с tcp-сокетом с помощью stream_socket_client и отправить сообщение с помощью fwrite. Но уже что-то.
Плюс ещё остался для меня незакрытый вопрос: поддерживает ли ratchet возможность запуска нескольких воркеров и если да, то как в таком случае отправлять сообщение одному пользователю, ведь не понятно на каком он воркере. На workerman это можно сделать так.
В общем, я выбрал для себя библиотеку Workerman и рекомендую переходить на неё пользователям моей библиотеки. Все примеры лежат на гитхабе.
Update: в комментариях рекомендуют swoole. Я натыкался на эту библиотеку ранее, но у меня сложилось ложное впечатление, что что она не поддерживает php7 и после этого она выпала из моего круга зрения. А зря. Интересная библиотека.