在Windows上,子进程可以继承大多数句柄。期望TCP套接字也可以继承。但是,当安装某些分层服务提供程序时,这不会按预期工作(A / V产品,例如Symantec的PCTools,会导致我们的客户应用程序出现问题)。
Microsoft构建WinSock的方式,我们是否应该能够正确继承SOCKET?
答案 0 :(得分:10)
不,SOCKET不应标记为可继承。安装某些分层服务提供程序(LSP)时,继承的句柄根本无法在子代中使用。
另外,请参阅相关问题"Can TCP SOCKETS be marked non-inheritable?"。简而言之,您不能依赖于能够继承套接字,但也不能阻止套接字被继承!
可悲的是,这违反了微软自己的一些示例和文档(例如KB150523)。简而言之,分层服务提供程序是Microsoft为第三方软件提供的一种方式,可以在您的WinSock DLL中将您自己插入应用程序和Microsoft的TCP / UDP堆栈之间。由于某些LSP的运行方式,它们很难在进程之间传输套接字,因为LSP将一些本地信息与它需要存在的每个套接字相关联。
LSP只能挂钩到WinSock函数;例如,在安装某些LSP时,在SOCKET上调用DuplicateHandle
将不起作用,因为它是一个句柄级函数,并且LSP永远不会有机会复制它所需的信息。 (这在DuplicateHandle
documentation中简要但明确说明。)
类似地,尝试将SOCKET句柄设置为可继承将在不通知LSP的情况下复制句柄,结果相同:子进程中的Winsock可能无法识别重复的句柄。典型错误是WSAENOTSOCK(10038,“非插槽上的套接字操作”),甚至是ERROR_INVALID_HANDLE(6,“句柄无效”)。
假设您要编写一个Windows程序,该程序启动带有重定向的stdin和stdout的子项,向它发送一些数据,在子项的stdin上发出EOF信号,以便它知道处理数据,然后等待子项返回。 / p>
让我们进一步假设一些富有想象力的启动形式被执行,这意味着您孩子可能根本就不是孩子(例如,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,行为正确。使用它作为Windows上socketpair
行为的解决方法很有诱惑力(记得发送一个nonce!)。
CreateProcess
之前,请使用CreateNamedPipe
/ ConnectNamedPipe
和朋友(GetOverlappedResult
创建一对连接的HANDLES作为重叠的父句柄)。 (要用作stdin的子句的句柄不能重叠!)子句的句柄可以设置为可继承,并且子项将通过它进行通信。
完成向客户端传送数据后,请在父句柄上调用FlushFileBuffers
和CloseHandle
。
在继续使用手柄之前等待孩子退出怎么样?没有办法直接用管道来做到这一点; Windows管道不能半封闭。如何做到这一点:
OpenProcess
将pid转换为句柄,所以如果你小心检查pid在OpenProcess
调用之后第二次,你可以摆脱在Unix上无法实现的竞争条件。)使用像这样的进程句柄仍然是一个正确的痛苦,因为你可能会发现你需要一个第二个命名管道连接将其发送,具体取决于您编写runas包装器的方式。问题:孩子如何收到父母已完成写入stdin的通知?如果父母试图呼叫DisconnectClient
,则孩子无法获得正常的EOF。根据您要执行的操作,这可能是个问题。当父关闭一个SOCKET时,你得到feof
,但是如果一个句柄连接到一个孩子的stdin,那么孩子将得到一个读错误,而不会向其发出EOF信号。这可能会导致孩子的工作方式与正常情况下连接到标准输出的方式完全相同。在父母中调用CloseHandle会在孩子中给出正确的行为。