凌峰创科服务平台

如何用HttpListener搭建简单服务器?

什么是 HttpListener

HttpListener 是 .NET Framework 和 .NET Core/.NET 5+ 中提供的一个类,它允许你的应用程序直接监听 HTTP 请求,而无需像 ASP.NET Core 或 ASP.NET 那样依赖于完整的 Web 服务器框架(如 Kestrel 或 IIS)。

如何用HttpListener搭建简单服务器?-图1
(图片来源网络,侵删)

你可以把它想象成一个轻量级的、内置的 Web 服务器,它非常适合以下场景:

  • 创建简单的 API 服务:当你的 API 非常简单,不需要 MVC、依赖注入等复杂功能时。
  • 文件服务器:快速搭建一个用于提供文件下载或 Web 页面的服务。
  • 自定义协议或网关:作为后端服务的入口点,处理特定的 HTTP 请求并转发。
  • 学习和实验:理解 HTTP 请求/响应的底层工作原理。
  • 集成到现有应用程序:在你的桌面应用(WinForms/WPF)或服务中嵌入一个 HTTP 端点。

HttpListener 的工作原理

HttpListener 的工作流程非常直观,类似于一个服务员在餐厅里等待点单:

  1. 开门营业:创建一个 HttpListener 实例,并告诉它要监听的 URL 前缀(http://+:8080/)。
  2. 开始等待:调用 Start() 方法,服务员开始站在门口等待客人。
  3. 接收请求:当有客户端(浏览器、Postman、其他程序)向指定的 URL 发送请求时,HttpListener 会接收到它。
  4. 获取请求上下文:调用 GetContext() 方法,这个方法是阻塞式的,它会等待直到一个请求进来,一旦有请求,它会返回一个 HttpListenerContext 对象,这个对象包含了完整的请求信息(Request 属性)和一个用于构建响应的响应对象(Response 属性)。
  5. 处理并响应:在请求上下文中,你可以读取请求的 URL、头信息、HTTP 方法(GET, POST 等)、查询参数和请求体,你可以构建一个响应,设置状态码(如 200 OK)、响应头和响应体(比如返回 JSON、HTML 或文件)。
  6. 发送响应:调用 Response 对象的 Close()OutputStreamClose() 方法,将响应发送回客户端,服务员把菜(响应)端给客人,这次服务结束。
  7. 循环等待:服务器继续回到第 3 步,等待下一个请求。

一个完整的 HttpListener 示例

下面是一个功能完整的控制台应用程序,它启动一个简单的 HTTP 服务器,能够处理 GET 和 POST 请求,并返回 JSON 响应。

创建项目

创建一个新的 .NET 控制台应用(使用 .NET 6 或更高版本)。

如何用HttpListener搭建简单服务器?-图2
(图片来源网络,侵删)

编写代码

Program.cs 文件的内容替换为以下代码:

// 引入必要的命名空间
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading;
public class HttpServer
{
    private readonly HttpListener _listener;
    private readonly CancellationTokenSource _cts = new();
    public HttpServer(string uriPrefix)
    {
        _listener = new HttpListener();
        _listener.Prefixes.Add(uriPrefix);
    }
    public void Start()
    {
        // 注意:在 Windows 上,运行此程序可能需要管理员权限,
        // 因为它要绑定到 80 或 8080 等端口。
        // 在开发机器上,通常以管理员身份运行 Visual Studio 或命令行。
        _listener.Start();
        Console.WriteLine($"服务器已启动,监听: {_listener.Prefixes.First()}");
        Console.WriteLine("按 Ctrl+C 停止服务器...");
        // 启动一个新线程来处理请求,这样主线程可以等待取消信号
        Task.Run(() => HandleRequests(_cts.Token));
        // 等待用户按下 Ctrl+C
        Console.CancelKeyPress += (sender, e) =>
        {
            e.Cancel = true; // 防止进程立即退出
            Stop();
        };
    }
    public void Stop()
    {
        _cts.Cancel();
        _listener.Stop();
        Console.WriteLine("服务器已停止。");
    }
    private async Task HandleRequests(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try
            {
                // GetContext() 是阻塞调用,但在这里它会在一个单独的线程中运行
                HttpListenerContext context = await _listener.GetContextAsync();
                // 处理请求不阻塞主循环
                _ = Task.Run(() => ProcessRequest(context), token);
            }
            catch (HttpListenerException ex) when (ex.ErrorCode == 995) // 操作被取消
            {
                // 当服务器停止时,GetContextAsync 会抛出此异常,这是正常的
                break;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"处理请求时发生错误: {ex.Message}");
            }
        }
    }
    private void ProcessRequest(HttpListenerContext context)
    {
        HttpListenerRequest request = context.Request;
        HttpListenerResponse response = context.Response;
        try
        {
            Console.WriteLine($"收到请求: {request.HttpMethod} {request.Url.AbsolutePath}");
            // 处理 GET 请求
            if (request.HttpMethod == "GET")
            {
                // 获取查询参数
                string name = request.QueryString["name"] ?? "匿名";
                int id = int.TryParse(request.QueryString["id"], out int parsedId) ? parsedId : 0;
                var data = new { Message = $"你好, {name}!", Id = id };
                string json = JsonSerializer.Serialize(data, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
                byte[] buffer = Encoding.UTF8.GetBytes(json);
                response.ContentType = "application/json";
                response.ContentLength64 = buffer.Length;
                response.OutputStream.Write(buffer, 0, buffer.Length);
            }
            // 处理 POST 请求
            else if (request.HttpMethod == "POST")
            {
                // 读取请求体
                using var reader = new StreamReader(request.InputStream, request.ContentEncoding);
                string requestBody = await reader.ReadToEndAsync();
                Console.WriteLine($"请求体内容: {requestBody}");
                // 这里可以解析 JSON、XML 等
                var responseData = new { ReceivedData = requestBody, Timestamp = DateTime.UtcNow };
                string json = JsonSerializer.Serialize(responseData);
                byte[] buffer = Encoding.UTF8.GetBytes(json);
                response.ContentType = "application/json";
                response.StatusCode = (int)HttpStatusCode.OK; // 显式设置状态码
                response.ContentLength64 = buffer.Length;
                response.OutputStream.Write(buffer, 0, buffer.Length);
            }
            else
            {
                response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理请求时发生内部错误: {ex}");
            response.StatusCode = (int)HttpStatusCode.InternalServerError;
        }
        finally
        {
            // 必须关闭响应流,否则客户端将不会收到响应
            response.Close();
        }
    }
}
public class Program
{
    public static void Main(string[] args)
    {
        // 使用 "http://+:8080/" 可以从本机的任何 IP 地址访问
        // 如果只想从本地访问,可以使用 "http://localhost:8080/" 或 "http://127.0.0.1:8080/"
        string uriPrefix = "http://+:8080/";
        var server = new HttpServer(uriPrefix);
        server.Start();
        // 保持主线程运行
        while (true)
        {
            Thread.Sleep(1000);
        }
    }
}

运行和测试

  1. 以管理员身份运行:右键点击你的 IDE(如 Visual Studio)或命令行,选择“以管理员身份运行”,这是因为绑定到低于 1024 的端口需要管理员权限,而 8080 有时也可能需要。
  2. 启动服务器:运行程序,你会看到控制台输出:
    服务器已启动,监听: http://+:8080/
    按 Ctrl+C 停止服务器...
  3. 测试 GET 请求
    • 打开浏览器或使用 Postman/curl
    • 访问 http://localhost:8080/?name=Alice&id=123
    • 你会看到响应:
      {
        "message": "你好, Alice!",
        "id": 123
      }
  4. 测试 POST 请求
    • 在 Postman 中,创建一个 POST 请求到 http://localhost:8080/
    • 在 Body 中选择 "raw" -> "JSON",并输入一些内容,
      { "key": "value" }
    • 发送请求,你会看到服务器控制台打印出请求体,并收到响应:
      {
        "receivedData": "{ \"key\": \"value\" }",
        "timestamp": "2025-10-27T10:30:00.1234567Z"
      }
  5. 停止服务器:在运行服务器的控制台窗口中按下 Ctrl+C,服务器会优雅地关闭。

重要注意事项和最佳实践

  1. 权限问题:如上所述,在 Windows 上绑定端口通常需要管理员权限,在 Linux/macOS 上,绑定到低于 1024 的端口也需要 root 权限。
  2. 异步处理GetContextAsync 是异步的,你应该在 async/await 上下文中使用它,或者将其与 Task.Run 结合使用,以避免阻塞线程,在示例中,我们使用 Task.Run 来处理每个请求,这样即使某个请求耗时很长,也不会影响服务器接收新请求。
  3. 异常处理GetContextAsync 在服务器关闭时会抛出 HttpListenerException,这是正常的,你的代码应该捕获并处理这些异常,以实现优雅的关闭。
  4. 关闭响应流:在 finally 块中调用 response.Close() 是至关重要的,忘记关闭会导致客户端连接挂起,无法正确接收响应。
  5. 安全性
    • URL 前缀http://+:8080/ 中的 表示监听所有网络接口,如果你的服务只应在本地运行,请使用 http://localhost:8080/ 以防止外部访问。
    • HTTPS:对于生产环境,强烈建议使用 HTTPSHttpListener 也支持 HTTPS,你只需要在 URL 前缀中使用 https:// 并配置 SSL 证书即可,这可以防止数据在传输过程中被窃听。
  6. 与 ASP.NET Core 的对比
    • HttpListener:轻量级,低开销,适合简单的、自定义的端点,你需要自己处理路由、序列化、中间件等所有事情。
    • ASP.NET Core:功能全面的 Web 框架,提供了路由、MVC、依赖注入、中间件管道、Kestrel 服务器、易于集成各种库等优点,适用于构建复杂的 Web 应用和 API。

HttpListener 是一个非常有用的工具,它为你提供了一种直接与 HTTP 协议交互的方式,而无需引入一个庞大的 Web 框架,它非常适合快速原型设计、创建微服务网关、或者在桌面应用中嵌入简单的网络功能。

对于绝大多数 Web 应用开发,ASP.NET Core 仍然是更好的选择,因为它提供了更丰富的生态系统、更强大的功能和更完善的开发体验,你可以将 HttpListener 看作是 .NET 工具箱中的一把“瑞士军刀”,而不是用来砍树的“电锯”。

如何用HttpListener搭建简单服务器?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇