.NET TcpListener Stop 方法在有子进程时不会停止侦听器
.NET TcpListener Stop method does not stop the listener when there is a child process
我正在使用一些旧的 TCP 服务器代码,这些代码直接与套接字一起使用,因为它是在 .NET 2.0 和更早版本中编写的。服务器具有 'stop' 和 'start' 接受客户端连接的功能。
为了解决问题,我 运行 服务器以管理员用户身份处于控制台模式。最重要的是,我已经从等式中消除了套接字接受线程,所有代码所做的都是这样的:
tcpListener = new TcpListener(IPAddress.Any, this.Port);
tcpListener.Start();
和
tcpListener.Stop();
这是从不同的方法调用的。我调试了代码,我很确定代码只执行一次。然而,问题是对 Stop
的调用实际上并没有释放套接字地址,因此对 Start
的后续调用失败并显示错误 "Only one usage of each socket address (protocol/network address/port) is normally permitted"。我还可以从 ProcessExplorer 确认服务器仍在侦听服务器端口。
当我编写一个使用相同代码片段的小型控制台应用程序时,一切正常。我什至尝试跟踪 .NET 网络和套接字库,但没有错误或任何表明那里存在问题的东西。
我不清楚为什么调用 Stop
没有释放套接字地址?
更新:
经过更多调查后发现,子进程启动对 TcpListener
有一些奇怪的影响。我制作了一个 'bare bone' 示例代码来说明这个问题:
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
套接字句柄与许多其他句柄一起由子进程继承。可以找到关于该主题的一些讨论 here and here。
一个明显的解决方案是在初始化之前启动子进程 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);
}
我正在使用一些旧的 TCP 服务器代码,这些代码直接与套接字一起使用,因为它是在 .NET 2.0 和更早版本中编写的。服务器具有 'stop' 和 'start' 接受客户端连接的功能。
为了解决问题,我 运行 服务器以管理员用户身份处于控制台模式。最重要的是,我已经从等式中消除了套接字接受线程,所有代码所做的都是这样的:
tcpListener = new TcpListener(IPAddress.Any, this.Port);
tcpListener.Start();
和
tcpListener.Stop();
这是从不同的方法调用的。我调试了代码,我很确定代码只执行一次。然而,问题是对 Stop
的调用实际上并没有释放套接字地址,因此对 Start
的后续调用失败并显示错误 "Only one usage of each socket address (protocol/network address/port) is normally permitted"。我还可以从 ProcessExplorer 确认服务器仍在侦听服务器端口。
当我编写一个使用相同代码片段的小型控制台应用程序时,一切正常。我什至尝试跟踪 .NET 网络和套接字库,但没有错误或任何表明那里存在问题的东西。
我不清楚为什么调用 Stop
没有释放套接字地址?
更新:
经过更多调查后发现,子进程启动对 TcpListener
有一些奇怪的影响。我制作了一个 'bare bone' 示例代码来说明这个问题:
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
套接字句柄与许多其他句柄一起由子进程继承。可以找到关于该主题的一些讨论 here and here。
一个明显的解决方案是在初始化之前启动子进程 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);
}