为 2 个 kestrel 服务器找到 2 个不同的免费端口

Find 2 different FREE ports for 2 kestrel servers

我需要从控制台应用程序启动 2 个 Kestrel 服务器。下面的代码显示了我现在是怎么做的。

不幸的是,两台服务器都试图在相同的端口 HTTP:5000HTTPS:5001 上启动,但实际上只有第一个启动了。

我也尝试在 appsettings.json 中指定 URLs 但它没有按预期工作,我不想硬编码服务器 URL,因为如果我重新启动控制台应用程序它不会杀死之前启动的服务器并且无法再次启动它们。

问题

如何从代码为两个服务器的 HTTP 和 HTTPS 找到空闲端口并确保它们不同?

服务器

public class WebServer
{
  public static IWebHost Run<TStartup>(WebOptions options = null)
  {
    var configuration = new ConfigurationBuilder().Build();

    var environment = WebHost
      .CreateDefaultBuilder(new string[0])
      .ConfigureServices(o => o.AddSingleton(options))
      .UseConfiguration(configuration)
      .UseContentRoot(Directory.GetCurrentDirectory())
      .UseKestrel()
      .UseStartup<TStartup>()
      .Build();

    environment.RunAsync();

    return environment;
  }
}

var serviceEnvironment = Server.Run<ServiceStartup>();
var webEnvironment = Server.Run<WebStartup>();
var serviceAddresses = serviceEnvironment.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
var webAddresses = webEnvironment.ServerFeatures.Get<IServerAddressesFeature>().Addresses;

您可以绑定到端口 0,Kestrel 会自动找到一个随机可用端口。

来自红隼的 Microsoft docs

When the port number 0 is specified, Kestrel dynamically binds to an available port. The following example shows how to determine which port Kestrel actually bound at runtime:

public void Configure(IApplicationBuilder app)
{
    var serverAddressesFeature = 
        app.ServerFeatures.Get<IServerAddressesFeature>();

    app.UseStaticFiles();

    app.Run(async (context) =>
    {
        context.Response.ContentType = "text/html";
        await context.Response
            .WriteAsync("<!DOCTYPE html><html lang=\"en\"><head>" +
                "<title></title></head><body><p>Hosted by Kestrel</p>");

        if (serverAddressesFeature != null)
        {
            await context.Response
                .WriteAsync("<p>Listening on the following addresses: " +
                    string.Join(", ", serverAddressesFeature.Addresses) +
                    "</p>");
        }

        await context.Response.WriteAsync("<p>Request URL: " +
            $"{context.Request.GetDisplayUrl()}<p>");
    });
}