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

.NET7控制器API转最小API:临时端点地址占用问题求助

解决.NET 7最小API临时端点地址占用问题

我正尝试将基于控制器的旧API路由转换为.NET 7最小API,需求是在主端点内创建临时端点,若20秒未被调用则自动关闭。修正端口填写错误后,出现「System.Net.HttpListenerException (48): Address already in use」错误,需要解决。

旧控制器代码

public class EndpointController : ControllerBase
{
    private bool endpointCalled = false;
    private HttpListener listener;

    [HttpGet]
    public IActionResult Endpoint1()
    {
        listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:7195/");
        listener.Start();

        // 创建临时POST端点
        listener.BeginGetContext(new AsyncCallback(Endpoint2), listener);

        // 20秒未调用则关闭端点
        var timer = new Timer(20000);
        timer.Elapsed += CloseEndpoint;
        timer.Start();

        return Results.Ok(new { message = "Endpoint2 created" });
    }

    private void Endpoint2(IAsyncResult result)
    {
        var context = listener.EndGetContext(result);
        var request = context.Request;
        var response = context.Response;

        string responseString = "<HTML><BODY> Hello from Endpoint2!</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        response.ContentLength64 = buffer.Length;
        System.IO.Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);
        output.Close();

        endpointCalled = true;
    }

    private void CloseEndpoint(object sender, ElapsedEventArgs e)
    {
        if (!endpointCalled)
        {
            listener.Stop();
        }
    }
}

新最小API代码

app.MapPost("/supplier/listen2", () =>
{
    bool endpointCalled = false;
    HttpListener listener = new HttpListener();

    listener.Prefixes.Add("http://localhost:7195/testi2/");
    listener.Start();

    listener.BeginGetContext(new AsyncCallback(Endpoint2), listener);
    var timer = new System.Timers.Timer(20000);
    timer.Elapsed += CloseEndpoint;
    timer.Start();

    void Endpoint2(IAsyncResult result)
    {
        var listener = (HttpListener)result.AsyncState;
        var context = listener.EndGetContext(result);
        var request = context.Request;
        var response = context.Response;

        string responseString = "<HTML><BODY> Hello from Endpoint2!</BODY></HTML>";
        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        response.ContentLength64 = buffer.Length;
        System.IO.Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);
        output.Close();

        endpointCalled = true;
    }
    
    void CloseEndpoint(object sender, System.Timers.ElapsedEventArgs e)
    {
        if (!endpointCalled)
        {
            listener.Stop();
        }
    }

    return Results.Ok(new { message = "Endpoint2 created" });
});

错误原因分析

  1. 重复绑定同一地址:每次调用/supplier/listen2都会创建新的HttpListener实例,并绑定到固定前缀http://localhost:7195/testi2/,导致端口被重复占用。
  2. 资源未彻底释放:即使调用listener.Stop(),未调用Close()或Dispose(),端口可能仍被系统保留,无法立即复用。
  3. 无重复创建校验:没有判断是否已有临时端点在运行,重复触发创建逻辑会直接抛出地址占用异常。

解决方案

修改后的最小API代码

// 全局维护临时端点的状态,避免重复创建
private static HttpListener? _tempListener;
private static bool _endpointCalled;
private static System.Timers.Timer? _tempTimer;

app.MapPost("/supplier/listen2", () =>
{
    // 检查是否已有活跃的临时端点
    if (_tempListener?.IsListening == true)
    {
        return Results.BadRequest(new { message = "临时端点已存在,请稍后再试" });
    }

    _endpointCalled = false;
    _tempListener = new HttpListener();
    // 生成唯一前缀,避免地址冲突
    var uniquePrefix = $"http://localhost:7195/testi2_{Guid.NewGuid()}/";
    _tempListener.Prefixes.Add(uniquePrefix);
    _tempListener.Start();

    // 异步处理临时端点请求
    _ = ProcessTempRequests(_tempListener);

    // 设置20秒超时关闭,仅触发一次
    _tempTimer = new System.Timers.Timer(20000);
    _tempTimer.Elapsed += (_, _) => CloseTempEndpoint();
    _tempTimer.AutoReset = false;
    _tempTimer.Start();

    return Results.Ok(new { message = "临时端点已创建", endpointUrl = uniquePrefix });
});

async Task ProcessTempRequests(HttpListener listener)
{
    try
    {
        while (listener.IsListening)
        {
            var context = await listener.GetContextAsync();
            _endpointCalled = true;
            
            var responseString = "<HTML><BODY> Hello from Endpoint2!</BODY></HTML>";
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            context.Response.ContentLength64 = buffer.Length;
            await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
            context.Response.OutputStream.Close();
            
            // 处理完请求后立即关闭临时端点(按需调整)
            CloseTempEndpoint();
        }
    }
    catch (HttpListenerException)
    {
        // 监听被停止时抛出的异常,无需处理
    }
}

void CloseTempEndpoint()
{
    // 彻底释放所有资源
    _tempTimer?.Stop();
    _tempTimer?.Dispose();
    _tempListener?.Stop();
    _tempListener?.Close();
    _tempListener?.Dispose();
    _tempListener = null;
}

关键优化点

  • 全局状态管理:用静态变量跟踪临时端点的运行状态,防止重复创建。
  • 唯一前缀生成:通过Guid.NewGuid()生成唯一的端点前缀,彻底避免地址冲突。
  • 异步请求处理:使用GetContextAsync替代旧的Begin/End异步模式,代码更简洁且符合.NET 7规范。
  • 资源彻底释放:关闭端点时同时停止定时器、释放HttpListener资源,确保端口被系统回收。
  • 重复创建校验:添加前置判断,返回明确的错误信息,避免用户重复触发异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 21:45:46