目录
C# 实现 HTTP 服务器三种方案
方案 1:内置 HttpListener(零第三方库,轻量,Windows/Linux 跨平台)
完整示例
关键说明
方案 2:ASP.NET Core(工业级、推荐正式项目)
最简控制台 Web 服务
方案 3:Socket 原生手写 HTTP(底层原理学习,不推荐生产)
三种方案选型对比
常用扩展补充
1. HttpListener 开启 HTTPS
2. 并发优化
3. 静态文件返回(HttpListener)
项目中已验证过实例(复制过去即可用)
Nancy 自动加载模块底层原理
使用
完整封装 HttpListener HTTP 服务类
使用说明
1. 访问测试
2. 内置能力清单
3. 端口权限说明
4. 扩展方向(按需自己加)
C# 实现 HTTP 服务器三种方案
方案 1:内置HttpListener(零第三方库,轻量,Windows/Linux 跨平台)
无需 NuGet,.NET Framework /.NET Core /.NET 5+ 通用,适合小型接口、本地工具服务。
完整示例
using System; using System.Net; using System.Text; using System.Threading.Tasks; class SimpleHttpServer { static async Task Main(string[] args) { // 监听地址,末尾必须带 / string prefix = "http://localhost:8080/"; using HttpListener listener = new HttpListener(); listener.Prefixes.Add(prefix); try { listener.Start(); Console.WriteLine($"服务启动:{prefix}"); Console.WriteLine("按任意键停止服务"); // 循环接收请求 while (true) { // 异步等待客户端请求 HttpListenerContext ctx = await listener.GetContextAsync(); // 新开线程处理请求,不阻塞下一个连接 _ = ProcessRequestAsync(ctx); } } catch (HttpListenerException ex) { Console.WriteLine($"启动失败:{ex.Message}"); Console.WriteLine("Windows需要管理员权限,或更换端口"); } finally { listener.Stop(); } } /// <summary>处理单个HTTP请求</summary> static async Task ProcessRequestAsync(HttpListenerContext ctx) { HttpListenerRequest req = ctx.Request; HttpListenerResponse resp = ctx.Response; try { // 1. 获取请求信息 string path = req.Url.AbsolutePath; string method = req.HttpMethod; Console.WriteLine($"[{DateTime.Now}] {method} {path}"); // 2. 路由分发 string responseText = path switch { "/" => "首页 Hello HttpListener", "/api/info" => "{\"msg\":\"接口数据\",\"code\":200}", _ => "404 Not Found" }; // 3. 设置响应头 if (path.StartsWith("/api/")) { resp.ContentType = "application/json;charset=utf-8"; } else { resp.ContentType = "text/html;charset=utf-8"; } resp.StatusCode = path == "/404" ? 404 : 200; byte[] buffer = Encoding.UTF8.GetBytes(responseText); resp.ContentLength64 = buffer.Length; // 4. 输出响应内容 await resp.OutputStream.WriteAsync(buffer); } catch (Exception ex) { Console.WriteLine($"请求异常:{ex.Message}"); resp.StatusCode = 500; } finally { resp.OutputStream.Close(); resp.Close(); } } }关键说明
- 权限问题
- Windows:监听
80、443需要管理员运行程序;自定义 8080 等端口一般无需权限 - Linux:监听 1024 以下端口需 sudo
- Windows:监听
- 跨域支持(加在响应头)
csharp
运行
resp.Headers.Add("Access-Control-Allow-Origin", "*"); resp.Headers.Add("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); if (method == "OPTIONS") { resp.StatusCode = 204; return; } - 读取 POST 表单 / JSON
using var reader = new StreamReader(req.InputStream, Encoding.UTF8); string postBody = await reader.ReadToEndAsync();方案 2:ASP.NET Core(工业级、推荐正式项目)
功能完整:路由、中间件、静态文件、鉴权、Swagger、HTTPS,适合生产服务。
最简控制台 Web 服务
- 创建项目:
dotnet new console dotnet add package Microsoft.AspNetCore- Program.cs
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // 路由 app.MapGet("/", () => "Hello ASP.NET Core Http Server"); app.MapGet("/api/data", () => Results.Json(new { code = 200, data = "测试接口" })); app.MapPost("/api/post", async (HttpRequest r) => { string body = await r.ReadAsStringAsync(); return Results.Ok(new { receive = body }); }); // 监听端口 app.Run("http://localhost:8080");运行:dotnet run,直接完整 HTTP 服务。
方案 3:Socket 原生手写 HTTP(底层原理学习,不推荐生产)
手动解析 TCP 流、构造 HTTP 报文,仅学习网络底层原理使用:
using System; using System.Net; using System.Net.Sockets; using System.Text; class RawSocketHttp { static void Main() { TcpListener server = new TcpListener(IPAddress.Loopback, 8080); server.Start(); Console.WriteLine("原生Socket HTTP服务 8080"); while (true) { TcpClient client = server.AcceptTcpClient(); _ = HandleClient(client); } } static async Task HandleClient(TcpClient client) { using client; var stream = client.GetStream(); byte[] buf = new byte[4096]; int len = await stream.ReadAsync(buf); string req = Encoding.UTF8.GetString(buf, 0, len); Console.WriteLine(req); // 构造HTTP响应报文 string html = "<h1>Raw Socket Http</h1>"; string response = $"HTTP/1.1 200 OK\r\nContent-Type:text/html;charset=utf-8\r\nContent-Length:{html.Length}\r\n\r\n{html}"; byte[] resBuf = Encoding.UTF8.GetBytes(response); await stream.WriteAsync(resBuf); } }缺点:需要手动处理分包、Cookie、编码、长连接、POST 解析、路由,开发成本极高。
三种方案选型对比
表格
| 方案 | 优点 | 缺点 | 使用场景 |
|---|---|---|---|
| HttpListener | 无第三方包、轻量、上手快 | 路由 / 中间件需自己封装 | 小工具、本地后台、简易接口 |
| ASP.NET Core | 成熟框架、路由 / 中间件 / ORM、高性能 | 依赖 AspNetCore 库 | WebAPI、后台服务、生产项目 |
| 原生 Socket | 完全可控底层 | 重复造轮子、易出 BUG | 学习 TCP/HTTP 协议,极少商用 |
常用扩展补充
1. HttpListener 开启 HTTPS
需要先给端口绑定证书(Windowsnetsh http/ Linux openssl),前缀改为https://localhost:8443/。
2. 并发优化
示例中使用_ = ProcessRequestAsync(ctx)实现多请求并发,不会串行阻塞。
3. 静态文件返回(HttpListener)
读取本地文件流写入resp.OutputStream,配合Content-Type区分图片、js、css。
项目中已验证过实例(复制过去即可用)
基于 Nancy 框架写的简易 HTTP 接口服务
Nancy 自动加载模块底层原理
- 当执行
new NancyHost(uri)创建宿主时,Nancy 内部会执行反射逻辑:- 扫描当前程序集(当前 exe/dll)里所有继承自 NancyModule 的公共类;
- 自动实例化这个
Module对象; - 自动读取构造函数里写的
Post("/", 处理委托),注册 HTTP 路由;
- 全程不需要你写任何调用、实例化代码,框架自动完成。
public class Module : NancyModule { private Lazy<ITestDataServer> SqlSugar => field ?? App.StaServices.Resolve<Lazy<ITestDataServer>>(); private Lazy<ILogger> Logger => field ?? App.StaServices.Resolve<Lazy<ILogger>>(); private object uplock = new object(); class RecvData { public string Code { get; set; } public TestInfo[] TestInfo { get; set; } } public Module() { base.Post("/", x => { RecvData[] recvDatas; try { string recvdata = Request.Body.AsString(); //.Value.Information("收到插入请求: {请求}", recvdata); recvDatas = JsonConvert.DeserializeObject<RecvData[]>(recvdata); } catch (Exception ex) { Logger.Value.Information($"收到CCD插入请求,数据解析失败,错误:{ex.Message}"); return $"Json 解析失败 {ex.Message}"; } if (recvDatas == null || recvDatas.Length == 0) return $"数据长度为空"; try { foreach (var item in recvDatas) { if (item.Code == null) { Logger.Value.Information($"收到CCD插入请求:条码为空!!!"); return "条码为空"; } if (item.TestInfo.Length == null || item.TestInfo.Length == 0) { Logger.Value.Information($"收到CCD插入请求:{item.Code},;数据为空!!!"); return "Value为 空"; } // item.TestInfo.Value = Encoding.UTF8.GetString(Encoding.Default.GetBytes(item.TestInfo.Value)); var testinfo = SqlSugar.Value.GetBatTestInfo(item.Code); if (testinfo == null) { Logger.Value.Information($"收到CCD插入请求:{item.Code},;条码错误!!!"); return "条码错误"; } Logger.Value.Information($"收到CCD插入请求:{item.Code},数据已插入数据库"); SqlSugar.Value.DirectAddTestInfo(testinfo, item.TestInfo); } Thread.Sleep(70); return "OK"; } catch (Exception ex) { Logger.Value.Information($"收到CCD插入请求处理错误:{ex.Message}"); return $"服务器错误 {ex.Message}"; } }); } }使用
public void Start() { Task.Run(() => { host = new NancyHost(new Uri("http://localhost:8088")); host.Start(); }); }完整封装 HttpListener HTTP 服务类
功能包含:跨域、OPTIONS 预检、GET/POST JSON 解析、路由分发、统一返回格式、404、异常捕获、UTF8 中文不乱码,无第三方依赖,.NET Framework /.NET Core /.NET 5/6/7/8 通用。
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace SimpleHttpServer { /// <summary> /// 轻量级HTTP服务封装类 /// 基于系统原生HttpListener实现,无第三方依赖 /// 内置功能:跨域CORS、OPTIONS预检处理、GET/POST JSON路由、统一返回格式、全局异常捕获、并发请求处理 /// </summary> public class SimpleHttpServer { /// <summary> /// 底层HTTP监听器实例 /// </summary> private readonly HttpListener _listener; /// <summary> /// 服务监听地址前缀集合,例如 http://localhost:8080/ /// </summary> private readonly string[] _prefixes; /// <summary> /// 路由字典 /// Key格式:请求方法:请求路径(例:GET:/hello、POST:/api/login) /// Value:当前路由对应的业务处理委托,接收请求对象,返回统一API结果 /// </summary> private readonly Dictionary<string, Func<HttpListenerRequest, Task<ApiResult>>> _routeMap = new(); /// <summary> /// 构造函数:初始化HTTP服务并绑定监听地址 /// </summary> /// <param name="prefixes">监听地址,多个地址可传入多个参数,地址末尾必须带 /</param> public SimpleHttpServer(params string[] prefixes) { _prefixes = prefixes; _listener = new HttpListener(); // 将所有监听地址注册到监听器 foreach (var pre in prefixes) { _listener.Prefixes.Add(pre); } } #region 路由注册方法 /// <summary> /// 注册GET类型接口路由 /// </summary> /// <param name="path">接口路径,例:/hello</param> /// <param name="handler">接口业务处理方法,传入请求对象,异步返回统一ApiResult</param> public void MapGet(string path, Func<HttpListenerRequest, Task<ApiResult>> handler) { string routeKey = $"GET:{path}"; _routeMap[routeKey] = handler; } /// <summary> /// 注册POST JSON类型接口路由 /// </summary> /// <param name="path">接口路径,例:/api/login</param> /// <param name="handler">接口业务处理方法,传入请求对象,异步返回统一ApiResult</param> public void MapPost(string path, Func<HttpListenerRequest, Task<ApiResult>> handler) { string routeKey = $"POST:{path}"; _routeMap[routeKey] = handler; } #endregion #region 服务启停控制 /// <summary> /// 启动HTTP监听服务 /// 启动后自动开启异步循环接收客户端请求,不会阻塞主线程 /// </summary> public void Start() { _listener.Start(); Console.WriteLine($"【服务启动成功】监听地址:{string.Join("、", _prefixes)}"); // 后台异步运行请求循环,不阻塞主线程 _ = RunLoopAsync(); } /// <summary> /// 停止HTTP监听服务,关闭所有连接 /// </summary> public void Stop() { if (_listener.IsListening) _listener.Stop(); Console.WriteLine("【服务已停止】"); } /// <summary> /// 后台循环任务:持续等待并接收客户端HTTP连接 /// 每收到一个请求,单独开异步任务处理,实现并发 /// </summary> private async Task RunLoopAsync() { try { // 服务运行期间持续接收请求 while (_listener.IsListening) { // 异步等待客户端连接,无请求时会挂起不占用CPU HttpListenerContext ctx = await _listener.GetContextAsync(); // 丢弃返回值,并行处理请求,不阻塞下一次接收 _ = HandleRequestAsync(ctx); } } catch (HttpListenerException ex) { Console.WriteLine($"【监听循环异常】{ex.Message}"); } } #endregion #region 请求核心处理逻辑 /// <summary> /// 处理单次客户端HTTP请求完整流程 /// 包含:跨域头写入、OPTIONS预检拦截、路由匹配、业务执行、JSON响应输出、异常兜底 /// </summary> /// <param name="ctx">单次请求上下文,包含请求Request和响应Response对象</param> private async Task HandleRequestAsync(HttpListenerContext ctx) { HttpListenerRequest req = ctx.Request; HttpListenerResponse resp = ctx.Response; try { // 1. 全局写入跨域响应头,解决前端浏览器跨域报错 resp.Headers.Add("Access-Control-Allow-Origin", "*"); resp.Headers.Add("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); resp.Headers.Add("Access-Control-Allow-Headers", "Content-Type"); // 2. 处理浏览器OPTIONS预检请求,直接返回204无内容 if (req.HttpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) { resp.StatusCode = 204; return; } // 3. 拼接路由匹配键:请求方法+请求路径 string path = req.Url.AbsolutePath; string routeKey = $"{req.HttpMethod}:{path}"; ApiResult result; // 4. 判断是否存在注册好的路由 if (_routeMap.TryGetValue(routeKey, out Func<HttpListenerRequest, Task<ApiResult>> handler)) { // 执行接口业务逻辑,拿到返回数据 result = await handler.Invoke(req); } else { // 无匹配路由,返回404接口不存在 result = new ApiResult(404, "请求接口不存在", null); } // 5. 统一以JSON格式返回数据,设置UTF8编码避免中文乱码 resp.ContentType = "application/json;charset=utf-8"; // 序列化返回实体,格式化输出方便调试 string jsonText = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); byte[] responseBuffer = Encoding.UTF8.GetBytes(jsonText); resp.ContentLength64 = responseBuffer.Length; // 将JSON字节写入响应输出流返回给客户端 await resp.OutputStream.WriteAsync(responseBuffer); } catch (Exception ex) { // 全局异常捕获:业务代码报错统一返回500错误JSON,不直接断开连接 ApiResult errorResult = new ApiResult(500, $"服务器内部异常:{ex.Message}", null); resp.ContentType = "application/json;charset=utf-8"; string errorJson = JsonSerializer.Serialize(errorResult); byte[] errorBuf = Encoding.UTF8.GetBytes(errorJson); resp.ContentLength64 = errorBuf.Length; await resp.OutputStream.WriteAsync(errorBuf); // 控制台打印异常详情方便排查 Console.WriteLine($"【请求异常】{ex}"); } finally { // 无论成功失败,最终关闭输出流与响应,释放资源 resp.OutputStream.Close(); resp.Close(); } } #endregion #region 工具静态方法 /// <summary> /// 读取POST请求体内的JSON字符串,并自动反序列化为指定实体对象 /// </summary> /// <typeparam name="T">需要反序列化的实体类型</typeparam> /// <param name="req">HTTP请求对象</param> /// <returns>反序列化后的实体;请求体为空时返回类型默认值</returns> public static async Task<T> ReadJsonBody<T>(HttpListenerRequest req) { // 使用流读取器读取请求原始流,UTF8编码解析 using StreamReader reader = new StreamReader(req.InputStream, Encoding.UTF8); string bodyText = await reader.ReadToEndAsync(); // 请求体为空直接返回默认值 if (string.IsNullOrWhiteSpace(bodyText)) return default; // JSON字符串转实体 return JsonSerializer.Deserialize<T>(bodyText); } #endregion } /// <summary> /// 全局统一API返回数据模型 /// 所有接口固定返回 Code、Msg、Data 三段式JSON结构 /// </summary> public class ApiResult { /// <summary> /// 业务状态码:200成功、404接口不存在、500服务器异常、自定义错误码-1等 /// </summary> public int Code { get; set; } /// <summary> /// 提示信息,用于前端展示文案 /// </summary> public string Msg { get; set; } /// <summary> /// 业务返回数据主体,无数据时为null /// </summary> public object Data { get; set; } /// <summary> /// 构造方法,完整赋值返回模型 /// </summary> /// <param name="code">状态码</param> /// <param name="msg">提示消息</param> /// <param name="data">返回数据</param> public ApiResult(int code, string msg, object data) { Code = code; Msg = msg; Data = data; } /// <summary> /// 快速构建成功返回对象 /// </summary> /// <param name="data">要返回的业务数据</param> /// <param name="msg">自定义成功提示,默认“操作成功”</param> /// <returns>Code=200的ApiResult实例</returns> public static ApiResult Success(object data, string msg = "操作成功") { return new ApiResult(200, msg, data); } /// <summary> /// 快速构建失败返回对象 /// </summary> /// <param name="msg">错误提示文案</param> /// <param name="code">自定义错误码,默认-1</param> /// <returns>对应错误码、无Data的ApiResult实例</returns> public static ApiResult Fail(string msg, int code = -1) { return new ApiResult(code, msg, null); } } #region 测试用实体类 /// <summary> /// 登录接口接收的POST JSON实体示例 /// </summary> public class LoginDto { /// <summary> /// 账号名 /// </summary> public string Username { get; set; } /// <summary> /// 密码 /// </summary> public string Password { get; set; } } #endregion /// <summary> /// 程序入口测试类,演示服务启动、路由注册、接口调用 /// </summary> class Program { /// <summary> /// 程序主入口方法 /// </summary> static async Task Main(string[] args) { // 实例化HTTP服务,监听本地8080端口 SimpleHttpServer server = new SimpleHttpServer("http://localhost:8080/"); // 注册GET测试接口 /hello server.MapGet("/hello", req => { var returnData = new { CurrentTime = DateTime.Now.ToString("HH:mm:ss"), Tip = "这是GET测试接口" }; return Task.FromResult(ApiResult.Success(returnData)); }); // 注册POST登录接口 /api/login server.MapPost("/api/login", async req => { // 读取请求JSON并转为LoginDto实体 LoginDto loginParam = await SimpleHttpServer.ReadJsonBody<LoginDto>(req); // 参数校验 if (loginParam == null || string.IsNullOrEmpty(loginParam.Username)) { return ApiResult.Fail("用户名不能为空"); } // 模拟登录成功返回Token var loginResult = new { Token = $"token_{Guid.NewGuid()}", UserName = loginParam.Username }; return ApiResult.Success(loginResult, "登录成功"); }); // 启动HTTP服务开始监听请求 server.Start(); Console.WriteLine("\n提示:按键盘任意键即可关闭服务"); Console.ReadKey(); // 停止服务释放端口 server.Stop(); } } }使用说明
1. 访问测试
- GET 请求:
http://localhost:8080/hello - POST JSON 请求(Postman/axios) 地址:
http://localhost:8080/api/login请求体:
json
{ "Username": "admin", "Password": "123456" }2. 内置能力清单
- 自动跨域:自带 CORS 跨域,前端浏览器直接调用无报错
- OPTIONS 预检自动处理:前端带自定义请求头不会 405
- 统一 JSON 返回结构:所有接口返回
{Code,Msg,Data} - POST JSON 读取工具方法:
ReadJsonBody<T>一键解析请求体 - 全局异常捕获:代码报错不会直接断开连接,返回 500 JSON
- 路由注册分离:
MapGet / MapPost清晰管理接口 - 并发处理:每个请求独立异步 Task,不会串行阻塞
3. 端口权限说明
- 监听 80、443 等 1024 以下端口:Windows 需要管理员运行程序,Linux 需要 sudo
- 8080、9090 高位端口:普通权限即可运行
4. 扩展方向(按需自己加)
- 文件上传:读取
req.InputStream二进制流保存本地 - 静态文件访问:判断路径是 .html/.js/.png,读取本地文件返回流
- URL 参数解析:
req.QueryString["key"]获取 GET 参数 - HTTPS 支持:构造
https://localhost:8443/前缀并绑定证书 - 日志持久化:把请求日志写入文本文件