TCP SOCKET 句柄是否可继承? [英] Are TCP SOCKET handles inheritable?

查看:27
本文介绍了TCP SOCKET 句柄是否可继承?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

On Windows, most sorts of handles can be inherited by child processes. The expectation is that TCP sockets can also be inherited. However, when certain Layered Service Providers are installed, this does not work as expected (A/V products such as PCTools from Symantec used to cause problems with our application for customers).

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

The way Microsoft has architectured WinSock, should we expect to be able to inherit SOCKETs correctly?

推荐答案

简短回答

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

Short answer

No, SOCKETs should not be marked inheritable. When certain Layered Service Providers (LSPs) are installed, the inherited handles simply can't be used in the child.

更令人恼火的是,请参阅相关问题 TCP SOCKETS 是否可以标记为不可继承?".简而言之,您不能依赖能够继承套接字,但也不能阻止套接字被继承!

As an added irritation, see the related issue "Can TCP SOCKETS be marked non-inheritable?". Briefly, you can't rely on being able to inherit the sockets, but nor can you stop the sockets from being inherited!

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

Sadly, this goes against some of Microsoft's own examples and documentation (such as KB150523). Briefly, Layered Service Providers are a way Microsoft offers for third-party software to insert itself between your application and Microsoft's TCP/UDP stack in their WinSock DLLs. Due to the way some LSPs function, they make it hard to transfer sockets between processes, because the LSP associates some local information with each socket which it requires to be present.

LSP 只能钩入 WinSock 函数;例如,当安装了某些 LSP 时,在 SOCKET 上调用 DuplicateHandle 将不起作用,因为它是一个句柄级功能并且 LSP 永远没有机会复制它需要的信息.(这在 DuplicateHandle 文档).

An LSP can only hook into WinSock functions; for example, calling DuplicateHandle on a SOCKET will not work when certain LSPs are installed, because it is a handle-level function and the LSP is never given a chance to copy the information it needs. (This is briefly but clearly stated in the DuplicateHandle documentation).

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

Similarly, attempting to set a SOCKET handle to be inheritable will copy the handle without informing the LSP, with the same result: the duplicate handle may not be recognized by Winsock in the child process. Typical errors are WSAENOTSOCK (10038, "Socket operation on nonsocket"), or even ERROR_INVALID_HANDLE (6, "The handle is invalid").

假设您要编写一个 Windows 程序,该程序使用重定向的 stdin 和 stdout 启动子进程,向其发送一些数据,在子进程的 stdin 上发出 EOF 信号,以便它知道处理数据,然后等待子进程返回.

Suppose you want to write a Windows program that launches a child with redirected stdin and stdout, sends it some data, signals EOF on the child's stdin so it knows to process the data, and then waits for the child to return.

让我们进一步假设执行了某种富有想象力的启动形式,这意味着您的孩子可能根本不是孩子(例如,gksu/runas 启动必须立即退出的包装器,只剩下到客户端的套接字连接).因此,您没有孩子的 PID 可以等待.

Let's suppose further that some imaginative form of launching is performed, which means that you the child perhaps isn't a child at all (for example, gksu/runas to launch a wrapper which must exit immediately, leaving you with just the socket connection to the client). You don't therefore have the child's PID to wait on.

行为将类似于:

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 行为的解决方法(记住发送随机数!).

Workaround

On a machine without Layered Service Providers, creating a connected pair of TCP sockets, and inheriting one in the child as stdin/stdout, does behave correctly. It's tempting to use this as the workaround for socketpair behaviour on Windows (remember to send a nonce!).

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

Sadly, but the SOCKET simply can't be reliably inherited. To write something with almost equivalent functionality on Windows, you need to use Named Pipes. Before calling CreateProcess, create a pair of connected HANDLES instead using CreateNamedPipe/ConnectNamedPipe and friends (GetOverlappedResult for an overlapped parent handle). (The handle for the child, to be used as stdin, must not be overlapped!) The handle for the child can set inheritable, and the child will communicate over it normally.

当您完成将数据传送到客户端后,在父句柄上调用 FlushFileBuffersCloseHandle.

When you have finished piping data to the client, call FlushFileBuffers and CloseHandle on the parent handle.

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

  1. What about waiting for the child to exit before continuing, using only the handle? There isn't a way to do that directly with just the pipe to it; Windows pipes can't be half-closed. Ways to do this:

  1. (Unix 方式,扭曲以适应 Windows)制作另一对连接的虚拟句柄,并在子进程中继承其中一个,但不要告诉子进程(不要将其附加为 std 句柄).然后,您可以等待父级中的第二个句柄来检测子级何时退出.
  2. (Windows-y 方式)一个正确的痛苦,但相当强大:使用命名管道获取父级中子级的实际进程句柄.这是 Windows 上等待孩子退出的正确"方式.(这实际上在 Unix 上是做不到的;只有进程的父进程可以直接等待进程退出;OpenProcess 将 pid 转换为句柄,所以如果你小心在 OpenProcess 调用后第二次检查 pid,您可以摆脱在 Unix 上无法做到这一点的竞争条件.)使用这样的进程句柄仍然是一种正确的痛苦,因为您可能会发现您需要第二个命名管道连接来发送它,这取决于您编写 runas 包装器的方式.
  1. (The Unix way, contorted to fit Windows) Make another dummy pair of connected handles, and inherit one of them in the child process, but don't tell the child about it (don't attach it as a std handle). Then, you can wait on the second handle in the parent to detect when the child exits.
  2. (The Windows-y way) A right pain, but rather powerful: Use the named pipe to get an actual process handle to the child in the parent. This is the 'correct' way on Windows to wait for a child to exit. (This is actually something you can't do on Unix; only the parent of a process can directly wait on a process to exit; OpenProcess converts a pid to a handle, so if you're careful to check the pid a second time after the OpenProcess call, you can get rid of the race condition that would make this not possible on Unix.) Using a process handle like this is nonetheless a right pain because you'll probably find you need a second named pipe connection to send it over, depending on how you write your runas wrapper.

  • 一个问题:子级如何收到父级已完成写入其标准输入的通知?如果父级尝试调用 DisconnectClient,则子级不会获得正常的 EOF.根据您尝试执行的操作,这可能是一个问题.当父级关闭 SOCKET 时,您会得到 feof,但是如果句柄连接到子级的 stdin,子级将收到读取错误,而不会收到 EOF 信号.这可能会导致孩子不像正常连接到标准输入那样以完全相同的方式工作.在父级中调用 CloseHandle 会在子级中提供正确的行为.

  • A gotcha: How does the child receive notification that the parent has finished writing to its stdin? If the parent tries to call DisconnectClient, the child doesn't get a normal EOF. Depending on what you're trying to execute, this may be a problem. When the parent shuts down a SOCKET, you do get feof, but if a handle is connected to a child's stdin, the child will get a read error without getting EOF signalled to it. This may cause the child not to work in exactly the same way as if it were hooked up to stdin normally. Calling CloseHandle in the parent instead gives the right behaviour in the child.

    这篇关于TCP SOCKET 句柄是否可继承?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆