【问题标题】:.NET TcpListener Stop method does not stop the listener when there is a child process.NET TcpListener Stop 方法在有子进程时不会停止侦听器
【发布时间】:2016-05-18 18:17:33
【问题描述】:

我正在使用一些旧的 TCP 服务器代码,这些代码直接与套接字一起使用,因为它是用 .NET 2.0 和更早版本编写的。服务器具有“停止”和“开始”接受客户端连接的功能。

为了解决问题,我以管理员用户身份在控制台模式下运行服务器。最重要的是,我已经从等式中消除了套接字接受线程,所有代码所做的都是这样的:

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

tcpListener.Stop();

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

当我编写一个使用相同代码 sn-ps 的小型控制台应用程序时,一切正常。我什至尝试过跟踪 .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 错误还是我不知道的一些特殊行为?

【问题讨论】:

  • From the documentation: "调用者注意事项:Stop 方法还会关闭底层 Socket,并为 TcpListener 创建一个新的 Socket。如果在调用之前在底层 Socket 上设置了任何属性Stop 方法,这些属性将不会延续到新的 Socket。”,听起来像调用 Stop() 确实释放了套接字,但它立即启动了一个新的套接字。编辑:没关系,这是一个未绑定的套接字。

标签: c# sockets tcp child-process tcplistener


【解决方案1】:

这种行为的真正原因是TcpListener套接字句柄与许多其他句柄一起被子进程继承。可以在herehere 找到有关该主题的一些讨论。

一个明显的解决方案是在初始化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);
    }

【讨论】:

  • 好问题,好答案!也是很好的复制示例
  • 优秀的答案。 Benn 在这个问题上坚持了一天多。 P/Invoke 修复了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-20
  • 1970-01-01
  • 2019-04-28
  • 1970-01-01
  • 1970-01-01
  • 2017-12-20
  • 1970-01-01
相关资源
最近更新 更多