C# 为什么重定向进程的输入流会影响TcpListener的行为?

C# 为什么重定向进程的输入流会影响TcpListener的行为?,c#,.net,winapi,C#,.net,Winapi,当一个线程被对TcpListener.AcceptTcpClient()的调用阻止,并且TcpListener被第二个线程停止()时,预期的行为是从对AcceptTcpClient()的调用引发的SocketException 当创建的进程的输入流被重定向时,对Process.Start(StartupInfo)的调用似乎会对此产生影响。这可以通过下面的代码显示 void Main() { TcpListener server = new TcpListener(IPAddress.An

当一个线程被对TcpListener.AcceptTcpClient()的调用阻止,并且TcpListener被第二个线程停止()时,预期的行为是从对AcceptTcpClient()的调用引发的SocketException

当创建的进程的输入流被重定向时,对Process.Start(StartupInfo)的调用似乎会对此产生影响。这可以通过下面的代码显示

void Main() {
    TcpListener server = new TcpListener(IPAddress.Any, 1339);
    server.Start();

    Stopwatch sw = new Stopwatch();

    Thread t = new Thread(() => {
        Thread.Sleep(1000);

        String cmdExe = Environment.ExpandEnvironmentVariables(@"%SYSTEMROOT%\system32\cmd.exe");
        ProcessStartInfo info = new ProcessStartInfo(cmdExe, "/Q");

        // This problem does not show up when this true
        info.UseShellExecute = false;

        // The exception is only delayed when this is false
        info.RedirectStandardInput = true;

        info.RedirectStandardOutput = true;
        info.RedirectStandardError = true;

        Process p = Process.Start(info);

        server.Stop();

        //Start a timer as soon as the server is stopped
        sw.Start();
    });

    t.Start();

    try {
        server.AcceptTcpClient();
    }
    catch (Exception) { }

    // Print how long between the server stopping and the exception being thrown
    sw.Stop();
    sw.Elapsed.Dump();
}
当UseShellExecute为true时,一切都按预期工作。当UseShellExecute为false时,在停止侦听器和引发异常之间有大约25-30秒的延迟。当UseShell Execute为false且RedirectStandardInput为true时,在进程终止之前,似乎永远不会引发异常

调用Stop()后,调试器显示侦听器确实已停止,套接字不再绑定。但是,任何输入连接都会抛出一个不同的SocketExceptions,表示“对象不是套接字”

我已经通过切换到异步方法解决了这个问题,这些方法似乎不受所有这些影响,但我无法理解这两个调用是如何连接的

更新 使用下面RbMm提供的信息,我通过更改侦听套接字的继承标志重新解决了这个问题。用于创建套接字的标志位于框架中,但是可以在创建侦听器后立即通过()更改继承标志。请注意,只要调用Stop(),就会创建一个新套接字,因此如果要重新启动侦听器,则需要再次调用该套接字

TcpListener server = new TcpListener(IPAddress.Any, 1339);
SetHandleInformation(server.Server.Handle, HANDLE_FLAGS.INHERIT, HANDLE_FLAGS.None);
server.Start();
套接字文件对象上的开始I/O请求。内部分配并与此文件对象关联

关闭套接字文件句柄。当文件的最后一个句柄关闭时,将调用该处理程序。例程取消与调用清理的文件对象关联的每个IRP(I/O请求)

因此,通常只存在一个文件(套接字)句柄,您可以在call
acceptcpclient
中使用该句柄。当调用
Stop
时(在客户端连接之前)-它关闭这个句柄。如果句柄是单句柄-这是最后一个句柄关闭,因此对它的所有I/O请求都已取消,
AcceptCpcClient
已完成,但出现错误(已取消)

但是,如果此套接字的句柄将被复制-关闭不是最后一个
停止
中的句柄不生效,则不会取消I/O

如何以及为什么重复套接字句柄?由于未知原因,默认情况下所有套接字句柄都是可继承的。仅从添加SP1标志的Windows 7开始,该标志允许创建不可继承的套接字

除非您在
bInheritHandles
设置为
true
的情况下不调用,否则这不会起作用。但在这样的调用之后,所有可继承句柄(包括所有套接字句柄)都将复制到子进程

重定向输入流实现对输入/输出/错误流使用可继承的命名管道。并将
bInheritHandles
设置为
true
启动该过程。这对网络代码有致命影响-侦听的套接字句柄复制到子进程和
Stop
closenot last套接字句柄(否则将有一个位于子进程中-在您的情况下为cmd)。因此,
AcceptCpcClient
将不会完成

在进程完成之前,似乎永远不会抛出异常 终止

当然。当子进程终止时-套接字的最后一个句柄将关闭,
AcceptCpcClient
完成

什么是解决方案?在C++上从Wi7 SP1开始-总是创建带有“代码> WSAIGravaNosiHANDLY继承/<代码>的套接字。在早期系统上-调用remove
HANDLE\u FLAG\u INHERIT

同样从vista开始,当我们需要使用一些重复的句柄启动子进程时,将
bInheritHandles
设置为true,这将所有可继承的句柄复制到子进程,我们可以使用显式设置子进程要继承的句柄数组

通过切换到似乎不受所有这些影响的异步方法


不。这里使用的同步或异步I/O(套接字句柄)与绝对无关。无论如何,不会取消I/O请求。简单地说,当您使用同步调用时—这是非常明显的—调用不会返回。如果您使用异步调用和托管环境-这里有更多问题,请注意这一点。如果使用callback,则必须在
AcceptCpcClient
完成时调用它-将不调用此回调。如果将事件与此io操作关联,则不会设置事件。等等。

谢谢您提供的详细信息。在对引用源中的大量复制粘贴内部类和少量反射进行测试之后,我能够修改正在创建的套接字的创建标志,看起来您完全正确。不幸的是,据我所知.NET没有提供简单的方法来实现这一点,并且已经对标志进行了硬编码。