Функция PHP socket_set_timeout(): всё, что нужно знать
Узнайте, как использовать socket_set_timeout() в PHP для установки таймаута на операции чтения/записи в потоке и избежания зависания скрипта.
Когда ваш скрипт читает данные из сетевого соединения, медленный или недоступный сервер может привести к бесконечной блокировке PHP, что занимает рабочий процесс и раздражает пользователей. Функция socket_set_timeout() устанавливает предельное время для операций чтения/записи, чтобы зависшее соединение быстро завершалось с ошибкой, а не висело. В этой статье объясняется, что именно она контролирует, какие есть распространённые подводные камни и как обнаружить таймаут, когда он происходит.
Что делает функция socket_set_timeout()
socket_set_timeout() устанавливает таймаут для операций ввода/вывода — fread(), fgets(), fwrite() и т. д. — для потока, открытого с помощью fsockopen() или pfsockopen(). Если операция чтения или записи не завершается в установленный срок, она возвращается досрочно, а поток помечается как тайм-аут.
Два действия, которые она не выполняет:
- Она не влияет на таймаут соединения. Это пятый аргумент функции
fsockopen($host, $port, $errno, $errstr, $connectTimeout).socket_set_timeout()управляет только передачей данных после того, как соединение установлено. - Она не завершает вызов с явной ошибкой. Операция чтения, при которой произошёл таймаут, возвращает данные, полученные к тому моменту (часто пустую строку), и устанавливает флаг — вы должны проверить этот флаг самостоятельно с помощью
stream_get_meta_data().
Ловушка с именованием: несмотря на префикс
socket_, эта функция относится к семейству потоков, а не к расширению Sockets. Она работает с ресурсами отfsockopen(), но никогда с ресурсами отsocket_create(). Начиная с PHP 8.0 она является устаревшим псевдонимомstream_set_timeout()— в новом коде используйте это имя.
Синтаксис
socket_set_timeout(resource $stream, int $seconds, int $microseconds = 0): bool| Параметр | Описание |
|---|---|
$stream | Открытый ресурс потока, возвращённый функцией fsockopen() или pfsockopen(). |
$seconds | Таймаут в целых секундах. |
$microseconds | Дополнительное время в микросекундах (необязательно, по умолчанию 0). |
Возвращает true в случае успеха и false при ошибке (например, если $stream не является допустимым ресурсом потока).
Рабочий пример
Откройте соединение, установите таймаут чтения в 5 секунд, затем проверьте, произошёл ли таймаут при чтении:
<?php
// Open a TCP connection to a web server.
$stream = fsockopen("www.example.com", 80, $errno, $errstr, 10);
if (!$stream) {
echo "Connection failed: $errstr ($errno)\n";
exit;
}
// Fail any single read/write that stalls for more than 5 seconds.
socket_set_timeout($stream, 5);
// Send a minimal HTTP request.
fwrite($stream, "GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n");
// Read the first line of the response.
$line = fgets($stream, 1024);
// Check whether that read hit the timeout.
$info = stream_get_meta_data($stream);
if ($info['timed_out']) {
echo "Read timed out — the server was too slow.\n";
} else {
echo "First response line: " . trim($line) . "\n";
}
fclose($stream);Ключевым является вызов stream_get_meta_data(): элемент timed_out — это единственный надёжный способ отличить настоящий таймаут от соединения, которое просто закрылось.
Чтение в цикле
При чтении целого ответа проверяйте timed_out на каждой итерации, чтобы зависание в середине передачи не приводило к незаметному усечению данных:
<?php
$stream = fsockopen("www.example.com", 80, $errno, $errstr, 10);
socket_set_timeout($stream, 5);
fwrite($stream, "GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n");
$body = "";
while (!feof($stream)) {
$chunk = fgets($stream, 4096);
$info = stream_get_meta_data($stream);
if ($info['timed_out']) {
echo "Stalled before the response finished.\n";
break;
}
$body .= $chunk;
}
fclose($stream);
echo "Received " . strlen($body) . " bytes.\n";Распространённые подводные камни
- Неправильный тип ресурса. Передача ресурса от
socket_create()не даёт полезного результата — используйтеstream_set_timeout()для сокетовsocket_create()илиsocket_set_option()дляSO_RCVTIMEO/SO_SNDTIMEO. - Путаница между таймаутами соединения и чтения. Большой таймаут соединения
fsockopen()не спасёт вас от медленного ответа; вам нужны оба. - Забыть проверить
timed_out. Без этой проверки операция чтения с таймаутом выглядит точно так же, как чистое завершение потока, что приводит к незаметному усечению данных.
Связанные функции
fsockopen()— открывает поток, с которым работает эта функция.stream_get_meta_data()через socket_get_status() — читает флагtimed_out.socket_set_blocking()— переключает поток между блокирующим и неблокирующим режимом.fgets()иfwrite()— вызовы ввода/вывода, к которым применяется таймаут.
Заключение
socket_set_timeout() предотвращает зависание скрипта PHP из-за медленных операций чтения и записи в сеть. Помните, что она работает с потоками fsockopen() (а не с расширением Sockets), управляет вводом/выводом, а не соединением, и что для определения факта таймаута необходимо проверять флаг timed_out в результате stream_get_meta_data(). В новом коде используйте современное имя — stream_set_timeout().