Именованный канал Windows: обнаружение в Python на стороне записи, когда программа чтения закрыла свой конец без необходимости записи данных

Мое приложение передает (записывает) данные в именованный канал, причем имя канала указывается в качестве аргумента CLI при вызове приложения. Потоковые данные нерегулярны с фазами, когда может не быть никаких данных для отправки по каналу.

Я хотел бы определить, когда другой процесс, читающий из канала, закрыл свой конец, чтобы быстро освободить ресурсы, выделенные моим приложением для потоковой передачи. Теперь моя проблема заключается в том, чтобы даже обнаружить, что конец канала для чтения был закрыт, ничего не записывая в канал.

Поскольку формат потоковых данных является фиксированным и не допускает пустых операций записи или проверки связи, я не могу просто попытаться записать какие-либо данные конвейера, даже если мне нечего передавать, чтобы увидеть, читает ли считыватель каналов.

У меня есть рабочее решение для Linux, которое, к сожалению, не работает в Windows, потому что именованные каналы Windows не могут быть единообразно обработаны в select(), как в Linux. В Linux я просто проверяю, стал ли конец канала записи доступным для чтения, поскольку это сигнализирует об ошибке канала, а затем закрываю канал и освобождаю выделенные ресурсы.

В Windows это невозможно. Я открыл канал для записи как таковой:

fifo = open('//./pipe/somepipe', 'wb')

Попытка fifo.read() из трубы не работает (как и следовало ожидать) и сразу выдает OSException.

Как я уже сказал, я не могу попробовать записать пустую/нулевую запись; fifo.write(b'') ничего не делает, даже не тыкает в трубу для записи вообще.

Есть ли способ в Windows проверить конец записи именованного канала, чтобы увидеть, подключен ли еще читатель (клиент)?

Я предполагаю, что вы открываете "//./pipe/<pipename>" в Windows. В любом случае, ваша идея сработает, если вы вызовете WINAPI WriteFile напрямую. Это не удастся с ERROR_NO_DATA (232), если труба закрывается.

Eryk Sun 10.04.2019 12:27

В качестве альтернативы, если вы знакомы с NT API, вы можете запросить это напрямую без пустой записи. Позвоните NtQueryInformationFile, чтобы получить FilePipeLocalInformation. Python open (в частности, WINAPI CreateFile) всегда запрашивает доступ к атрибутам чтения, поэтому нам разрешено читать эту информацию на стороне клиента, даже если это входящий канал (т. е. клиент имеет доступ только для записи данных). NamedPipeState будет FILE_PIPE_CLOSING_STATE (4).

Eryk Sun 10.04.2019 12:33

@eryksun: звучит очень интересно, попробую; на данный момент я нашел обходной путь в том, что протокол внешнего приложения имеет точку расширения, которую я могу повторно использовать для проверки состояния канала, написав специальную запись, которую принимающее приложение молча игнорирует.

TheDiveO 10.04.2019 14:32
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
3
896
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как указал @eryksun выше, на самом деле можно написать тестовую байтовую строку нулевой длины, используя Win32 API WriteFile напрямую: если канал закрыт, то это вернет «нет успеха» (false/0), если канал жив, то "успех" (true/!=0). Именно то, что я просил. Поскольку я считаю, что это проще, чем использовать NtQueryInformationFile, теперь я использую метод пустой записи; вот упрощенный пример:

import ctypes
from ctypes import byref, c_ulong, c_char_p
import msvcrt
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

fifo = open('//./pipe/somepipe', 'wb')
data = b''
written = c_ulong(0)
if not kernel32.WriteFile(
        msvcrt.get_osfhandle(fifo.fileno()),
        c_char_p(data), 0, 
        byref(written), 
        None):
    last_error = ctypes.get_last_error()
    if last_error in (
            0x000000E8,  # ERROR_NO_DATA
            # enable as required: 0x000000E9,  # ERROR_PIPE_NOT_CONNECTED
    ):
        # pipe broken
        pass
    else:
        # something else is wrong...
        pass
else:
    # pipe still okay
    pass

Полезные ресурсы:

Я предлагаю изменить это, чтобы использовать kernel32 = ctypes.WinDLL('kernel32', use_last_error=True). Если kernel32.WriteFile не получится, получите last_error = ctypes.get_last_error(). Если last_error != ERROR_NO_DATA, вызовите исключение с помощью повышения ctypes.WinError(last_error). В противном случае вы маскируете неожиданные ошибки.

Eryk Sun 15.04.2019 12:41

Кроме того, использование ctypes.WinDLL изолирует ваш пакет от других библиотек Python. ctypes.windll была плохой идеей с самого начала. Он кэширует WinDLL экземпляры, которые кэшируют указатели на функции. Таким образом, пакет, использующий windll, может установить пользовательские argtypes и restype для часто используемых функций API, что может нарушить одновременное использование других пакетов, которым нужны те же функции. Ранее это происходило с colorama и pyreadline, которые используют API консоли Windows.

Eryk Sun 15.04.2019 12:47

О, большое спасибо! Обновлен мой пример кода; Я узнал из своего рабочего кода, что в моем случае я не увижу канал закрытие, а только уже закрыто, поэтому проверяю 0xe8 и 0xe9 (ERROR_NO_DATA, а также ERROR_PIPE_NOT_CONNECTED). Так как канал был открыт синхронно и блокирующе, мы знаем, что ERROR_PIPE_NOT_CONNECTED означает, что канал закрылся.

TheDiveO 15.04.2019 15:33
ERROR_PIPE_NOT_CONNECTED означает, что сервер отключил экземпляр канала через DisconnectNamedPipe. В запросе будет сказано, что состояние канала равно FILE_PIPE_DISCONNECTED_STATE. Сервер может закрыть или не закрыть свой конец экземпляра. Скорее всего, он просто отключился и будет повторно использовать экземпляр для следующего подключения.
Eryk Sun 15.04.2019 15:58

Хорошо знать; в моем случае сервер не будет повторно подключаться (это определено в общем протоколе между сервером и клиентом).

TheDiveO 15.04.2019 16:55

Другие вопросы по теме