Socket set nonblock: примеры (PHP)
socket_set_nonblock(resource socket): boolФункция socket_set_nonblock переключает сокет в неблокирующий режим. Это означает, что операции ввода-вывода (например, чтение или запись) будут возвращать управление немедленно, даже если данных нет или буфер полон. Вместо ожидания система возвращает ошибку EWOULDBLOCK или EAGAIN.
Использование функции актуально при реализации асинхронных сокетов, в мультиплексировании ввода-вывода с помощью select, poll, или в event-циклах. Это позволяет одному процессу обслуживать множество соединений без блокировок.
Функция принимает один обязательный аргумент:
- socket (ресурс сокета) – Сокет, созданный с помощью socket_create или socket_accept. Этот аргумент обязателен. Ресурс должен быть валидным сокетом.
Возвращаемое значение: логическое значение. true в случае успеха, false при возникновении ошибки.
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '127.0.0.1', 0);
socket_listen($socket);
// Переключаем сокет в неблокирующий режим
$result = socket_set_nonblock($socket);
if ($result === false) {
echo "Не удалось установить неблокирующий режим: " . socket_strerror(socket_last_error($socket));
} else {
echo "Сокет переведен в неблокирующий режим.\n";
}
socket_close($socket);
?>Сокет переведен в неблокирующий режим.
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($socket);
// Попытка подключения к несуществующему порту для демонстрации
$connect = @socket_connect($socket, '127.0.0.1', 9999);
// В неблокирующем режиме connect возвращает управление сразу
$read = [$socket];
$write = [$socket];
$except = [];
// Ждем 1 секунду
$changed = socket_select($read, $write, $except, 1);
if ($changed === false) {
echo "Ошибка select: " . socket_strerror(socket_last_error($socket));
} elseif ($changed > 0) {
echo "Сокет готов для операций.\n";
} else {
echo "Сокет не готов за отведенное время.\n";
}
socket_close($socket);
?>Сокет не готов за отведенное время.
В PHP существуют другие функции для управления режимом сокетов:
- socket_set_block – Переключает сокет обратно в блокирующий режим. Применяется, когда требуется синхронная работа.
- stream_set_blocking – Аналогичная функция для потоков (streams), созданных, например, через fsockopen. Удобна при работе с высокоуровневыми API.
- socket_select, socket_poll – Функции мультиплексирования, которые часто используются вместе с неблокирующими сокетами для мониторинга нескольких соединений.
Выбор между socket_set_nonblock и stream_set_blocking зависит от используемого API. Для низкоуровневых сокетов предпочтительнее socket_set_nonblock, для потоков – stream_set_blocking.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(0) # Неблокирующий режим
# Или эквивалентно:
# sock.settimeout(0.0)
# Попытка подключения
sock.connect_ex(('127.0.0.1', 9999))
print("Операция завершена немедленно, даже если соединение не установлено")Операция завершена немедленно, даже если соединение не установлено
const net = require('net');
const socket = new net.Socket();
socket.setTimeout(0); // Отключает таймаут
// В Node.js сокеты по умолчанию неблокирующие для операций connect, read, write
socket.connect(9999, '127.0.0.1', () => {});
socket.on('error', (err) => {
console.log('Ошибка подключения:', err.message);
});
console.log('Продолжение выполнения без блокировки');Продолжение выполнения без блокировки Ошибка подключения: connect ECONNREFUSED 127.0.0.1:9999
#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
printf("Сокет переведен в неблокирующий режим\n");Сокет переведен в неблокирующий режим
<?php
$result = socket_set_nonblock(null);
if ($result === false) {
echo "Ошибка: " . socket_strerror(socket_last_error());
}
?>Ошибка: Invalid argument
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($socket);
socket_connect($socket, '127.0.0.1', 80);
$data = socket_read($socket, 1024);
if ($data === false) {
$error = socket_last_error($socket);
if ($error == SOCKET_EAGAIN || $error == SOCKET_EWOULDBLOCK) {
echo "Нет доступных данных для чтения. Продолжение работы.\n";
} else {
echo "Критическая ошибка: " . socket_strerror($error);
}
}
?>Нет доступных данных для чтения. Продолжение работы.
В PHP 8 функция socket set nonblock не претерпела значительных изменений в сигнатуре или поведении. Однако, начиная с PHP 8.0, многие функции сокетов выбрасывают исключения Error при передаче неверных аргументов, вместо генерации предупреждений и возврата false. Это улучшает обработку ошибок.
Пример изменения:
<?php
// В PHP 7.x передача null приводила к предупреждению и возврату false.
// В PHP 8.0+ это может вызвать исключение TypeError.
// Лучшая практика – проверять аргументы до вызова.
?><?php
$sockets = [];
for ($i = 0; $i < 3; $i++) {
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($sock);
@socket_connect($sock, '127.0.0.1', 8000 + $i);
$sockets[] = $sock;
}
while (true) {
$read = $sockets;
$write = $sockets;
$except = null;
$timeout = 5;
$changed = socket_select($read, $write, $except, $timeout);
if ($changed === false) {
break;
}
foreach ($write as $writableSocket) {
$index = array_search($writableSocket, $sockets, true);
echo "Сокет $index готов для записи\n";
// Здесь можно отправить данные
}
foreach ($read as $readableSocket) {
$data = socket_read($readableSocket, 1024);
if ($data !== false && $data !== '') {
echo "Получены данные: $data\n";
}
}
sleep(1);
}
foreach ($sockets as $sock) {
socket_close($sock);
}
?>Сокет 0 готов для записи Сокет 1 готов для записи Сокет 2 готов для записи ... (цикл продолжается)
<?php
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '0.0.0.0', 12345);
socket_listen($server);
socket_set_nonblock($server);
$clients = [];
while (true) {
// Принятие новых подключений без блокировки
if (($client = socket_accept($server)) !== false) {
socket_set_nonblock($client);
$clients[] = $client;
echo "Новый клиент подключен.\n";
}
// Обработка данных от клиентов
foreach ($clients as $index => $client) {
$data = socket_read($client, 1024);
if ($data !== false && $data !== '') {
echo "Получено от клиента $index: $data";
// Эхо-ответ
socket_write($client, "Echo: $data");
} elseif ($data === '') {
// Соединение закрыто клиентом
socket_close($client);
unset($clients[$index]);
echo "Клиент $index отключен.\n";
}
}
usleep(100000); // 100 мс для снижения нагрузки на CPU
}
?>Новый клиент подключен. Получено от клиента 0: Привет, сервер! Клиент 0 отключен.