TCP SOCKET处理是否可继承?

时间:2012-08-07 14:07:51

标签: winapi winsock

在Windows上,子进程可以继承大多数句柄。期望TCP套接字也可以继承。但是,当安装某些分层服务提供程序时,这不会按预期工作(A / V产品,例如Symantec的PCTools,会导致我们的客户应用程序出现问题)。

Microsoft构建WinSock的方式,我们是否应该能够正确继承SOCKET?

1 个答案:

答案 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!)。

可悲的是,但SOCKET根本无法可靠地继承。要在Windows上编写具有几乎相同功能的内容,您需要使用命名管道。在调用CreateProcess之前,请使用CreateNamedPipe / ConnectNamedPipe和朋友(GetOverlappedResult创建一对连接的HANDLES作为重叠的父句柄)。 (要用作stdin的子句的句柄不能重叠!)子句的句柄可以设置为可继承,并且子项将通过它进行通信。

完成向客户端传送数据后,请在父句柄上调用FlushFileBuffersCloseHandle

其他问题

  1. 在继续使用手柄之前等待孩子退出怎么样?没有办法直接用管道来做到这一点; Windows管道不能半封闭。如何做到这一点:

    1. (Unix方式,扭曲以适应Windows)制作另一对虚拟连接句柄,并在子进程中继承其中一个,但不要告诉孩子它(不要将它作为std句柄附加) )。然后,您可以等待父项中的第二个句柄检测子项何时退出。
    2. (Windows-y方式)一种正确的痛苦,但相当强大:使用命名管道获取父级中子级的实际进程句柄。这是Windows上等待孩子退出的“正确”方式。 (这实际上是你在Unix上无法做到的事情;只有进程的父进程可以直接等待进程退出; OpenProcess将pid转换为句柄,所以如果你小心检查pid在OpenProcess调用之后第二次,你可以摆脱在Unix上无法实现的竞争条件。)使用像这样的进程句柄仍然是一个正确的痛苦,因为你可能会发现你需要一个第二个命名管道连接将其发送,具体取决于您编写runas包装器的方式。
  2. 问题:孩子如何收到父母已完成写入stdin的通知?如果父母试图呼叫DisconnectClient,则孩子无法获得正常的EOF。根据您要执行的操作,这可能是个问题。当父关闭一个SOCKET时,你得到feof,但是如果一个句柄连接到一个孩子的stdin,那么孩子将得到一个读错误,而不会向其发出EOF信号。这可能会导致孩子的工作方式与正常情况下连接到标准输出的方式完全相同。在父母中调用CloseHandle会在孩子中给出正确的行为。