建立与匿名管道的连接后的步骤需要服务器调用DisposeLocalCopyOfClientHandle.MSDN解释说:
客户端句柄传递给客户端后,应调用DisposeLocalCopyOfClientHandle方法.如果未调用此方法,则当客户端处理其PipeStream对象时,AnonymousPipeServerStream对象将不会收到通知.
试图理解为什么在客户端关闭时不会注意到服务器,我继续查看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();
}
}
Run Code Online (Sandbox Code Playgroud)
这句话困惑了我:
once the child handle is inherited, the OS considers the parent and child's handles to be different.
父母的句柄和孩子的句柄(即服务器m_handle和服务器的 m_clientHandle传递给孩子)是不是与第一个不同?这里的"不同"是指"引用不同的对象"(这是我理解它的方式),还是有其他意义?
在.NET中很难看到的模糊细节是CreateProcess()的bInheritHandles参数,这是一个令人讨厌的小unixism,在winapi中悄然兴起.确定该论证的正确值是非常难以理解的,你必须对你开始的过程有很多了解并且它的扩展性很差,它是一个全有或全无的选择.Raymond Chen有一篇博文,讲述了丑陋的角落案例以及他们在Windows 6.0版中为解决问题所做的工作.
不是可以在.NET中使用的解决方案.主要是因为它仍然支持.NET 4.5以前的旧版本.这将非常难以使用.因此,ProcessStartInfo类没有允许您显式控制bInheritHandles参数值的属性,Process.Start()始终传递TRUE.这是什么"直到我们在进程第一类之间传递句柄"注释所指的.
进一步的细节是子进程继承的句柄是一个独立的句柄,它与父进程的句柄不同.因此,需要总共两个 CloseHandle调用来销毁系统对象.换句话说,父母和孩子都需要停止使用该对象.这是"操作系统认为父母和孩子的处理方式不同"的评论所指的.
用于创建匿名管道的底层CreatePipe()winapi函数返回两个句柄,一个用于读取,一个用于写入.根据管道方向,父级(也称为服务器)应使用一个,子进程(也称为客户端)应使用一个.这些句柄是可继承的句柄,因此在启动子进程后,总共需要四次 CloseHandle调用来销毁管道对象.
那是不愉快的..NET包装器可以对服务器句柄做一些事情.它调用DuplicateHandle()来生成服务器端句柄的副本,为bInheritHandle参数传递FALSE.然后关闭原始手柄.好的,子进程将不再继承服务器端句柄,因此现在只需要三个 CloseHandle调用.
但是,相同的技巧不适用于子进程需要使用的管道句柄.毕竟,目的是继承句柄,以便它可以与服务器对话.在您知道子进程正确启动后,您必须明确地执行此操作.在DisposeLocalCopyOfClientHandle()方法调用之后,现在只需要两个 CloseHandle调用.
客户端上的CloseHandle调用很容易,它通过在AnonymousPipeClientStream上调用Close或Dispose来实现.或者通过一个崩溃过程的未处理异常来操作,然后操作系统负责关闭句柄.现在只剩下一个 CloseHandle调用.
一个去,它在服务器端更难.它只知道在收到子进程不再使用它的"通知"时关闭/释放它的AnonymousPipeServerStream.围绕"通知"的可怕报价,没有事件告诉你这一点.正确的方法是让子进程发送明确的"再见"消息,以便服务器知道调用Close.不太恰当但并非罕见的方式是孩子没有很好地说再见,那么服务器只能知道它不再是它继续使用管道时获得的异常.
哪个是关键,当操作系统发现服务器尝试使用管道并且在另一侧没有打开剩余句柄时,您只能获得异常.或者换句话说,如果您忘记调用DisposeLocalCopyOfClientHandle(),那么您不会得到异常.不好.
您的困惑源于服务器和客户端也是父进程和子进程这一事实。管道句柄是服务器或客户端,但可以存在于父级和子级中。在服务器生成客户端之后但在DisposeLocalCopyOfClientHandle调用之前的短暂时间内,三个句柄正在发挥作用:
第二个句柄需要在子进程启动并运行后关闭,因为正如注释所解释的,管道仍然可用,直到所有客户端句柄都已关闭。如果第二个句柄仍然存在,它将阻止服务器检测到子进程已完成。
该实现还可以生成子进程并使用,而不是使用继承DuplicateHandle,这将消除对此辅助方法的需要,因为原始句柄可以立即关闭。这大概就是“在进程之间传递句柄的第一类”的含义。
| 归档时间: |
|
| 查看次数: |
649 次 |
| 最近记录: |