【问题标题】:Are TCP SOCKET handles inheritable?TCP SOCKET 句柄是否可继承?
【发布时间】:2012-08-07 14:07:50
【问题描述】:

在 Windows 上,大多数类型的句柄都可以被子进程继承。期望 TCP 套接字也可以被继承。但是,当安装了某些分层服务提供商时,这将无法按预期工作(赛门铁克的 PCTools 等 A/V 产品曾导致我们为客户提供的应用程序出现问题)。

按照微软构建 WinSock 的方式,我们是否应该期望能够正确继承 SOCKET?

【问题讨论】:

  • 这个 MS KB 是错误的:support.microsoft.com/kb/150523 它表明 SOCKET 句柄可以被继承,但也在 SOCKET 上使用DuplicateHandle,尽管DuplicateHandle 的 MSDN 文档明确指出这是可能的: “目标进程中的 Winsock 可能无法识别句柄”正是因为 LSP。我真的开始怀疑 LSP 架构从根本上破坏了继承套接字。
  • WSADuplicateSocket 应该可以工作。
  • WSADuplicateSocket 不能工作,因为它必须在子进程启动后调用(显然,因为您需要目标进程的进程 ID)。那么,目标进程不仅会神奇地获得套接字;它必须接受它。如果你不能修改你正在调用的二进制文件并且只想重定向它的标准输出怎么办! WSADuplicateSocket 要求接收进程知道它的调用者,而它可能不知道。
  • 不能用两个管道吗?
  • 有点。这是丑陋的解决方法。我已经写下了我的结论作为答案。

标签: winapi winsock


【解决方案1】:

简答

不,不应将 SOCKET 标记为可继承。安装某些分层服务提供程序 (LSP) 后,继承的句柄根本无法在子级中使用。

另外,请参阅相关问题"Can TCP SOCKETS be marked non-inheritable?"。简而言之,您不能依赖能够继承套接字,但也不能阻止套接字被继承!

说明

遗憾的是,这与 Microsoft 自己的一些示例和文档(例如 KB150523)背道而驰。简而言之,分层服务提供程序是 Microsoft 为第三方软件提供的一种方式,可以将自己插入到您的应用程序和 Microsoft 的 WinSock DLL 中的 TCP/UDP 堆栈之间。由于某些 LSP 的工作方式,它们使得在进程之间传输套接字变得很困难,因为 LSP 将一些本地信息与它需要存在的每个套接字相关联。

LSP 只能挂接到 WinSock 函数;例如,在安装了某些 LSP 时,在 SOCKET 上调用 DuplicateHandle 将不起作用,因为它是句柄级函数,并且 LSP 永远没有机会复制它需要的信息。 (这在DuplicateHandledocumentation中有简要但明确的说明)。

类似地,尝试将 SOCKET 句柄设置为可继承将在不通知 LSP 的情况下复制句柄,结果相同:Winsock 在子进程中可能无法识别重复的句柄。典型的错误是 WSAENOTSOCK(10038,“非套接字上的套接字操作”),甚至是 ERROR_INVALID_HANDLE(6,“句柄无效”)。

示例

假设您要编写一个 Windows 程序,该程序使用重定向的 stdin 和 stdout 启动子进程,向其发送一些数据,在子进程的标准输入上发出 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 套接字,并在子节点中继承一个作为标准输入/标准输出,确实可以正常运行。很容易将其用作 Windows 上 socketpair 行为的解决方法(记得发送随机数!)。

遗憾的是,SOCKET 根本无法可靠地继承。要在 Windows 上编写具有几乎相同功能的东西,您需要使用命名管道。在调用 CreateProcess 之前,创建一对连接的 HANDLES,而不是使用 CreateNamedPipe/ConnectNamedPipe 和朋友(GetOverlappedResult 用于重叠的父句柄)。 (子句柄,作为标准输入,不能重叠!)子句柄可以设置为可继承,子句可以正常通信。

当您完成向客户端传输数据后,在父句柄上调用 FlushFileBuffersCloseHandle

其他问题

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

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

【讨论】:

  • SOCKETs should not be marked inheritable 好笑-A socket handle created by the WSASocket or the socket function is inheritable by default.。只从win7开始添加WSA_FLAG_NO_HANDLE_INHERIT
  • 使用命名管道时,等待子进程退出通常不是问题;当子进程退出时,管道中断,父进程的 I/O 操作将失败并显示ERROR_BROKEN_PIPE
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-31
  • 1970-01-01
相关资源
最近更新 更多