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

你可以把它想象成一个轻量级的、内置的 Web 服务器,它非常适合以下场景:
- 创建简单的 API 服务:当你的 API 非常简单,不需要 MVC、依赖注入等复杂功能时。
- 文件服务器:快速搭建一个用于提供文件下载或 Web 页面的服务。
- 自定义协议或网关:作为后端服务的入口点,处理特定的 HTTP 请求并转发。
- 学习和实验:理解 HTTP 请求/响应的底层工作原理。
- 集成到现有应用程序:在你的桌面应用(WinForms/WPF)或服务中嵌入一个 HTTP 端点。
HttpListener 的工作原理
HttpListener 的工作流程非常直观,类似于一个服务员在餐厅里等待点单:
- 开门营业:创建一个
HttpListener实例,并告诉它要监听的 URL 前缀(http://+:8080/)。 - 开始等待:调用
Start()方法,服务员开始站在门口等待客人。 - 接收请求:当有客户端(浏览器、Postman、其他程序)向指定的 URL 发送请求时,
HttpListener会接收到它。 - 获取请求上下文:调用
GetContext()方法,这个方法是阻塞式的,它会等待直到一个请求进来,一旦有请求,它会返回一个HttpListenerContext对象,这个对象包含了完整的请求信息(Request属性)和一个用于构建响应的响应对象(Response属性)。 - 处理并响应:在请求上下文中,你可以读取请求的 URL、头信息、HTTP 方法(GET, POST 等)、查询参数和请求体,你可以构建一个响应,设置状态码(如 200 OK)、响应头和响应体(比如返回 JSON、HTML 或文件)。
- 发送响应:调用
Response对象的Close()或OutputStream的Close()方法,将响应发送回客户端,服务员把菜(响应)端给客人,这次服务结束。 - 循环等待:服务器继续回到第 3 步,等待下一个请求。
一个完整的 HttpListener 示例
下面是一个功能完整的控制台应用程序,它启动一个简单的 HTTP 服务器,能够处理 GET 和 POST 请求,并返回 JSON 响应。
创建项目
创建一个新的 .NET 控制台应用(使用 .NET 6 或更高版本)。

编写代码
将 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);
}
}
}
运行和测试
- 以管理员身份运行:右键点击你的 IDE(如 Visual Studio)或命令行,选择“以管理员身份运行”,这是因为绑定到低于 1024 的端口需要管理员权限,而 8080 有时也可能需要。
- 启动服务器:运行程序,你会看到控制台输出:
服务器已启动,监听: http://+:8080/ 按 Ctrl+C 停止服务器... - 测试 GET 请求:
- 打开浏览器或使用 Postman/
curl。 - 访问
http://localhost:8080/?name=Alice&id=123 - 你会看到响应:
{ "message": "你好, Alice!", "id": 123 }
- 打开浏览器或使用 Postman/
- 测试 POST 请求:
- 在 Postman 中,创建一个 POST 请求到
http://localhost:8080/。 - 在 Body 中选择 "raw" -> "JSON",并输入一些内容,
{ "key": "value" } - 发送请求,你会看到服务器控制台打印出请求体,并收到响应:
{ "receivedData": "{ \"key\": \"value\" }", "timestamp": "2025-10-27T10:30:00.1234567Z" }
- 在 Postman 中,创建一个 POST 请求到
- 停止服务器:在运行服务器的控制台窗口中按下
Ctrl+C,服务器会优雅地关闭。
重要注意事项和最佳实践
- 权限问题:如上所述,在 Windows 上绑定端口通常需要管理员权限,在 Linux/macOS 上,绑定到低于 1024 的端口也需要 root 权限。
- 异步处理:
GetContextAsync是异步的,你应该在async/await上下文中使用它,或者将其与Task.Run结合使用,以避免阻塞线程,在示例中,我们使用Task.Run来处理每个请求,这样即使某个请求耗时很长,也不会影响服务器接收新请求。 - 异常处理:
GetContextAsync在服务器关闭时会抛出HttpListenerException,这是正常的,你的代码应该捕获并处理这些异常,以实现优雅的关闭。 - 关闭响应流:在
finally块中调用response.Close()是至关重要的,忘记关闭会导致客户端连接挂起,无法正确接收响应。 - 安全性:
- URL 前缀:
http://+:8080/中的 表示监听所有网络接口,如果你的服务只应在本地运行,请使用http://localhost:8080/以防止外部访问。 - HTTPS:对于生产环境,强烈建议使用 HTTPS。
HttpListener也支持 HTTPS,你只需要在 URL 前缀中使用https://并配置 SSL 证书即可,这可以防止数据在传输过程中被窃听。
- URL 前缀:
- 与 ASP.NET Core 的对比:
HttpListener:轻量级,低开销,适合简单的、自定义的端点,你需要自己处理路由、序列化、中间件等所有事情。- ASP.NET Core:功能全面的 Web 框架,提供了路由、MVC、依赖注入、中间件管道、Kestrel 服务器、易于集成各种库等优点,适用于构建复杂的 Web 应用和 API。
HttpListener 是一个非常有用的工具,它为你提供了一种直接与 HTTP 协议交互的方式,而无需引入一个庞大的 Web 框架,它非常适合快速原型设计、创建微服务网关、或者在桌面应用中嵌入简单的网络功能。
对于绝大多数 Web 应用开发,ASP.NET Core 仍然是更好的选择,因为它提供了更丰富的生态系统、更强大的功能和更完善的开发体验,你可以将 HttpListener 看作是 .NET 工具箱中的一把“瑞士军刀”,而不是用来砍树的“电锯”。

