需要在建立连接后调用DisposeLocalCopyOfClientHandle()

本文关键字:调用 DisposeLocalCopyOfClientHandle 连接 建立 | 更新日期: 2023-09-27 18:08:44

与匿名管道建立连接之后的步骤需要服务器调用DisposeLocalCopyOfClientHandle。MSDN解释说:

方法应该在客户端句柄已传递给客户端。如果这个方法不是调用时,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();
    }
}

这句话把我弄糊涂了:

一旦继承了子句柄,操作系统就会认为父句柄和子句柄是不同的。

不是父句柄和子句柄(即,服务器的m_handle服务器的 m_clientHandle,这是传递给子)在第一个地方不同?做"different"这里的意思是"引用不同的对象"。(这是我的理解),还是有其他含义?

需要在建立连接后调用DisposeLocalCopyOfClientHandle()

在。net中很难看到的模糊细节是CreateProcess()的bInheritHandles参数,这是winapi中蹑手蹑脚的一个讨厌的小单引号。确定该参数的适当值是非常困难的,你必须对你开始的过程有很多了解,而且它的可扩展性非常差,这是一个全有或全无的选择。Raymond Chen写了一篇博文,讨论了一些丑陋的角落案例,以及他们在Windows 6.0版本中是如何解决这个问题的。

不是一个可以在。net中使用的解决方案。主要是因为它仍然支持。net 4.5以前的Windows版本。而且很难用。相应地,ProcessStartInfo类没有允许你显式控制bInheritHandles参数值的属性,Process.Start()总是传递TRUE。这就是在我们把进程间的句柄传递作为第一类之前注释指。

进一步的细节是,子进程继承的句柄是一个独立的句柄,与父进程的句柄不同。因此总共需要两个 CloseHandle调用来销毁系统对象。或者换句话说,父进程和子进程都需要停止使用该对象。这就是"操作系统认为父进程和子进程的句柄是不同的"。注释指。

底层的CreatePipe() winapi函数用于创建匿名管道,它返回两个句柄,一个用于读,一个用于写。根据管道的方向,父进程(又名服务器)应该使用一个管道,子进程(又名客户端)应该使用一个管道。这些句柄是可继承的句柄,所以在启动子进程后,总共需要四个 CloseHandle调用来销毁管道对象。

这是不愉快的。. net包装器可以对服务器句柄做一些事情。它调用DuplicateHandle()来复制服务器端句柄,并为binherithhandle参数传递FALSE。然后关闭原始句柄。很好,子进程将不再继承服务器端句柄,所以现在只需要三个 CloseHandle调用。

但是,同样的技巧不适用于子进程需要使用的管道句柄。毕竟,目的是让它继承句柄,这样它就可以与服务器进行对话。这就是为什么您必须显式地执行它,在之后您知道子进程已正确启动。在你的DisposeLocalCopyOfClientHandle()方法调用之后,现在只需要两个 CloseHandle调用。 客户端的CloseHandle调用很简单,它通过调用AnonymousPipeClientStream上的Close或Dispose来实现。或者由于未处理的异常导致进程崩溃,操作系统随后负责关闭句柄。现在只剩下一个 CloseHandle调用了。

还有一个,在服务器端更难。它只知道关闭/处置它的AnonymousPipeServerStream,当它得到"通知"时;子进程不再使用它。在"通知"后面加上可怕的引号,没有事件告诉你这一点。正确的方法是让子进程发送显式的"再见";消息,以便服务器知道调用Close。不太合适但不常见的方式是,子进程没有很好地说再见,那么服务器只能从继续使用管道时得到的异常中知道它不在了。

这是关键,只有当操作系统看到服务器试图使用管道并且在另一边没有打开剩余的句柄时,您才会得到异常。或者换句话说,如果你忘记调用DisposeLocalCopyOfClientHandle(),那么你不会得到异常。不好。

您的困惑源于服务器和客户端也是父进程和子进程这一事实。管道句柄是服务器或客户端,但可以出现在父和子中。在服务器生成客户端之后,但在DisposeLocalCopyOfClientHandle被调用之前,有一小段时间,三个句柄在起作用:

  • 服务器(父)进程中的管道句柄。
  • 服务器(父)进程中管道的客户端句柄。
  • 客户端(子)进程中从父进程继承的管道句柄。

第二个句柄需要在子进程启动并运行后关闭,因为正如注释所解释的那样,在关闭所有客户端句柄之前,管道仍然可用。如果第二个句柄一直存在,它将阻止服务器检测子进程已经完成。

与其使用继承,实现还可以产生子进程并使用DuplicateHandle,这将消除对这个helper方法的需要,因为原始句柄可以立即关闭。这大概就是"使进程之间的句柄传递为一级"的意思。