Идентификатор потока в PHP: методы и примеры
Что такое идентификатор потока и зачем он нужен
Идентификатор потока (stream id) это уникальный числовой номер, присваиваемый каждому открытому потоку в PHP. Потоками могут быть файлы, сокеты, HTTP соединения, каналы proc_open и другие. Знание идентификатора полезно для отслеживания потоков в логах, сравнения двух переменных на принадлежность одному потоку, организации мультиплексирования при работе с сокетами и предотвращения утечек ресурсов.
Основное решение функция stream_get_id()
Как получить идентификатор любого потока в PHP 8 и выше?
Начиная с PHP 8.0.0 в языке появилась встроенная функция stream_get_id, которая принимает потоковый ресурс (объект) и возвращает его числовой идентификатор (int). Это наиболее прямой и эффективный способ.
<?php
// Открываем файловый поток
$file = fopen('/tmp/test.txt', 'w');
// Получаем ID
$id = stream_get_id($file);
echo "ID файлового потока: $id\n";
fclose($file);
// Открываем TCP сокет
$sock = stream_socket_client('tcp://example.com:80');
$sockId = stream_get_id($sock);
echo "ID сокетного потока: $sockId\n";
fclose($sock);
?>
ID файлового потока: 4 ID сокетного потока: 5
Цель использования быстрый доступ к уникальному ключу для идентификации потока. Например, для логирования или в циклах с stream_select.
Проблема: функция доступна только начиная с PHP 8.0. Если код выполняется на более старой версии, возникает фатальная ошибка.
Решение: перед вызовом проверить существование функции через function_exists.
if (function_exists('stream_get_id')) {
$id = stream_get_id($stream);
} else {
// альтернативный метод
}
Вариант 1 приведение потока к целому числу (для PHP 7 и ниже)
Как получить идентификатор потока, если stream_get_id недоступна?
В PHP 7 и более ранних версиях поток представлял собой ресурс. Приведение ресурса к целому числу с помощью intval() или (int) возвращало его внутренний идентификатор. В PHP 8 ресурсы заменены на объекты, поэтому этот способ перестал работать.
<?php
// старый способ (PHP 7)
$file = fopen('/tmp/test.txt', 'r');
$id = (int) $file; // или intval($file)
echo "ID потока (старый): $id\n";
fclose($file);
?>
ID потока (старый): 3
Проблема: после PHP 8 такой код выдаст ошибку, так как объект нельзя привести к int. Кроме того, в PHP 7 идентификатор мог быть переиспользован после закрытия потока.
Решение: использовать только для устаревших проектов и обязательно проверять версию PHP. Рекомендуется по возможности перейти на stream_get_id.
Цель использования поддержка обратной совместимости в коде, который раньше полагался на этот трюк.
Вариант 2 spl_object_id() для объектов потоков
Можно ли использовать spl_object_id вместо stream_get_id?
В PHP 8 потоки являются объектами (экземплярами внутреннего класса, например, stream или Socket). Функция spl_object_id возвращает уникальный идентификатор любого объекта в течение его жизни. Однако этот идентификатор не обязательно совпадает с внутренним номером потока, используемым системой. Более того, он может быть повторно использован для другого объекта после удаления исходного. Поэтому не рекомендуется полагаться на него для идентификации потоков.
<?php
$f = fopen('/tmp/test.txt', 'r');
$oid = spl_object_id($f);
echo "spl_object_id: $oid\n";
$id = stream_get_id($f);
echo "stream_get_id: $id\n";
fclose($f);
?>
spl_object_id: 1 stream_get_id: 3
Как видно, значения различаются. Использование spl_object_id может быть оправдано только в контексте отслеживания самого объекта, а не системного потока.
Проблема: несоответствие системному идентификатору, потенциальная путаница. Нельзя сравнивать с идентификаторами, полученными извне (например, из логов сервера).
Цель редкие случаи, когда нужно просто отличать один объект потока от другого без привязки к системному номеру.
Вариант 3 функция get_resource_id() (устаревшая)
Какой был способ до появления stream_get_id в PHP 7?
В PHP 7 существовала функция get_resource_id(), которая возвращала идентификатор ресурса. Она работала только с ресурсами, а не с объектами потоков. После перехода на объекты в PHP 8 она также устарела и в конечном итоге была удалена. Если в проекте используется PHP 7, можно применить её, но лучше переписать код.
<?php
// PHP 7
$file = fopen('/tmp/test.txt', 'r');
$id = get_resource_id($file);
echo "get_resource_id: $id\n";
fclose($file);
?>
Проблема: функция не поддерживается в PHP 8, вызовет ошибку. Кроме того, в PHP 7.2 она уже была помечена как устаревшая.
Решение: заменить на stream_get_id при обновлении PHP.
Цель временное решение для уже написанного кода на PHP 7.x.
Вариант 4 использование stream_get_meta_data и других обходных путей
Как идентифицировать поток без встроенного ID?
Иногда можно обойтись без числового идентификатора, анализируя метаданные потока. Функция stream_get_meta_data возвращает информацию о потоке, включая тип, режим, uri (если доступен). Для уникальной идентификации можно комбинировать эти данные, но это не даёт единого числа.
<?php
$f = fopen('/tmp/test.txt', 'r');
$meta = stream_get_meta_data($f);
// Выводим uri и тип
echo "URI: " . $meta['uri'] . "\n";
echo "Type: " . $meta['stream_type'] . "\n";
fclose($f);
?>
Проблема: не является идентификатором в строгом смысле; два потока могут иметь одинаковые метаданные (например, один и тот же файл, открытый дважды). Не подходит для точного сравнения.
Решение: только в ситуациях, когда числовой ID не требуется, а нужно узнать, откуда поток.
Цель отладка, логирование, диагностика.
Расширенные примеры использования идентификатора потока
Пример 1 несколько открытых потоков и их ID
Демонстрация того, как разные потоки получают уникальные идентификаторы.
<?php
$f1 = fopen('/tmp/file1.txt', 'w');
$f2 = fopen('/tmp/file2.txt', 'w');
$s1 = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
$ids = [
'file1' => stream_get_id($f1),
'file2' => stream_get_id($f2),
'server' => stream_get_id($s1),
];
print_r($ids);
fclose($f1); fclose($f2); fclose($s1);
?>
Array
(
[file1] => 4
[file2] => 5
[server] => 6
)
Пример 2 использование stream_select и идентификация потоков по ID
В цикле обработки событий можно отслеживать активные потоки по их идентификаторам.
<?php
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
$clients = [];
// добавляем сервер в массив для чтения
$read = [$server];
while (true) {
$write = $except = null;
if (stream_select($read, $write, $except, 0) > 0) {
foreach ($read as $stream) {
$id = stream_get_id($stream);
echo "Готов к чтению поток ID: $id\n";
// обработка...
}
}
// лимит на демонстрацию
break;
}
fclose($server);
?>
Готов к чтению поток ID: 5
Пример 3 сравнение потоков через ID
Проверка, ссылаются ли две переменные на один и тот же поток.
<?php
$f = fopen('/tmp/test.txt', 'r');
$g = $f; // присваиваем ссылку
$h = fopen('/tmp/test.txt', 'r'); // открываем отдельно
$idF = stream_get_id($f);
$idG = stream_get_id($g);
$idH = stream_get_id($h);
echo "f: $idF, g: $idG, h: $idH\n";
echo "f === g: " . ($idF === $idG ? 'true' : 'false') . "\n";
echo "f === h: " . ($idF === $idH ? 'true' : 'false') . "\n";
fclose($f); fclose($h);
?>
f: 4, g: 4, h: 5 f === g: true f === h: false
Пример 4 пользовательский stream wrapper и его ID
Создание простого обёртки потока и получение идентификатора объекта.
<?php
class MyStream {
private $data;
public function stream_open($path, $mode, $options, &$opened_path) {
$this->data = 'test';
return true;
}
public function stream_read($count) { return substr($this->data, 0, $count); }
public function stream_write($data) { $this->data .= $data; return strlen($data); }
public function stream_eof() { return empty($this->data); }
}
stream_wrapper_register('my', MyStream::class);
$s = fopen('my://test', 'r+');
echo "ID пользовательского потока: " . stream_get_id($s) . "\n";
fclose($s);
?>
ID пользовательского потока: 3
Пример 5 получение ID сокета сервера и его клиентов
Демонстрация идентификации серверного и клиентских сокетов в простом TCP сервере.
<?php
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
echo "Сервер ID: " . stream_get_id($server) . "\n";
// имитируем подключение клиента
$client = stream_socket_client(stream_socket_get_name($server, false));
$accepted = stream_socket_accept($server, 0);
echo "Принятый клиент ID: " . stream_get_id($accepted) . "\n";
echo "Клиент (исходный) ID: " . stream_get_id($client) . "\n";
fclose($client); fclose($accepted); fclose($server);
?>
Сервер ID: 4 Принятый клиент ID: 5 Клиент (исходный) ID: 6