2016-09-25 1 views
5

Шаг, следующий за установлением соединения с анонимным каналом, требует вызова сервера DisposeLocalCopyOfClientHandle. MSDN объясняет:Необходимость вызова DisposeLocalCopyOfClientHandle() после установления соединения

Метод DisposeLocalCopyOfClientHandle должен быть вызван после того, как клиент ручка была передана клиенту. Если этот метод не вызван , объект AnonymousPipeServerStream не получит уведомление , когда клиент располагает своим объектом PipeStream.

Пытаясь понять, почему не сервер будет заметить, когда клиент закрыт, я пошел на смотреть на DisposeLocalCopyOfClientHandle на справочный источник:

// This method is an annoying one but it has to exist at least until we make passing handles between 
// processes first class. We need this because once the child handle is inherited, the OS considers 
// the parent and child's handles to be different. Therefore, if a child closes its handle, our 
// Read/Write methods won't throw because the OS will think that there is still a child handle around 
// that can still Write/Read to/from the other end of the pipe. 
// 
// Ideally, we would want the Process class to close this handle after it has been inherited. See 
// the pipe spec future features section for more information. 
// 
// Right now, this is the best signal to set the anonymous pipe as connected; if this is called, we 
// know the client has been passed the handle and so the connection is live. 
[System.Security.SecurityCritical] 
public void DisposeLocalCopyOfClientHandle() { 
    if (m_clientHandle != null && !m_clientHandle.IsClosed) { 
     m_clientHandle.Dispose(); 
    } 
} 

Это предложение меня смутило:

once the child handle is inherited, the OS considers the parent and child's handles to be different.

Не являются ручкой родителя и ручкой ребенка (то есть, m_handle и t он серверm_clientHandle, который передается ребенку) отличается от первого места? «разные» здесь означают «ссылки на разные объекты» (так я понимаю) или имеет другое значение?

ответ

3

Ваше замешательство проистекает из того факта, что сервер и клиент также являются родительскими и дочерними процессами. Ручки труб - сервер или клиент, но могут присутствовать в родительском и дочернем. За короткое время, после того, как сервер породил клиент, но до DisposeLocalCopyOfClientHandle называется, три ручки в игре:

  • Сервера ручка к трубе, в процессе сервера (родитель).
  • Клиентский дескриптор трубы, в серверном (родительском) процессе.
  • Клиентский дескриптор трубы в процессе клиента (дочерний), унаследованный от родительского процесса.

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

Вместо реализации наследования реализация может также порождать дочерний процесс и использовать DuplicateHandle, что избавит от необходимости использовать этот вспомогательный метод, так как первоначальный дескриптор может быть немедленно закрыт. По-видимому, это означает, что «mak [ing] передаёт ручки между процессами первого класса».

8

Неясная деталь, которую трудно увидеть в .NET, - это аргумент от CreateProcess(), неприятный маленький креатив, который прокрался в winapi. Определение правильного значения этого аргумента - very сложно понять, вам нужно много узнать о процессе, который вы начинаете, и он масштабируется очень плохо, это вариант «все или ничего». У Раймонда Чена есть blog post, в котором говорится об уродливых угловых случаях и о том, что они сделали в Windows версии 6.0, чтобы решить эту проблему.

В противном случае это решение, которое может быть использовано в .NET.В первую очередь потому, что он по-прежнему поддерживает старые версии Windows вплоть до .NET 4.5. И было бы довольно сложно использовать. Соответственно, класс ProcessStartInfo не имеет свойства, позволяющего явно контролировать значение аргумента bInheritHandles, Process.Start() всегда передает TRUE. Это то, о чем говорится в комментарии «до тех пор, пока мы не сделаем прохождение между обработчиками первого класса».

Еще одна деталь заключается в том, что дескриптор, который дочерний процесс наследует, представляет собой отдельный дескриптор, отличный от дескриптора родительского процесса. Так что в общей сложности два Для вызова системного объекта требуются вызовы CloseHandle. Или, говоря иначе, и родитель, и ребенок должны прекратить использование объекта. Это означает, что «ОС считает, что родительский и дочерний дескрипторы отличаются друг от друга».

Основная функция CreatePipe() winapi, которая используется для создания анонимного канала, возвращает две ручки, одну для чтения и одну для записи. В зависимости от направления трубы следует использовать родительский (aka server) и один дочерний процесс (aka client). Эти дескрипторы являются наследуемыми ручками, поэтому после запуска дочернего процесса требуется четыре. Для уничтожения объекта трубы требуются вызовы CloseHandle.

Это неприятно. Оболочка .NET может что-то сделать с дескриптором сервера. Он вызывает DuplicateHandle(), чтобы сделать копию дескриптора на стороне сервера, передав FALSE для аргумента bInheritHandle. Затем закрывает оригинальную ручку. Хорошо, дочерний процесс больше не будет наследовать серверный дескриптор, так что теперь только три Заклинания CloseHandle не требуются.

Тот же трюк, однако, не может работать на ручку трубы, которую должен использовать дочерний процесс. В конце концов, намерение состоит в том, чтобы он наследовал дескриптор, чтобы он мог вернуться к серверу. Вот почему вы должны это делать явным образом, после вы знаете, что дочерний процесс был запущен должным образом. После вызова метода DisposeLocalCopyOfClientHandle() теперь только требуется два вызова CloseHandle.

Вызов CloseHandle на стороне клиента достаточно прост, он делает это, вызывая Close или Dispose на AnonymousPipeClientStream. Или, перевернувшись с помощью необработанного исключения, которое приводит к сбою процесса, ОС затем заботится о закрытии дескриптора. Теперь только один Закрытие вызова CloseHandle.

Чтобы идти, это сложнее на стороне сервера. Он знает только, чтобы закрыть/удалить свой анонимный протокол, когда получает уведомление, что дочерний процесс больше не использует его. Страшные цитаты вокруг «уведомления», нет события, которое сообщает об этом. Правильный способ заключается в том, чтобы дочерний процесс отправил явное сообщение «до свидания», чтобы сервер знал, что нужно вызвать Close. Не так правильно, но не редкость заключается в том, что ребенок не попрощался, а сервер знает только, что он больше не существует из того исключения, которое он получает, когда он продолжает использовать этот канал.

Что является ключом, вы получаете только исключение, когда ОС видит, что сервер пытается использовать этот канал, и на другой стороне нет остальных ручек. Или, другими словами, если вы забыли вызвать DisposeLocalCopyOfClientHandle(), то вы получите не. Нехорошо.