Именованные каналы на практике — от проектирования до безопасности IPC в Windows

· Обновлено: · · Windows, IPC, Разработка под Windows, C#, C++, Безопасность, Win32 API

История изменений (первая версия, опубликована 22 Aug 2026)
Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.22176781)

Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.

Го Комура (2026). Именованные каналы на практике — от проектирования до безопасности IPC в Windows. KomuraSoft LLC. https://comcomponent.com/ru/blog/windows-named-pipes-practical-guide/

DOI (зарегистрированный архив)
10.5281/zenodo.22176781
DOI (последняя зарегистрированная версия)
10.5281/zenodo.22176782

«Нужно обмениваться командами между постоянно работающей службой и приложением с экраном настроек.» «Нужно вынести в отдельный процесс только операции, которым нужны права администратора.» Когда на Windows возникает такое межпроцессное взаимодействие (IPC), первое, что стоит рассмотреть, — именованный канал (named pipe).

Почему это первый кандидат для связи на одном ПК — не только удобство чтения и записи. Большое преимущество в том, что через ACL Windows можно сузить, кто подключается, и при необходимости проверить учётную запись Windows партнёра и выполнить работу с его правами. Создать канал само по себе ещё не делает его безопасным: нужно настроить и подключение, и права.123

Статья организует проект в порядке форма подключения → границы сообщений → устройство сервера → безопасность → поведение при сбое. Она рассчитана на разработчиков, которые пишут бизнес-приложения и службы под Windows. Берёт «первый кандидат для IPC на одной машине» из статьи с таблицей выбора IPC и доводит его до решений на этапе реализации.

1. Сначала выводы: пять проектных решений

Прежде чем выбирать API, решите, с кем общаться, какова единица данных, как принимать одновременные подключения, какие права и как обрабатывать сбои.

Решение Базовое правило Где читать дальше
С кем общаться Для процессов Windows на одном ПК именованный канал — первый кандидат. Если важны удалённое развёртывание, другие ОС или повторное использование существующего протокола, рассмотрите вариант на TCP Глава 2
Что считать одной единицей Если одна запись трактуется как одна единица — режим сообщений. Если фрейминг уже есть — байтовый режим Глава 3
Как принимать нескольких клиентов Подготовьте несколько экземпляров с одним именем. Для новой реализации на .NET естественный выбор — асинхронный ввод-вывод и async/await Глава 4
Кому разрешать подключение и заимствование прав Проектируйте отказ удалённым, ACL, гарантию первого экземпляра и уровень олицетворения клиента как набор Главы 5 и 6
Что делать, когда обмен ломается Заложите в протокол ожидание запуска, переподключение, предел размера сообщения и подтверждение ответа Глава 7

У именованного канала контроль подключений через ACL и олицетворение клиента доступны как механизмы Windows. У TCP на localhost отдельно проектировать аутентификацию, чтобы установить, кто на том конце. С другой стороны, если хотите позже выйти на удалённый обмен, общаться с другими ОС или опереться на существующие активы вроде gRPC, выгоднее вариант на TCP.23

Самое важное — никогда не выполнять запрос, олицетворение которого не удалось. Если проигнорировать неудачу, обработка продолжится с правами самого сервера, а не клиента. Если строите привилегированную службу, читайте главы 5 и 6, а не только примеры кода.4

2. Как устроено подключение: имя общее, канал — на клиента

2.1 Разделяйте роли создания, подключения и чтения/записи

Именованный канал — односторонний или двунаправленный канал, который идентифицируется именем вроде \\.\pipe\MyCompany.MyApp.Control. Сервер и клиент начинают с разных API.1

Этап Сторона сервера Сторона клиента
Подготовить канал Создать экземпляр через CreateNamedPipe Использовать имя канала, которое подготовил сервер
Подключиться Ждать клиента через ConnectNamedPipe Открыть то же имя через CreateFile
Обменяться данными Читать и писать через ReadFile / WriteFile Читать и писать через ReadFile / WriteFile

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

2.2 Один экземпляр обслуживает одного клиента

Каналы с одним именем можно создать в нескольких экземплярах, и один экземпляр становится каналом к одному клиенту. Общее имя не значит, что все клиенты делят один канал. Первый CreateNamedPipe задаёт максимум экземпляров. Как верхний предел доступен и PIPE_UNLIMITED_INSTANCES.15

Базовая структура именованного каналаСервер создаёт несколько экземпляров канала с одним именем и ждёт подключений через ConnectNamedPipe; каждый клиент открывает имя через CreateFile и имеет двунаправленный канал один к одному с одним экземпляромСерверЭкземпляр 1Экземпляр 2Экземпляр 3Клиент AКлиент BКлиент C

Рис. 1: Пример двунаправленного канала. Подготовив несколько экземпляров с одним именем, сервер общается один к одному с каждым из нескольких клиентов.

2.3 «Направление» названо с точки зрения сервера

Права доступа клиента должны совпадать с направлением канала, который создал сервер. Если они расходятся, CreateFile завершается ошибкой.6

Направление, которое создаёт сервер Поведение сервера Доступ, который указывает клиент
Двунаправленный Читает и пишет Можно открыть на чтение, на запись или на оба
Исходящий Только пишет Открыть только на чтение
Входящий Только читает Открыть только на запись

Если все экземпляры заняты, CreateFile клиента возвращает ERROR_PIPE_BUSY. Дождитесь свободного экземпляра через WaitNamedPipe, затем снова вызовите CreateFile. Это другой сбой, чем «сервер ещё не создал канал», поэтому раздел 7.1 разбирает его вместе с гонками порядка запуска.6

2.4 Даже для локального использования решите, как обращаются с удалёнными подключениями

Именованный канал можно использовать и для удалённых подключений по SMB, в форме \\server\pipe\name. В современном проекте, однако, почти нет причин активно пользоваться этим путём, а для локального IPC важно не оставлять открытым путь, которым не пользуетесь. Явно откажите через PIPE_REJECT_REMOTE_CLIENTS в разделе 5.1.17

3. Выбор режима: решите, «сколько составляет одна единица»

3.1 Режим сообщений для запрос/ответ, байтовый — если границы уже есть

Разница режимов передачи в том, сохраняет ли канал границы записей.5

Режим Что видит приёмник Подходит для
Байтовый (PIPE_TYPE_BYTE) Непрерывный поток байтов, как TCP Данные со своим фреймингом вроде префикса длины или потоковая передача
Сообщений (PIPE_TYPE_MESSAGE с режимом чтения сообщений) Единицы, в которых одна запись — одно сообщение Запросы и ответы по одному
Разница между байтовым режимом и режимом сообщенийВ байтовом режиме три записи становятся непрерывным потоком байтов, который приёмник должен сам разрезать, а в режиме сообщений единица каждой записи сохраняется и доходит до приёмника как естьБайтовый режим: записать AAA, BB, CCCCПринято как поток байтов AAABBCCCCГраницы (фрейминг) проектируете самиРежим сообщений: те же три записиПринято как три единицы: AAA, BB, CCCCЕдиница каждой записи сохраняется

Рис. 2: В байтовом режиме границы ведёт приёмник. Режим сообщений может сохранить единицу каждой записи, но одно чтение не обязательно принимает всё сообщение.

Если своих границ ещё нет и хотите трактовать одну запись как один запрос, удобен режим сообщений. Если уже пользуетесь чем-то вроде сериализации с префиксом длины, байтовый режим подходит. В .NET PipeTransmissionMode.Message — это указание типа сообщения.8

Для двунаправленного канала типа сообщений есть ещё TransactNamedPipe, который отправляет запрос и принимает ответ одним вызовом.9

3.2 Тип сообщения и режим чтения — отдельные настройки

Режим чтения задаётся на дескриптор. Указание PIPE_READMODE_MESSAGE в CreateNamedPipe — настройка на стороне сервера. Дескриптор, который клиент открыл через CreateFile, по умолчанию в байтовом режиме чтения.6

Реализация Сторона сервера Сторона клиента
Win32 Указать PIPE_TYPE_MESSAGE и PIPE_READMODE_MESSAGE После подключения указать PIPE_READMODE_MESSAGE через SetNamedPipeHandleState
.NET Указать PipeTransmissionMode.Message После подключения поставить NamedPipeClientStream.ReadMode в Message

Суть — не считать, что достаточно сделать только сервер типом сообщений, чтобы клиент читал теми же единицами.

3.3 Даже в режиме сообщений нужно читать остаток

Сохранить границы сообщений и уместить всё сообщение в приёмный буфер — разные вещи. Если буфер мал, ReadFile возвращает ERROR_MORE_DATA, и сообщение режется. Нужен цикл, который держит уже принятую часть, читает остаток и склеивает.56

Результат чтения Обработка
Успех Обработать сообщение как полное
ERROR_MORE_DATA Есть ещё, поэтому прочитать остаток и склеить
Любая другая ошибка Не продолжать обработку запроса; трактовать как сбой обмена, например обрыв

Если маленькие запросы проходят, а ломаются только большие, проверяйте эту логику чтения остатка. Кроме того, безграничная склейка недопустима. Решите максимальный размер одного сообщения как часть протокола и примите политику отключаться, когда предел превышен. Это предотвращает расход памяти из-за огромных сообщений.

4. Устройство сервера: отделяйте приём от обработки клиента

4.1 Сравните синхронно-потоковую схему и overlapped

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

Схема Механизм Преимущества и оговорки
Синхронно-потоковая Назначить по потоку на экземпляр и обрабатывать синхронным вводом-выводом Код прямой. Однако потребляет по потоку на клиента, и на завершении нужен способ выйти из блокирующего ввода-вывода
Overlapped (асинхронная) Создавать с FILE_FLAG_OVERLAPPED и выдавать подключение, чтение и запись асинхронно Небольшое число потоков может обрабатывать завершения нескольких экземпляров

Официальный пример Microsoft кладёт событие каждого экземпляра в массив, ждёт через WaitForMultipleObjects и обрабатывает несколько подключений в одном потоке. Это отделяет число клиентов от числа потоков. На большем масштабе можно ещё выбрать привязку к IOCP или вводу-выводу пула потоков.10

Как устроен сам асинхронный ввод-вывод, см. статью о синхронном и асинхронном вводе-выводе.

4.2 Для новой реализации на .NET естественный выбор — async/await

В .NET можно использовать WaitForConnectionAsync / ReadAsync / WriteAsync у NamedPipeServerStream вместе с async/await. Если нет особых причин, для новых реализаций рекомендую эту асинхронную схему. Когда цикл приёма получает подключение, он отдаёт его обработке на клиента и возвращается принимать на следующем экземпляре.8

Ниже — скелет этого цикла приёма. Как пример для использования между процессами одного пользователя он указывает PipeOptions.Asynchronous и PipeOptions.CurrentUserOnly. В Windows CurrentUserOnly ограничивает подключения тем же пользователем и тем же уровнем повышения. Это не пример подключения службы и UI под разными учётными записями. Тому случаю нужен проект ACL из раздела 5.2.11

// C#: скелет сервера, который принимает нескольких клиентов
while (!token.IsCancellationRequested)
{
    var server = new NamedPipeServerStream(
        "MyCompany.MyApp.Control",
        PipeDirection.InOut,
        NamedPipeServerStream.MaxAllowedServerInstances,
        PipeTransmissionMode.Message,
        PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);

    try
    {
        await server.WaitForConnectionAsync(token);
    }
    catch
    {
        await server.DisposeAsync();        // если выходите до подключения, освобождайте сами
        throw;
    }
    _ = HandleClientAsync(server, token);   // после подключения владение переходит обработчику
}

Если исключение возникает до подключения, принимающая сторона освобождает поток; после подключения HandleClientAsync берёт владение потоком. Это владение включает не только чтение и запись, но и освобождение в конце.

Этот код — скелет, который опускает протокол обмена и тело обработчика. В продакшене управляйте исключениями и завершением отсоединённых задач и сочетайте его с чтением остатка и пределом размера из главы 3, безопасностью из глав 5 и 6 и обработкой обрыва и подтверждением ответа из главы 7. CurrentUserOnly — удобный вариант ограничить подключающихся без собственного ACL, но одного его недостаточно, чтобы закончить проект привилегированной службы.

5. Безопасность: три пункта на стороне сервера и один на стороне клиента

Преимущества безопасности именованных каналов получаются только когда они настроены правильно. Особенно в брокерской схеме «служба с правами администратора плюс UI-приложение с низкими правами» канал — граница привилегий.

5.1 Сторона сервера: откажите ненужным удалённым подключениям

Если канал используется как локальный IPC, укажите PIPE_REJECT_REMOTE_CLIENTS в CreateNamedPipe. Удалённые клиенты отказываются автоматически, и закрывается путь, по которому канал, задуманный для приложений на том же ПК, можно открыть ещё и из сети.7

5.2 Сторона сервера: сузьте подключающихся и права через ACL

Передайте дескриптор безопасности в SECURITY_ATTRIBUTES и явно укажите, какие пользователи и группы могут подключаться. ACL по умолчанию не обязательно достаточно строг для вашего сценария.2

Что легко упустить — право записи, которое вы даёте клиентам. Если ACL даёт общую запись (GENERIC_WRITE / FILE_GENERIC_WRITE), в него входит право, эквивалентное FILE_CREATE_PIPE_INSTANCE. Тогда уполномоченный клиент сам может создать серверный экземпляр с тем же именем и перехватить последующие подключения.2

Давайте чтение и запись как отдельные нужные права и не отдавайте клиентам право создавать экземпляры. Проект ACL покрывает не только «кому можно», но и «что можно».

5.3 Сторона сервера: проверьте, что первый экземпляр не заняли раньше вас

Если вредоносный процесс создаст канал с тем же именем первым, клиенты подключатся к этому поддельному серверу. Сервер указывает FILE_FLAG_FIRST_PIPE_INSTANCE только при создании первого экземпляра, гарантируя, что он первый. Если это не удаётся, подозревайте, что имя заняли раньше, и останавливайтесь.5

Этот флаг только для первого экземпляра, того, который занимает имя. Поставить его и на второй и последующие экземпляры — значит сделать создание неуспешным. В цикле приёма на нескольких клиентов не повторяйте ту же спецификацию на каждом создании.5

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

5.4 Сторона клиента: решите, насколько серверу можно заимствовать ваши права

Как защита от поддельного сервера клиент держит уровень олицетворения на минимуме, который нужен. Если проект разрешает только проверку личности, укажите SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION в CreateFile. Тогда сервер может проверить учётную запись, но уже не может заимствовать её права для реального доступа.3

Что вы хотите, чтобы сервер делал Политика на стороне клиента
Только проверить личность подключающегося Сузить до уровня идентификации (SECURITY_IDENTIFICATION)
Выполнять реальный доступ, например открывать файлы с правами клиента Должен быть разрешён уровень олицетворения (SECURITY_IMPERSONATION). Сочетайте с проверкой, что вы подключены к легитимному серверу

На уровне идентификации брокерская обработка, которая выполняет реальный доступ с правами клиента, невозможна. Наоборот, разрешить олицетворение без проверки цели подключения — риск отдать права поддельному серверу.

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

6. Использование олицетворения: никогда не выполняйте запрос, олицетворение которого не удалось

6.1 Олицетворяйте после чтения запроса, а не просто после подключения

API, которым сервер проверяет личность клиента и обрабатывает с его правами, — ImpersonateNamedPipeClient. Его предмет — контекст безопасности отправителя последнего сообщения, прочитанного из этого канала. Сначала прочитайте запрос, затем вызывайте.4

Олицетворение применяется к вызывающему потоку. Если в этом состоянии открыть файл, разрешён ли доступ судят по правам клиента. Даже для привилегированной службы это механизм «выполнить запрошенную операцию с правами запросившего».3

6.2 Сделайте чтение, подтверждение успеха и возврат одной единицей

Нужный порядок — прочитать запрос, попытаться олицетворить, подтвердить успех, оперировать с правами клиента и вернуться к исходному контексту.

Поток обработки запроса, который использует олицетворениеСервер читает запрос из канала, подтверждает, что ImpersonateNamedPipeClient успешен, затем выполняет операцию с правами клиента и возвращается к своему контексту через RevertToSelf. Если олицетворение не удалось, он отказывает в запросе, не выполняя егоСерверКлиентСерверКлиентПри неудаче отказать, не выполняя запросОтправить запросПрочитать запросImpersonateNamedPipeClientВыполнить операцию с правами клиентаВернуться к исходному контексту через RevertToSelfОтветить результатом

Рис. 3: Если олицетворение не удалось, запрос не выполняйте. Даже при успехе последовательность не закончена, пока RevertToSelf не восстановит исходный контекст после работы.

Никогда не переходите к обработке запроса, не проверив возвращаемое значение ImpersonateNamedPipeClient. При неудаче поток не переключился на права клиента, и используются собственные права серверного процесса. Если сервер работает под привилегированной учётной записью, он пропускает операции, которые клиенту выполнять нельзя. Документация Microsoft тоже прямо говорит, что при неудаче запрос клиента выполнять нельзя.4

Когда работа закончена, надёжно вернитесь к исходному контексту через RevertToSelf. Держите и проверку успеха, и очистку. Подробности вроде токенов, уровней олицетворения и SeImpersonatePrivilege разобраны в статье о токенах олицетворения Windows.

7. Практические ловушки: проектируйте из допущения, что обмен сломается

7.1 Ожидание запуска и ожидание свободного экземпляра — разные сбои

Когда подключиться нельзя, различайте, что сервер ещё не создал канал, и что все созданные экземпляры заняты.69

Состояние Действие клиента
Канала нет Заложите, что сервер ещё запускается; немного подождите и повторите
ERROR_PIPE_BUSY Дождитесь свободного экземпляра через WaitNamedPipe и повторите CreateFile
Подключено Переходите к обмену. При необходимости ещё задайте режим чтения из раздела 3.2
Поток повторных попыток подключения клиентаОткрыть канал через CreateFile; если канала нет, немного подождать и повторить; если все экземпляры заняты и возвращается ERROR_PIPE_BUSY, дождаться свободного экземпляра через WaitNamedPipe и затем повторить; при успехе начать обменУспехКанала нетERROR_PIPE_BUSYОткрыть через CreateFileРезультат?Начать обменНемного подождать (сервер не запущен)Дождаться свободного экземпляра через WaitNamedPipe

Рис. 4: «Не существует» и «занято» — разные состояния. В обоих случаях ждите так, как требует состояние, затем повторяйте подключение.

На стороне сервера принцип — начать ждать через ConnectNamedPipe до того, как клиент стартует. Гонки порядка запуска всё равно возможны, поэтому повторы закладывайте и на стороне клиента.9

7.2 Сделайте отключение обычным путём кода, а не аварийным

Когда процесс партнёра завершается, чтение и запись завершаются ошибкой вроде ERROR_BROKEN_PIPE. Трактуйте это как повседневный обмен.

Когда сервер обнаруживает обрыв, он отсоединяет экземпляр через DisconnectNamedPipe и готовится к следующему подключению. Клиент переподключается. Идея «идемпотентного переподключения», которое не портит состояние, сколько бы раз ни повторялось, разобранная в статье о пробуждении ото сна, здесь тоже работает.

7.3 Большим сообщениям нужны и «чтение остатка», и «предел»

Обработка ERROR_MORE_DATA из раздела 3.3 — логика чтения сообщения по частям. Максимальный размер сообщения, напротив, — правило ограничить объём данных, которые вы принимаете. Не путайте одно с другим.

Если вредоносный или ошибочный партнёр пришлёт огромное сообщение, реализация, которая читает остаток без предела, тратит память. Примите политику задать предел в протоколе и отключаться, когда он превышен.

7.4 Отличайте успешную запись от того, что партнёр закончил обработку

Успех WriteFile не значит, что приложение партнёра обработало данные. Для операций, о которых нужно знать, что они действительно выполнены, подтверждайте ответным сообщением.

Кроме того, чтобы каждый ответ можно было сопоставить с запросом, на который он отвечает, включите отношение между запросами и ответами в протокол. Отделить «отправлено» от «обработка закончена» — то, что ведёт к подтверждению завершения в деловом смысле.

8. Итог: канал, данные и права решайте отдельно

Именованный канал сочетает удобство файлового ввода-вывода с моделью безопасности Windows — ACL и олицетворением — и является первым кандидатом для IPC на одной машине. Создать канал, однако, — другое дело, чем создать безопасный протокол, который трудно сломать.

В проекте подключения и данных исходите из того, что один экземпляр обслуживает одного клиента. Берите режим сообщений для запрос/ответ и байтовый, если фрейминг уже есть, и задавайте режим чтения сообщений также на стороне клиента. Нужны и обработка частичного чтения, и максимальный размер. Для новой реализации на .NET, которая принимает несколько подключений, естественный выбор — асинхронный ввод-вывод и async/await.

В проекте прав отказ удалённым, явный ACL, гарантию первого экземпляра и минимизацию уровня олицетворения на стороне клиента рассматривайте как набор. Если пользуетесь олицетворением, вызывайте его после чтения запроса, при неудаче не выполняйте и возвращайтесь через RevertToSelf после работы.

Наконец, выпишите порядок запуска, отключение, предел размера сообщения и подтверждение ответа как свой протокол. Практический смысл — до начала реализации решить форму, которая умеет восстановиться, когда обмен ломается, не выходя за права партнёра, а не считать «это на том же ПК, значит не сломается».

Похожие статьи

Смежные области консультирования

KomuraSoft LLC занимается проектированием и реализацией систем с межпроцессным взаимодействием — отделение службы от UI-приложения и изоляция прав администратора; заменой существующего IPC (разделяемая память, самодельные сокеты, COM и так далее) именованными каналами; и ревью безопасности канального обмена привилегированной службы. Можно обратиться даже если хотите только проговорить проект протокола.

Справочные ссылки

  1. Microsoft Learn, Named Pipes. О том, что именованный канал — односторонний или двунаправленный канал между сервером канала и одним или несколькими клиентами канала; о том, что каждый экземпляр разделяет одно имя, но имеет независимые буферы и дескрипторы; и о том, что им можно пользоваться из локальных и удалённых процессов. ↩ ↩2 ↩3 ↩4

  2. Microsoft Learn, Named Pipe Security and Access Rights. О составе прав доступа именованного канала; о том, что GENERIC_WRITE включает FILE_CREATE_PIPE_INSTANCE, поэтому общая запись клиенту также разрешает создать серверный экземпляр; и о том, что чтение и запись данных даются как отдельные права доступа. ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, Impersonating a Named Pipe Client. О том, что олицетворение позволяет потоку сервера оперировать в правах клиента; о том, что уровень олицетворения по умолчанию — SecurityImpersonation; и о том, что клиент может управлять уровнем олицетворения флагом SECURITY_SQOS_PRESENT в момент CreateFile (SECURITY_IDENTIFICATION разрешает только идентификацию). ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). О том, что поток на стороне сервера начинает олицетворение в контексте безопасности клиента последнего сообщения, прочитанного из канала; о возврате через RevertToSelf после завершения; и о том, что продолжение после неудачи олицетворения приводит к выполнению в собственном (привилегированном) контексте серверного процесса, поэтому возвращаемое значение нужно всегда проверять и при неудаче запрос клиента выполнять нельзя. ↩ ↩2 ↩3

  5. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). О направлении канала (входящий, исходящий, двунаправленный), байтовом типе и типе сообщений (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) и режиме чтения (PIPE_READMODE_MESSAGE), максимуме экземпляров (PIPE_UNLIMITED_INSTANCES), асинхронном режиме через FILE_FLAG_OVERLAPPED, гарантии первого экземпляра через FILE_FLAG_FIRST_PIPE_INSTANCE и тайм-ауте по умолчанию, которым пользуется WaitNamedPipe. ↩ ↩2 ↩3 ↩4 ↩5

  6. Microsoft Learn, Named Pipe Client. О том, что клиент открывает канал через CreateFile; о ERROR_PIPE_BUSY, когда все экземпляры заняты, и ожидании свободного через WaitNamedPipe; и о том, что открытый дескриптор по умолчанию в байтовом чтении, блокирующий и не overlapped, а SetNamedPipeHandleState может сменить его на режим чтения сообщений. ↩ ↩2 ↩3 ↩4 ↩5

  7. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). О двух режимах удалённых клиентов: PIPE_ACCEPT_REMOTE_CLIENTS (принимать удалённые подключения и проверять их по дескриптору безопасности) и PIPE_REJECT_REMOTE_CLIENTS (автоматически отказывать в подключениях от удалённых клиентов). ↩ ↩2

  8. Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). О подключении и чтении/записи через NamedPipeServerStream / NamedPipeClientStream, передаче единицами сообщений через PipeTransmissionMode.Message и обработке нескольких клиентов асинхронными методами. ↩ ↩2

  9. Microsoft Learn, Named Pipe Operations. Об overlapped-операциях через ReadFileEx / WriteFileEx, чтении без потребления через PeekNamedPipe, TransactNamedPipe, который отправляет запрос и принимает ответ одним вызовом на двунаправленном канале типа сообщений, и о том, что блокирующее чтение до старта клиента может вызвать гонку. ↩ ↩2 ↩3

  10. Microsoft Learn, Named Pipe Server Using Overlapped I/O. Об официальном примере однопоточного сервера, который через overlapped-операции обслуживает одновременные подключения нескольких клиентов. О структуре, которая ждёт OVERLAPPED и событие каждого экземпляра через WaitForMultipleObjects и продвигает автомат состояний завершившегося экземпляра, и о подтверждении завершения ожидающего ввода-вывода через GetOverlappedResult. ↩

  11. Microsoft Learn, PipeOptions Enum (System.IO.Pipes). О включении асинхронного ввода-вывода через Asynchronous и о том, что CurrentUserOnly может разрешать подключения только процессам того же пользователя (и того же уровня повышения). ↩

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Как выбирать между именованным каналом и TCP (сокет на localhost)?
Если речь о межпроцессном взаимодействии на одной машине, именованный канал — первый кандидат. Причина — модель безопасности. Через дескриптор безопасности Windows (ACL) ОС сама контролирует, кто может подключиться. Сервер может проверить учётную запись Windows клиента и олицетворить её через ImpersonateNamedPipeClient. С портом TCP на localhost всё иначе: подключиться может кто угодно, и кто именно на том конце, приходится устанавливать своей аутентификацией. Вариант на TCP выгоднее, если высока вероятность, что связь станет удалённой, если нужно общаться и с процессами на других ОС, или если хочется опереться на уже существующий протокол вроде gRPC. То же решение разобрано в статье с таблицей выбора IPC.
Что выбрать: байтовый режим или режим сообщений?
Если нужно трактовать «одна запись = одна смысловая единица», удобен режим сообщений (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE). Приёмник читает теми же порциями, которыми писал отправитель, и границы сообщений не нужно вести самому. Байтовый режим — непрерывный поток байтов, как TCP: фрейминг (например, префикс длины) проектируете сами. Если по каналу уже идёт протокол со своим фреймингом (например, сериализация с префиксом длины), байтовый режим подходит. Оговорка: даже в режиме сообщений, если приёмный буфер меньше сообщения, чтение идёт по частям (ERROR_MORE_DATA) — это нужно обрабатывать. Режим чтения задаётся на дескриптор, и CreateNamedPipe настраивает его только на сервере. Клиент после CreateFile должен указать PIPE_READMODE_MESSAGE через SetNamedPipeHandleState. В .NET сервер задаёт PipeTransmissionMode.Message, а клиент после подключения ставит NamedPipeClientStream.ReadMode в Message.
Как сделать сервер, который одновременно обслуживает несколько клиентов?
У именованного канала можно создать несколько экземпляров с одним именем; один экземпляр обслуживает одного клиента. Есть два варианта. Первый — синхронный: поток на каждого клиента. Реализация простая, но потоков столько, сколько клиентов. Второй — асинхронный ввод-вывод с FILE_FLAG_OVERLAPPED: небольшое число потоков обрабатывает ConnectNamedPipe, ReadFile и WriteFile по всем экземплярам. В официальном примере Microsoft несколько экземпляров обрабатываются в одном потоке. В .NET асинхронный вариант пишется почти так же просто, как синхронный: NamedPipeServerStream.WaitForConnectionAsync и async/await. Если нет особых причин, для новой реализации лучше взять асинхронный вариант на .NET.
Что минимум нужно сделать для безопасности именованных каналов?
Четыре пункта. Первый: если удалённые подключения не нужны, укажите PIPE_REJECT_REMOTE_CLIENTS и явно запретите подключения по сети. Второй: задайте подходящий ACL через SECURITY_ATTRIBUTES и ограничьте пользователей и группы, которым можно подключаться (ACL по умолчанию для некоторых сценариев слишком широкий). Третий: при создании первого экземпляра укажите FILE_FLAG_FIRST_PIPE_INSTANCE, чтобы обнаружить перехват имени — ситуацию, когда канал с тем же именем создали раньше вас (на второй и последующие экземпляры этот флаг не ставьте). Четвёртый: клиенту, которому нужно только чтобы сервер его опознал, следует указать SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION в CreateFile, чтобы поддельный сервер не мог олицетворить его права. В брокерской схеме, где сервер реально обращается к ресурсам с правами клиента, олицетворение должно быть разрешено, поэтому это ограничение включают или нет в зависимости от того, должен ли проект позволять серверу действовать от имени клиента.
На что обратить внимание при вызове ImpersonateNamedPipeClient?
Главное — проверять возвращаемое значение. Если продолжить после неудачного олицетворения, дальнейшие операции идут уже с правами самого серверного процесса (часто более высокими), и проходят действия, которых у клиента быть не должно. Официальная документация прямо говорит: при неудаче запрос клиента выполнять нельзя. Вызывать функцию нужно только после того, как что-то прочитали: олицетворение идёт в контексте «последнего сообщения, прочитанного из канала». Когда работа закончена, обязательно вернитесь к своему контексту через RevertToSelf. Устройство олицетворения (токены, уровни олицетворения, SeImpersonatePrivilege) подробно разобрано в статье о токенах олицетворения.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог