You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Minimal API临时端点监听报错:地址已被占用求助

问题

想要在ASP.NET Minimal API中实现一个接口,调用该接口时生成并监听一个临时端点45秒,若该临时端点被请求则结束流程。编写的代码如下:

app.MapPost("/dataid/supplier/listen", async (Supplier supplier) =>
{
    var listener = new HttpListener();

    listener.Prefixes.Add($"https://localhost:7147/dataid/supplier/{supplier.token}/");

    listener.Start();

    HttpListenerContext context = listener.GetContext();

    HttpListenerRequest requesti = context.Request;

    for (int i = 0; i < 45; i++)
    {
        if (context.Response != null)
        {
            HttpListenerResponse response = context.Response;
            Console.WriteLine(response.ContentLength64);
            listener.Stop();
            return Results.Ok(new { token = "2" });
        }
        Thread.Sleep(1000);
        Console.WriteLine(i);
        i++;
    }

    listener.Stop();
    return Results.Ok(new { token = "1" });
});

运行时提示地址已被占用,报错信息:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.Net.HttpListenerException (48): Address already in use
         at System.Net.HttpEndPointManager.GetEPListener(String host, Int32 port, HttpListener listener, Boolean secure)
解决方案

问题根源

  1. 端口冲突:你的Minimal API本身已经在监听https://localhost:7147,再用HttpListener监听同一端口的前缀,会直接触发地址占用错误。
  2. 逻辑阻塞:listener.GetContext()是同步阻塞方法,会一直等待请求到来,后续的for循环根本不会执行;同时循环里额外的i++会导致实际等待间隔变成2秒,逻辑不符合预期。

推荐方案:复用现有Minimal API端口(无需额外HttpListener)

利用Minimal API的动态路由注册和内存状态管理实现需求,无需启动额外监听服务:

1. 定义内存状态存储

在Program.cs中添加静态字典,用于跟踪每个临时token对应的等待任务:

// 存储每个token对应的任务完成信号源
private static readonly Dictionary<string, TaskCompletionSource<string>> _pendingTokens = new();

2. 重写原接口逻辑

app.MapPost("/dataid/supplier/listen", async (Supplier supplier, CancellationToken ct) =>
{
    var token = supplier.token;
    
    // 避免重复使用同一token
    if (_pendingTokens.ContainsKey(token))
    {
        return Results.BadRequest(new { message = "Token already in use" });
    }

    var tcs = new TaskCompletionSource<string>();
    _pendingTokens.Add(token, tcs);

    // 动态注册临时端点
    var tempEndpoint = app.MapGet($"/dataid/supplier/{token}", async () =>
    {
        if (_pendingTokens.TryRemove(token, out var pendingTcs))
        {
            pendingTcs.SetResult("2");
            return Results.Ok(new { token = "2" });
        }
        return Results.NotFound();
    });

    try
    {
        // 等待45秒超时或临时端点被请求
        var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(45), ct));
        
        if (completedTask == tcs.Task)
        {
            return Results.Ok(new { token = tcs.Task.Result });
        }
        else
        {
            // 超时后清理临时端点和状态
            _pendingTokens.Remove(token);
            tempEndpoint.Dispose();
            return Results.Ok(new { token = "1" });
        }
    }
    finally
    {
        // 确保请求结束后清理资源
        _pendingTokens.Remove(token);
        tempEndpoint.Dispose();
    }
});

备选方案:使用独立端口的HttpListener

如果坚持使用HttpListener,需要更换未被占用的端口,并修复阻塞逻辑:

app.MapPost("/dataid/supplier/listen", async (Supplier supplier, CancellationToken ct) =>
{
    // 使用与Minimal API不同的端口,例如7148
    var prefix = $"https://localhost:7148/dataid/supplier/{supplier.token}/";
    var listener = new HttpListener();
    listener.Prefixes.Add(prefix);

    try
    {
        listener.Start();

        // 异步等待请求,避免同步阻塞
        var listenTask = listener.GetContextAsync();
        var completedTask = await Task.WhenAny(listenTask, Task.Delay(TimeSpan.FromSeconds(45), ct));

        if (completedTask == listenTask)
        {
            var context = await listenTask;
            // 给临时端点的请求返回响应
            using var response = context.Response;
            var responseBytes = System.Text.Encoding.UTF8.GetBytes("{\"token\":\"2\"}");
            response.ContentLength64 = responseBytes.Length;
            await response.OutputStream.WriteAsync(responseBytes, ct);
            
            listener.Stop();
            return Results.Ok(new { token = "2" });
        }
        else
        {
            listener.Stop();
            return Results.Ok(new { token = "1" });
        }
    }
    catch (HttpListenerException ex)
    {
        listener.Stop();
        return Results.BadRequest(new { message = $"Failed to start listener: {ex.Message}" });
    }
});

注意事项

  • 方案一中的动态路由注册需要确保你的Minimal API支持动态添加端点(.NET 6+版本默认支持)。
  • 方案二中需要确保新端口(如7148)未被其他程序占用,且防火墙允许该端口的访问。

内容的提问来源于stack exchange,提问作者thelittlemaster

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 13:31:13