简短回答
不,SOCKET 不应标记为可继承。安装某些分层服务提供程序 (LSP) 后,继承的句柄根本无法在子级中使用。
作为额外的刺激,请参阅相关问题“TCP SOCKETS 可以标记为不可继承吗?” https://stackoverflow.com/questions/12058911。简而言之,您不能依赖能够继承套接字,但也不能阻止套接字被继承!
解释
遗憾的是,这违背了微软自己的一些示例和文档(例如KB150523 http://support.microsoft.com/kb/150523)。简而言之,分层服务提供程序是 Microsoft 为第三方软件提供的一种将其自身插入到您的应用程序和 WinSock DLL 中的 Microsoft TCP/UDP 堆栈之间的方式。由于某些 LSP 的工作方式,它们使得在进程之间传输套接字变得困难,因为 LSP 将一些本地信息与其需要存在的每个套接字相关联。
LSP 只能挂接到 WinSock 函数;例如,调用DuplicateHandle
当安装某些 LSP 时,SOCKET 上的功能将不起作用,因为它是句柄级函数,并且 LSP 永远没有机会复制其所需的信息。 (这在DuplicateHandle
文档 http://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx).
类似地,尝试将 SOCKET 句柄设置为可继承将在不通知 LSP 的情况下复制该句柄,从而产生相同的结果:子进程中的 Winsock 可能无法识别重复的句柄。典型的错误是WSAENOTSOCK(10038,“非套接字上的套接字操作”),甚至ERROR_INVALID_HANDLE(6,“句柄无效”)。
Example
假设您要编写一个 Windows 程序,该程序使用重定向的 stdin 和 stdout 启动一个子进程,向其发送一些数据,在子进程的 stdin 上发出 EOF 信号,以便它知道要处理数据,然后等待子进程返回。
让我们进一步假设执行某种富有想象力的启动形式,这意味着您的孩子可能根本不是孩子(例如, gksu/runas 启动必须立即退出的包装器,只留下与客户端)。因此,您无需等待子进程的 PID。
该行为将与此类似:
int main(int argc, char* argv[]) {
int handles[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, handles);
if (fork()) {
// child
close(handles[0]);
dup2(handles[1], 0);
dup2(handles[1], 1);
execl("clever-app", "clever-app", (char*)0);
}
// parent
close(handles[1]);
char* data[100];
write(handles[0], data, sizeof(data)); // should at least check for EINTR...
// tell the app we called there's nothing more to read from stdin:
shutdown(handles[0], SHUT_WR);
// wait until child has exited (discarding all output)
while (read(handles[0], data, sizeof(data)) >= 0) ;
// now continue with the rest of the program...
}
解决方法
在没有分层服务提供程序的计算机上,创建一对连接的 TCP 套接字并在子级中继承一个套接字作为 stdin/stdout,确实会正常运行。使用它作为解决方法很诱人socketpair
Windows 上的行为(记住发送随机数!)。
遗憾的是,SOCKET 根本无法可靠地继承。要在 Windows 上编写具有几乎相同功能的东西,您需要使用命名管道。致电之前CreateProcess
,创建一对连接的 HANDLES,而不是使用CreateNamedPipe
/ConnectNamedPipe
和朋友 (GetOverlappedResult
对于重叠的父句柄)。 (子进程的句柄要用作标准输入,不能重叠!)子进程的句柄可以设置为可继承,子进程将通过它正常通信。
当您完成向客户端传输数据后,调用FlushFileBuffers
and CloseHandle
在父句柄上。
进一步的问题
-
只使用句柄等待孩子退出后再继续怎么样?没有办法直接通过管道来做到这一点;窗户管道不能半封闭。执行此操作的方法:
- (Unix方式,扭曲以适应Windows)制作另一对虚拟的连接句柄,并在子进程中继承其中一个,但不要告诉子进程(不要将其附加为标准句柄)。然后,您可以等待父级中的第二个句柄来检测子级何时退出。
- (Windows 方式)确实很痛苦,但相当强大:使用命名管道获取父级中子级的实际进程句柄。这是 Windows 上等待子进程退出的“正确”方法。 (这实际上在Unix上是做不到的;只有进程的父进程才能直接等待进程退出;
OpenProcess
将 pid 转换为句柄,因此如果您在OpenProcess
调用,你可以摆脱在 Unix 上不可能实现这一点的竞争条件。)使用这样的进程句柄仍然是一个痛苦的事情,因为你可能会发现你需要第二个命名管道连接来发送它,具体取决于关于如何编写 runas 包装器。
问题:子级如何接收父级已完成对其标准输入的写入的通知?如果家长尝试打电话DisconnectClient
,孩子没有得到正常的 EOF。根据您尝试执行的内容,这可能是一个问题。当父母关闭一个 SOCKET 时,你会得到feof
,但是如果句柄连接到子进程的 stdin,则子进程将收到读取错误,而不会收到 EOF 信号。这可能会导致子进程的工作方式与正常连接到 stdin 时的工作方式不同。相反,在父级中调用 CloseHandle 会在子级中提供正确的行为。