C# 当存在子进程时,.NET TcpListener Stop方法不会停止侦听器

C# 当存在子进程时,.NET TcpListener Stop方法不会停止侦听器,c#,sockets,tcp,child-process,tcplistener,C#,Sockets,Tcp,Child Process,Tcplistener,我正在使用一些旧的TCP服务器代码,这些代码与.NET2.0及更早版本中编写的套接字直接一起使用。服务器具有“停止”和“启动”接受客户端连接的功能 为了解决这个问题,我以管理员用户的身份在控制台模式下运行服务器。除此之外,我还从等式中删除了套接字接受线程,代码所做的一切如下: tcpListener = new TcpListener(IPAddress.Any, this.Port); tcpListener.Start(); 及 这是从不同的方法调用的。我已经调试了代码,我很确定代码只执行

我正在使用一些旧的TCP服务器代码,这些代码与.NET2.0及更早版本中编写的套接字直接一起使用。服务器具有“停止”和“启动”接受客户端连接的功能

为了解决这个问题,我以管理员用户的身份在控制台模式下运行服务器。除此之外,我还从等式中删除了套接字接受线程,代码所做的一切如下:

tcpListener = new TcpListener(IPAddress.Any, this.Port);
tcpListener.Start();

这是从不同的方法调用的。我已经调试了代码,我很确定代码只执行一次。但是,问题是调用
停止
实际上并不会释放套接字地址,因此后续调用
启动
失败,错误为“每个套接字地址(协议/网络地址/端口)通常只允许一次使用”。我还可以从ProcessExplorer确认服务器仍在侦听服务器端口

当我编写一个使用相同代码片段的小型控制台应用程序时,一切都很好。我甚至尝试过跟踪.NET网络和套接字库,但没有任何错误或任何迹象表明存在问题

我不清楚为什么调用
Stop
不会释放套接字地址

更新: 经过进一步调查,发现子进程启动对
TcpListener
有一些奇怪的影响。我制作了一个“裸骨”示例代码来说明这个问题:

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;

namespace TcpListenerStartStop
{
    class MyTcpListener
    {
        public static void Main(string[] args)
        {
            Int32 port = 13000;

            if (args.Length > 0) // indicates child process
            {
                Thread.Sleep(4000); // as a child do nothing and wait for a few seconds
            }
            else // parent will play with the TcpListener
            {
                //LaunchChildProcess(); // launch child here and listener restart is fine?!?

                var tcpListener = new TcpListener(IPAddress.Any, port);
                tcpListener.Start();

                Console.WriteLine("Starting test in 2 seconds...");
                Thread.Sleep(2000);

                LaunchChildProcess(); // launch child here and listener restart is not fine?!?

                tcpListener.Stop();
                Console.WriteLine("Stopped.");
                Thread.Sleep(1000);

                tcpListener.Start();
                Console.WriteLine("Started");
            }

            Console.WriteLine("All is good, no exceptions :)");
        }

        private static void LaunchChildProcess()
        {
            Process process = new Process();
            var processStartInfo = new ProcessStartInfo
            {
                CreateNoWindow = true,
                FileName = Assembly.GetExecutingAssembly().Location,
                UseShellExecute = false, // comment out this line out listener restart is fine?!?
                Arguments = "child"
            };
            process.StartInfo = processStartInfo;

            process.Start();
        }
    }
}
正如您从代码中看到的,如果我在创建侦听器之前启动子进程,那么一切都很好,如果我在侦听器重新启动失败后启动它。这与子进程的
UseShellExecute=false
选项有关


不确定这是.NET错误还是我不知道的某些特殊行为?

此行为的真正原因是
TcpListener
套接字句柄与许多其他句柄一起由子进程继承。关于这个主题的一些讨论可以在下面找到

一个明显的解决方案是在初始化
TcpListener
之前启动子进程

另一个解决方案是使用
UseShellExecute=true
来避免此套接字句柄继承

理想的解决方案是设置套接字句柄选项以防止子进程继承。可以通过TcpListener套接字句柄上的P/Invoke在.NET中执行此操作:

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);
    private const uint HANDLE_FLAG_INHERIT = 1;

    private static void MakeNotInheritable(TcpListener tcpListener)
    {
        var handle = tcpListener.Server.Handle;
        SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
    }

:“调用者注意:Stop方法还关闭基础套接字,并为TcpListener创建一个新套接字。如果在调用Stop方法之前在基础套接字上设置了任何属性,这些属性将不会传递到新套接字。”,因此听起来像是在调用
Stop()
确实释放了套接字,但它立即启动了一个新的套接字。编辑:没关系,这是一个未绑定的套接字。好问题,好答案!很好的例子,很好的答案。本在这个问题上坚持了一天多。P/Invoke修复了它。
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);
    private const uint HANDLE_FLAG_INHERIT = 1;

    private static void MakeNotInheritable(TcpListener tcpListener)
    {
        var handle = tcpListener.Server.Handle;
        SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
    }