Мое приложение передает (записывает) данные в именованный канал, причем имя канала указывается в качестве аргумента CLI при вызове приложения. Потоковые данные нерегулярны с фазами, когда может не быть никаких данных для отправки по каналу.
Я хотел бы определить, когда другой процесс, читающий из канала, закрыл свой конец, чтобы быстро освободить ресурсы, выделенные моим приложением для потоковой передачи. Теперь моя проблема заключается в том, чтобы даже обнаружить, что конец канала для чтения был закрыт, ничего не записывая в канал.
Поскольку формат потоковых данных является фиксированным и не допускает пустых операций записи или проверки связи, я не могу просто попытаться записать какие-либо данные конвейера, даже если мне нечего передавать, чтобы увидеть, читает ли считыватель каналов.
У меня есть рабочее решение для Linux, которое, к сожалению, не работает в Windows, потому что именованные каналы Windows не могут быть единообразно обработаны в select(), как в Linux. В Linux я просто проверяю, стал ли конец канала записи доступным для чтения, поскольку это сигнализирует об ошибке канала, а затем закрываю канал и освобождаю выделенные ресурсы.
В Windows это невозможно. Я открыл канал для записи как таковой:
fifo = open('//./pipe/somepipe', 'wb')
Попытка fifo.read()
из трубы не работает (как и следовало ожидать) и сразу выдает OSException
.
Как я уже сказал, я не могу попробовать записать пустую/нулевую запись; fifo.write(b'')
ничего не делает, даже не тыкает в трубу для записи вообще.
Есть ли способ в Windows проверить конец записи именованного канала, чтобы увидеть, подключен ли еще читатель (клиент)?
В качестве альтернативы, если вы знакомы с NT API, вы можете запросить это напрямую без пустой записи. Позвоните NtQueryInformationFile
, чтобы получить FilePipeLocalInformation
. Python open
(в частности, WINAPI CreateFile
) всегда запрашивает доступ к атрибутам чтения, поэтому нам разрешено читать эту информацию на стороне клиента, даже если это входящий канал (т. е. клиент имеет доступ только для записи данных). NamedPipeState
будет FILE_PIPE_CLOSING_STATE
(4).
@eryksun: звучит очень интересно, попробую; на данный момент я нашел обходной путь в том, что протокол внешнего приложения имеет точку расширения, которую я могу повторно использовать для проверки состояния канала, написав специальную запись, которую принимающее приложение молча игнорирует.
Как указал @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)
. В противном случае вы маскируете неожиданные ошибки.
Кроме того, использование ctypes.WinDLL
изолирует ваш пакет от других библиотек Python. ctypes.windll
была плохой идеей с самого начала. Он кэширует WinDLL
экземпляры, которые кэшируют указатели на функции. Таким образом, пакет, использующий windll
, может установить пользовательские argtypes
и restype
для часто используемых функций API, что может нарушить одновременное использование других пакетов, которым нужны те же функции. Ранее это происходило с colorama и pyreadline, которые используют API консоли Windows.
О, большое спасибо! Обновлен мой пример кода; Я узнал из своего рабочего кода, что в моем случае я не увижу канал закрытие, а только уже закрыто, поэтому проверяю 0xe8 и 0xe9 (ERROR_NO_DATA
, а также ERROR_PIPE_NOT_CONNECTED
). Так как канал был открыт синхронно и блокирующе, мы знаем, что ERROR_PIPE_NOT_CONNECTED
означает, что канал закрылся.
ERROR_PIPE_NOT_CONNECTED
означает, что сервер отключил экземпляр канала через DisconnectNamedPipe
. В запросе будет сказано, что состояние канала равно FILE_PIPE_DISCONNECTED_STATE
. Сервер может закрыть или не закрыть свой конец экземпляра. Скорее всего, он просто отключился и будет повторно использовать экземпляр для следующего подключения.
Хорошо знать; в моем случае сервер не будет повторно подключаться (это определено в общем протоколе между сервером и клиентом).
Я предполагаю, что вы открываете "//./pipe/<pipename>" в Windows. В любом случае, ваша идея сработает, если вы вызовете WINAPI
WriteFile
напрямую. Это не удастся сERROR_NO_DATA
(232), если труба закрывается.