与匿名pipe道build立连接后的步骤需要服务器调用DisposeLocalCopyOfClientHandle 。 MSDN解释说:
客户端句柄传递给客户端后,应该调用DisposeLocalCopyOfClientHandle方法。 如果未调用此方法,则当客户端处理其PipeStream对象时,AnonymousPipeServerStream对象将不会收到通知。
试图理解客户端closures时服务器为什么不被注意,我继续看参考源的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和传递给孩子的服务器的 m_clientHandle )与第一个地方不同吗? 这里的“不同”是指“引用不同的对象”(这是我理解它的方式)还是还有其他含义?
你的困惑源于服务器和客户端也是父进程和子进程的事实。 管道手柄是服务器或客户端,但可以在父母和子女中存在。 一会儿,在服务器产生了客户端之后,但在DisposeLocalCopyOfClientHandle之前, 三个句柄正在播放:
第二个句柄需要在孩子启动和运行之后关闭,因为正如注释所解释的那样,在所有客户句柄关闭之前,管道仍然可用。 如果第二个手柄粘住,则会阻止服务器检测到子进程已完成。
而不是使用继承,实现也可以产生子进程并使用DuplicateHandle ,由于原来的句柄可以被立即关闭,所以不需要这个帮助方法。 这可能是“在第一流程之间传递句柄”的意思。
在.NET中很难看清楚的细节是CreateProcess()的bInheritHandles参数,这是一个在winapi中爬行的令人讨厌的小unixism。 确定这个观点的正确价值是非常困难的,因为你必须了解很多关于你开始的过程,而且这个过程真的很差,这是一个全有或全无的选择。 Raymond Chen有一篇博客文章 ,谈论丑角的案例,以及他们在Windows 6.0中如何解决这个问题。
否则,可以在.NET中使用的解决方案。 主要是因为它仍然支持.NET 4.5以前的Windows版本。 这将是相当困难的使用。 因此,ProcessStartInfo类没有属性允许您显式控制bInheritHandles参数值,Process.Start()总是传递TRUE。 这是什么“直到我们在流程一级之间传递句柄”的意思。
进一步的细节是,子进程继承的句柄是一个独立的句柄,与父进程的句柄不同。 所以总共需要两个 CloseHandle调用来销毁系统对象。 或者换句话说,父母和孩子都需要停止使用该对象。 这就是“操作系统认为父母和孩子的手柄不同”的意思。
用于创建匿名管道的底层CreatePipe()winapi函数返回两个句柄,一个读取,一个写入。 根据管道的方向,父级(aka服务器)应该使用一个,子进程(又名客户机)使用一个。 这些句柄是可继承的句柄,因此在启动子进程后,需要总共四次 CloseHandle调用来销毁该管道对象。
这是不愉快的。 .NET包装可以做一些关于服务器句柄。 它调用DuplicateHandle()来创建服务器端句柄的副本,为bInheritHandle参数传递FALSE。 然后关闭原来的句柄。 好的,子进程将不再继承服务器端句柄,所以现在只需要三个 CloseHandle调用。
但是,相同的技巧不能用于子进程需要使用的管道句柄。 毕竟,它的目的是为了继承这个句柄,这样它就可以和服务器通话了。 这就是为什么你必须明确地做, 因为你知道子进程已经正确启动。 在您的DisposeLocalCopyOfClientHandle()方法调用之后,只需要两个 CloseHandle调用。
客户端的CloseHandle调用很简单,通过在AnonymousPipeClientStream上调用Close或Dispose来实现。 或者通过一个未处理的异常来崩溃进程,然后OS负责关闭句柄。 现在只剩下一个 CloseHandle调用了。
一个去,在服务器端更难。 它只知道关闭/处置它的AnonymousPipeserverStream,当它得到子进程不再使用它的“通知”。 可怕的引用“通知”,没有事件告诉你这一点。 正确的方法是让子进程发送一个明确的“再见”消息,以便服务器知道调用Close。 不正常但并不少见的方式是孩子没有很好的说再见,然后服务器只能知道它不在继续使用管道的异常。
哪一个是关键,只有当操作系统看到服务器试图使用管道,而另一端没有剩余的句柄时,才会得到异常。 换句话说,如果你忘记调用DisposeLocalCopyOfClientHandle(),那么你不会得到异常。 不好。