Функция PHP socket_set_blocking(): всё, что нужно знать
Узнайте, как управлять блокирующим и неблокирующим режимами сокетов в PHP с помощью socket_set_blocking() и её актуальных замен.
При написании сетевого кода на PHP часто требуется управлять тем, ожидает ли вызов ввода-вывода данных (блокирующий режим) или возвращается немедленно (неблокирующий режим). Функция socket_set_blocking() исторически использовалась как псевдоним для установки этого режима. В PHP 8.1 она была объявлена устаревшей, и использовать её в новом коде не следует.
Важный нюанс: правильная замена зависит от типа соединения, и именно здесь многие руководства допускают ошибку:
- Если вы использовали расширение Sockets (
socket_create(), возвращающее объектSocket), применяйтеsocket_set_nonblock()иsocket_set_block(). - Если вы использовали поток (
fsockopen()илиstream_socket_client(), возвращающие потоковый ресурс), применяйтеstream_set_blocking().
socket_set_blocking() фактически являлась псевдонимом stream_set_blocking(), поэтому она всегда работала только с потоковыми ресурсами — но не с объектами Socket. Именно это различие позволяет избежать ошибки TypeError во время выполнения.
Блокирующий и неблокирующий режим
В блокирующем режиме (по умолчанию) вызов чтения или записи приостанавливает выполнение скрипта до завершения операции. Вызов socket_read() на пустом сокете просто ждёт поступления байтов.
В неблокирующем режиме тот же вызов возвращается немедленно. Если данные ещё не готовы, он возвращает пустой результат (или false) вместо ожидания. Это позволяет одному скрипту обслуживать множество соединений или сохранять отзывчивость — ценой необходимости опрашивать состояние и проверять, действительно ли поступили данные.
| Используйте блокирующий режим, когда… | Используйте неблокирующий режим, когда… |
|---|---|
| Вы обрабатываете одно соединение за раз | Вы обслуживаете множество клиентов в одном цикле |
| Простота важнее пропускной способности | Скрипт должен оставаться отзывчивым |
| Короткий, предсказуемый запрос/ответ | Вы реализуете собственный цикл опроса/событий |
Установка режима для объекта Socket
Если соединение создано с помощью расширения Sockets, используйте socket_set_nonblock() и socket_set_block():
socket_set_nonblock(Socket $socket): bool
socket_set_block(Socket $socket): boolОбе функции возвращают true при успехе и false при ошибке. Полный, работоспособный пример с обработкой ошибок и освобождением ресурсов:
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
die("Socket creation failed: " . socket_strerror(socket_last_error()) . "\n");
}
// Switch to non-blocking mode
if (!socket_set_nonblock($socket)) {
die("Failed to set non-blocking mode\n");
}
echo "Socket is now non-blocking.\n";
// ... perform non-blocking socket operations ...
// Switch back to blocking mode if needed
socket_set_block($socket);
echo "Socket is now blocking again.\n";
socket_close($socket);Не вызывайте
stream_set_blocking($socket, false)для объектаSocket—socket_create()возвращает экземплярSocket, аstream_set_blocking()принимает только потоковый ресурс. Передача объектаSocketвызоветTypeError.
Установка режима для потока
Если соединение открыто с помощью fsockopen() или stream_socket_client(), у вас есть потоковый ресурс, и следует использовать stream_set_blocking():
stream_set_blocking(resource $stream, bool $enable): bool$stream: настраиваемый потоковый ресурс.$enable:trueдля блокирующего режима,falseдля неблокирующего.
<?php
$stream = stream_socket_client('tcp://example.com:80', $errno, $errstr, 5);
if ($stream === false) {
die("Connect failed: $errstr ($errno)\n");
}
// Switch the stream to non-blocking mode
if (!stream_set_blocking($stream, false)) {
die("Failed to set non-blocking mode\n");
}
echo "Stream is now non-blocking.\n";
fclose($stream);Цикл неблокирующего чтения
Неблокирующий режим полезен только при наличии опроса. Типичный паттерн отправляет запрос, а затем многократно проверяет ответ без заморозки скрипта:
<?php
$stream = stream_socket_client('tcp://example.com:80', $errno, $errstr, 5);
if ($stream === false) {
die("Connect failed: $errstr ($errno)\n");
}
stream_set_blocking($stream, false);
fwrite($stream, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");
$response = '';
$start = time();
while (!feof($stream) && time() - $start < 5) {
$chunk = fread($stream, 8192);
if ($chunk === '' || $chunk === false) {
// No data yet — do other work or wait briefly
usleep(50000); // 50 ms
continue;
}
$response .= $chunk;
}
echo substr($response, 0, 15), "\n"; // e.g. "HTTP/1.1 200 OK"
fclose($stream);Вызов usleep() предотвращает интенсивный цикл опроса, нагружающий процессор. В продакшене этот ручной опрос обычно заменяют на stream_select() для эффективного ожидания данных сразу на нескольких потоках.
Примечания о версиях
- PHP 8.1: функция
socket_set_blocking()объявлена устаревшей. Её вызов генерирует уведомление об устаревании. - Функция являлась псевдонимом
stream_set_blocking(), поэтому никогда не принимала объектыSocket. - Для объектов
Socketфункцииsocket_set_nonblock()/socket_set_block()всегда были правильным вариантом и по-прежнему поддерживаются.
Связанные функции
socket_get_status()— получить состояние сокета, в том числе информацию о блокировке.socket_set_timeout()— управлять временем ожидания блокирующей операции перед прерыванием.- PHP Streams — более широкий API потоков, частью которого является
stream_set_blocking().
Заключение
Управление режимом блокировки соединения необходимо для создания отзывчивых сетевых приложений на PHP. Главное — использовать функцию, соответствующую типу соединения: socket_set_nonblock() / socket_set_block() для объектов Socket и stream_set_blocking() для потоковых ресурсов. Избегайте устаревшей socket_set_blocking() в новом коде и помните, что неблокирующий режим оправдывает себя только в сочетании с циклом опроса.