Привет, Хабр!
Swoole — высокопроизводительной асинхронный и многопоточный фреймворк для PHP. Он отличается от традиционной модели PHP-FPM, предлагая асинхронный ввод-вывод и корутины, а также возможность работать с веб-сокетами и различными сетевыми протоколами непосредственно в PHP.
Установим
Swoole доступен как расширение PECL:
sudo apt update
sudo apt install php php-dev php-pear
После установки добавляемextension=openswoole.so
в файл php.ini
, чтобы PHP мог загружать Swoole при запуске.
Устанавливаем Swoole через Composer как зависимость проекта:
composer require openswoole/core
С Докером можно изолировать установку и эксперименты с Swoole. Можно юзать официальный образ PHP с предустановленным Swoole или настроить свой. Создаем dockerfile:
FROM php:7.4-cli
RUN pecl install openswoole && docker-php-ext-enable openswoole
Собираем образ:
docker build -t php-swoole .
Запускаем контейнер и монтируем проект внутрь контейнера для тестирования:
docker run -p 9501:9501 -v $(pwd):/app -w /app php-swoole php your-script.php
Основная архитектура сервера Swoole
Процесс Master является основным и отвечает за запуск и управление остальными процессами. Master-процесс создает реакторные потоки, которые работают с сетевыми соединениями. Он также инициирует Manager-процесс для управления воркерами. Master-процесс также обрабатывает сигналы от ОС и занимается общей организацией работы сервера.
Реакторные потоки, созданные внутри Master-процесса, обрабатывают сетевые соединения с клиентами. Они используют асинхронный ввод-вывод для приема и передачи данных, применяя модели на основе событийного цикла, такие как epoll
или kqueue
. Реакторные потоки принимают данные от клиентов и передают их в рабочие процессы (о них чуть ниже).
Процесс Manager процесс отвечает за управление жизненным циклом рабочих процессов и задач. Он отслеживает состояние рабочих процессов и перезапускает их в случае отказа или завершения работы.
Рабочие процессы запускаются Manager-процессом и выполняют основную бизнес-логику. Они получают запросы от реакторных потоков, обрабатывают их и возвращают результаты обратно. Каждый рабочий процесс может быть настроен на выполнение определенного типа задач. Код приложения разрабатывается для запуска в этих процессах.
Задачные процессы (task workers) получают задачи от рабочих процессов и обрабатывают их параллельно. Они используются для выполнения операций, которые могут блокировать или замедлять основной поток событий, таких как длительные вычисления или взаимодействие с внешними API. Рабочие процессы отправляют задачи с помощью функций task
, taskwait
, и taskWaitMulti
.
Примерно так выглядит процесс обработки запросов:
Клиент отправляет запрос, который принимается реакторным потоком Master-процесса.
Реакторный поток передает данные в рабочий процесс для выполнения основной бизнес-логики.
Рабочий процесс может обрабатывать запрос сам или делегировать выполнение задач задачным процессам.
Задачный процесс возвращает результат в рабочий процесс, который затем отправляет данные обратно клиенту через реакторный поток.
Кратко про основной синтаксис
Инициализация сервера происходит через создание экземпляра OpenSwoole\HTTP\Server
.
Сервер обрабатывает события через методы on
, такие как start
и request
.
Для отправки ответа используется метод end
объекта Response
.
Пример инициализации HTTP сервера:
$server = new OpenSwoole\HTTP\Server("127.0.0.1", 9501);
$server->on("request", function ($request, $response) {
$response->end("Hello World\n");
});
$server->start();
Swoole поддерживает написание асинхронного кода через корутины. Методы go
или co::run
, используются для запуска корутин.
WebSocket сервер настраивается аналогично HTTP серверу с использованием событий open
, message
и close
.
Swoole позволяет организовывать асинхронную обработку задач через Task Workers.
Для распределения задач используется метод task
, а обработка завершения задачи выполняется через finish
.
Пример настройки Task Workers:
$server->set(['task_worker_num' => 4]);
$server->on('task', function ($server, $taskId, $data) {
// обработка задачи
});
$server->on('finish', function ($server, $taskId, $returnValue) {
// завершение задачи
});
Swoole предоставляет классы для создания асинхронных TCP, UDP, HTTP клиентов.
Реализация TCP/UDP клиентов и серверов на Swoole
Для создания TCP/UDP сервера в Swoole используется класс OpenSwoole\Server
. Сервер настраивается через метод set
, где можно задать различные параметры, например - количество рабочих процессов. События сервера обрабатываются через метод on
, который позволяет реагировать на различные события:
$server = new OpenSwoole\Server("127.0.0.1", 9501);
$server->set(['worker_num' => 4]);
$server->on('receive', function ($server, $fd, $reactor_id, $data) {
$server->send($fd, 'Received: '.$data);
$server->close($fd);
});
$server->start();
Swoole предоставляет различные классы для создания TCP и UDP клиентов. Например, OpenSwoole\Coroutine\Client
позволяет создать асинхронного клиента, который может коннектится, отправлять и получать данные в неблокирующем режиме, используя корутины:
use OpenSwoole\Coroutine\Client;
co::run(function() {
$client = new Client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9501, 0.5)) {
echo "Connection failed: {$client->errCode}\n";
}
$client->send("Hello World\n");
echo $client->recv();
$client->close();
});
Обработка HTTP и WebSocket запросов с использованием корутин
Swoole предоставляет класс OpenSwoole\HTTP\Server
для создания HTTP сервера. Можно обрабатывать HTTP запросы, используя корутины, что позволяет управлять множеством запросов параллельно без блокировки и с минимальными задержками.
Пример создания HTTP сервера:
use OpenSwoole\HTTP\Server;
use OpenSwoole\Http\Request;
use OpenSwoole\Http\Response;
$server = new Server("0.0.0.0", 9501);
$server->on("request", function (Request $request, Response $response) {
// Обработка запроса
$response->end("Hello, Swoole HTTP!");
});
$server->start();
Сервер использует событийную модель, где событие request
активируется каждый раз, когда сервер получает новый HTTP запрос. Корутины позволяют обрабатывать асинхронные операции.
WebSocket сервер в Swoole обеспечивает полнодуплексное общение по TCP соединению. Пример создания WebSocket сервера:
use OpenSwoole\WebSocket\Server;
use OpenSwoole\Http\Request;
use OpenSwoole\WebSocket\Frame;
$server = new Server("0.0.0.0", 9502);
$server->on("open", function (Server $server, Request $request) {
echo "Новое соединение: {$request->fd}\n";
});
$server->on("message", function (Server $server, Frame $frame) {
echo "Получено сообщение: {$frame->data}\n";
$server->push($frame->fd, "Эхо: {$frame->data}");
});
$server->on("close", function ($ser, $fd) {
echo "Соединение закрыто: {$fd}\n";
});
$server->start();
Событие open
активируется при подключении нового клиента, message
— когда сервер получает сообщение от клиента, и close
— когда клиент закрывает соединение.
Пример
Хороший пример работы с Swolle - это создание WebSocket сервера с механизмами для улучшенной безопасности и функциональности, включая маршрутизацию запросов и использование корутин:
<?php
use OpenSwoole\WebSocket\Server as WebSocketServer;
use OpenSwoole\Http\Request;
use OpenSwoole\WebSocket\Frame;
// маршруты для WebSocket запросов
$routes = [
'/chat' => 'handleChat',
'/notify' => 'handleNotifications',
];
// проверка допустимости маршрута
function checkRoute(string $path): bool {
global $routes;
return array_key_exists($path, $routes);
}
// обработчик чата
function handleChat($server, $frame) {
// простая логика для отправки ответа клиенту
$server->push($frame->fd, "Чат: {$frame->data}");
}
// обработчик уведомлений
function handleNotifications($server, $frame) {
// пример: отправка уведомления всем подключенным клиентам
foreach ($server->connections as $fd) {
if ($server->isEstablished($fd)) {
$server->push($fd, "Уведомление: {$frame->data}");
}
}
}
// создание WebSocket сервера
$server = new WebSocketServer("0.0.0.0", 9502);
// событие запуска сервера
$server->on("start", function(WebSocketServer $server) {
echo "Сервер запущен на ws://0.0.0.0:9502\n";
});
// установка SSL для безопасности
$server->set([
'ssl_cert_file' => '/path/to/your/cert.pem',
'ssl_key_file' => '/path/to/your/key.pem',
'open_http2_protocol' => true,
'worker_num' => 4,
'daemonize' => false,
]);
// установка события подключения
$server->on("open", function (WebSocketServer $server, Request $request) {
echo "Новое подключение от клиента: {$request->fd}\n";
// проверка допустимого маршрута
if (!checkRoute($request->server['request_uri'])) {
$server->disconnect($request->fd, 400, "Неверный маршрут");
}
});
// обработка сообщений от клиентов
$server->on("message", function (WebSocketServer $server, Frame $frame) {
// в этом примере предполагается, что первый символ данных — идентификатор маршрута
global $routes;
$routeKey = '/'.trim($frame->data);
if (isset($routes[$routeKey])) {
call_user_func($routes[$routeKey], $server, $frame);
} else {
$server->push($frame->fd, "Неверный маршрут");
}
});
// событие закрытия соединения
$server->on("close", function ($ser, $fd) {
echo "Соединение закрыто: {$fd}\n";
});
$server->start();
А теперь попробуем создать полнофункциональный HTTP-сервер:
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$server = new Server("0.0.0.0", 9501);
// настройки сервера
$server->set([
'worker_num' => 4, // колво рабочих процессов
'ssl_cert_file' => '/path/to/ssl_cert.pem',
'ssl_key_file' => '/path/to/ssl_key.pem',
]);
// маршруты
$routes = [
'/login' => function ($request, $response) {
// логика аутентификации
$response->end(json_encode(['status' => 'success', 'message' => 'Logged in']));
},
'/data' => function ($request, $response) {
// защищенный маршрут, требующий аутентификации
$response->end(json_encode(['data' => 'secret data']));
}
];
// обработчик запросов
$server->on("request", function (Request $request, Response $response) use ($routes) {
$path = $request->server['request_uri'];
$method = $request->server['request_method'];
// валидация HTTP метода
if ($method !== 'GET' && $method !== 'POST') {
$response->status(405);
$response->end();
return;
}
// проверка наличия маршрута
if (isset($routes[$path])) {
$routes[$path]($request, $response);
} else {
$response->status(404);
$response->end("Not found");
}
});
$server->start();
Подробнее с Swolle можно ознакомиться в документации.
В заключение напоминаю про открытые уроки по PHP:
14 мая: Сложные логические операции в PHP — детально разберём в игровой форме сложные логические операции. Записаться
20 мая: Что нового в PHP 8.3? — посмотрим, что нового нам принесла новая минорная версия, и как это можно применять. Записаться
Комментарии (5)
gruzoveek
14.05.2024 07:35+3А обязательно писать так?
global $routes;
Нельзя например так?
function (WebSocketServer $server, Frame $frame) use ($routes)
gimcnuk
14.05.2024 07:35_unexpected token "use"_
только для анонимных функций
FanatPHP
14.05.2024 07:35+1@gruzoveekпишет про другое использование global, разумеется, как раз в анонимной функции.
FanatPHP
14.05.2024 07:35+3Это же badcasedaily1, ему тексты пишет GPT, а сам он не понимает ничего в этом коде. То же самое с динамическим вызовом функций: во втором примере нормально,
$routes[$path]($request, $response);
, а в первом откуда-то ископаемаяcall_user_func()
. Если внимательно смотреть, то таких галлюцинаций по тексту много. Даже название перевирается постоянно.
ponikrf
Все это какие то костыли и подпорки для PHP. Представляю сколько гемора работать с этим без полноценных async await. Хотя о чем это я, это же просто реклама уроков по PHP.