#c# #.net #named-pipes
#c# #.net #именованные каналы
Вопрос:
У нас есть несколько клиентов, подключающихся к серверу по именованным каналам. Иногда мы видим pipe/file not found
ошибку, когда клиент пытается подключиться к именованному каналу. Наш исходный код
while (!cancelToken.IsCancellationRequested)
{
// This creates a new NamedPipeServerStream
var stream = PipeHelper.NewCurrentUserServerStream(PipeNames.Server, PipeTransmissionMode.Byte);
await stream.WaitForConnectionAsync(cancelToken);
Task.Run(() => HandleStreamAsync(stream).WithExceptionTrapper(LogNamedPipeException));
Из того, что я могу сказать, NamedPipeServerStream
должен быть создан, а затем WaitForConnection
должен быть вызван, затем сервер должен обработать запрос.
Однако, если этот запрос выполняется почти мгновенно или клиент отключает канал, представляется возможным, что серверный поток будет закрыт и весь именованный канал исчезнет, прежде чем цикл кода завершится и будет создан новый серверный поток.
Итак, как мы можем гарантировать, что именованный канал существует в течение всего срока службы сервера, независимо от времени и количества подключающихся клиентов?
Код здесь https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication предполагает, что простое использование нескольких потоков является вариантом (в этом примере 4). Однако это, по-видимому, только снижает вероятность ошибки, и возможно, что клиент попытается подключиться в редком промежутке, когда все 4 потока только что закрыли серверный поток, и именованный канал исчезает. Кроме того, это ограничивает параллельное выполнение 4 потоками.
Существует ли механизм, позволяющий нам открывать именованный канал-сервер, который допускает произвольное количество подключений, создает новый серверный поток для каждого соединения и никогда не рискует закрыть именованный канал?
Прямо сейчас мы думаем о том, чтобы сделать это:
var nextStream = PipeHelper.NewCurrentUserServerStream(PipeNames.Server, PipeTransmissionMode.Byte);
while (!cancelToken.IsCancellationRequested)
{
try
{
await nextStream.WaitForConnectionAsync(cancelToken);
var currentStream = nextStream;
// Always have another stream ready so if the task is handled and finished
// really quickly theres no chance of the named pipe being removed altogether.
nextStream = PipeHelper.NewCurrentUserServerStream(PipeNames.Server, PipeTransmissionMode.Byte);
Task.Run(() => HandleStreamAsync(currentStream).WithExceptionTrapper(LogNamedPipeException));
}
Это в основном гарантирует, что мы создадим 2-й серверный поток до того, как начнем обрабатывать подключение к 1-му серверному потоку, поэтому мы гарантируем, что всегда есть один активный, и мы запускаем обработчик для отдельной задачи, поэтому мы должны запустить другое WaitForConnection как можно скорее.
Существует ли шаблон фреймворка, который позволяет нам выполнять вышеуказанное, поддерживая любое количество параллельных клиентов и гарантируя, что именованный канал никогда не будет удален при условии, что сервер все еще активен?
Ответ №1:
Итак, как мы можем гарантировать, что именованный канал всегда существует независимо от времени и количества подключающихся клиентов?
Это невозможно для любого сетевого сервера. Даже TCP / IP (на котором построены именованные каналы) имеет ограниченное «отставание». Даже если вы измените количество невыполненных работ до смешного, вы все равно будете ограничены серверным оборудованием.
Что вы можете сделать, так это всегда сохранять определенное количество прослушивающих вызовов (т. Е. WaitForConnectionAsync
), чтобы у вас всегда было достаточное количество слушателей, постоянно подключающихся, начиная новое прослушивание, как только каждый из них завершается, точно так же, как в вашем последнем примере кода. Вы захотите запустить этот код несколько раз, например, 20 или около того, чтобы у вас было много слушателей. Все, что вы можете сделать, это свести к минимуму вероятность пропуска соединения; вы не можете предотвратить это полностью.
Комментарии:
1. Имеющийся у нас код предполагает, что нам даже не нужен прослушивающий вызов, чтобы предотвратить сбой клиента при «канал / файл не найден», нам просто нужен созданный поток, который гарантирует наличие канала, независимо от того, есть ли активные слушатели. Таким образом, мы можем создавать и удерживать (и никогда не использовать) серверный поток до тех пор, пока процесс активен. Однако мне интересно, не ошибочна ли используемая нами клиентская модель. Правильно ли предполагать, что канал всегда создает сервер, а клиент полагается на существующий канал? Или клиент обычно способен создать канал, если он попадает туда первым?
2. @MichaelParker: Прошло почти два десятилетия с тех пор, как я имел дело с именованными каналами, но я считаю, что сначала должна быть создана серверная часть. Существуют шаблоны, в которых сервер и клиент могут быть созданы одновременно, но не в разных процессах IIRC.
3. Хорошо, в некотором смысле это хорошо (мы ничего не упустили). Я обнаружил аналогичную проблему в коде Microsoft Go: github.com/Microsoft/go-winio/pull/80/commits/… где они пытаются справиться с одним и тем же состоянием гонки. Их решение заключается в запуске серверного потока и немедленном запуске клиентского потока (на стороне сервера), чтобы поддерживать канал открытым, но не допускать подключения клиентов к первому серверному потоку. Затем откройте новые потоки сервера для работы с реальными клиентами. Это кажется сумасшедшим, но я могу перенести это на C #, если нет лучшего решения?