C# 实现 HTTP 服务器

目录

[C# 实现 HTTP 服务器三种方案](# 实现 HTTP 服务器三种方案)

[方案 1:内置 HttpListener(零第三方库,轻量,Windows/Linux 跨平台)](#方案 1:内置 HttpListener(零第三方库,轻量,Windows/Linux 跨平台))

完整示例

关键说明

[方案 2:ASP.NET Core(工业级、推荐正式项目)](#方案 2:ASP.NET Core(工业级、推荐正式项目))

[最简控制台 Web 服务](#最简控制台 Web 服务)

[方案 3:Socket 原生手写 HTTP(底层原理学习,不推荐生产)](#方案 3:Socket 原生手写 HTTP(底层原理学习,不推荐生产))

三种方案选型对比

常用扩展补充

[1. HttpListener 开启 HTTPS](#1. HttpListener 开启 HTTPS)

[2. 并发优化](#2. 并发优化)

[3. 静态文件返回(HttpListener)](#3. 静态文件返回(HttpListener))

项目中已验证过实例(复制过去即可用)

[Nancy 自动加载模块底层原理](#Nancy 自动加载模块底层原理)

使用

[完整封装 HttpListener HTTP 服务类](#完整封装 HttpListener HTTP 服务类)

使用说明

[1. 访问测试](#1. 访问测试)

[2. 内置能力清单](#2. 内置能力清单)

[3. 端口权限说明](#3. 端口权限说明)

[4. 扩展方向(按需自己加)](#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();
        }
    }
}

关键说明

  1. 权限问题

    • Windows:监听80443需要管理员运行程序;自定义 8080 等端口一般无需权限
    • Linux:监听 1024 以下端口需 sudo
  2. 跨域支持 (加在响应头)

    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;
    }
  3. 读取 POST 表单 / JSON

    using var reader = new StreamReader(req.InputStream, Encoding.UTF8);
    string postBody = await reader.ReadToEndAsync();

方案 2:ASP.NET Core(工业级、推荐正式项目)

功能完整:路由、中间件、静态文件、鉴权、Swagger、HTTPS,适合生产服务。

最简控制台 Web 服务

  1. 创建项目:

    dotnet new console
    dotnet add package Microsoft.AspNetCore

  2. 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

需要先给端口绑定证书(Windows netsh http / Linux openssl),前缀改为https://localhost:8443/

2. 并发优化

示例中使用_ = ProcessRequestAsync(ctx) 实现多请求并发,不会串行阻塞。

3. 静态文件返回(HttpListener)

读取本地文件流写入resp.OutputStream,配合Content-Type区分图片、js、css。


项目中已验证过实例(复制过去即可用)

基于 Nancy 框架写的简易 HTTP 接口服务

Nancy 自动加载模块底层原理
  1. 当执行 new NancyHost(uri) 创建宿主时,Nancy 内部会执行反射逻辑:

    • 扫描当前程序集(当前 exe/dll)里所有继承自 NancyModule 的公共类
    • 自动实例化这个 Module 对象;
    • 自动读取构造函数里写的 Post("/", 处理委托),注册 HTTP 路由;
  2. 全程不需要你写任何调用、实例化代码,框架自动完成。

    public class Module : NancyModule
    {
    private Lazy SqlSugar => field ?? App.StaServices.Resolve<Lazy>();

    复制代码
      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. 访问测试

  1. GET 请求:http://localhost:8080/hello
  2. POST JSON 请求(Postman/axios) 地址:http://localhost:8080/api/login 请求体:

json

复制代码
{
    "Username": "admin",
    "Password": "123456"
}

2. 内置能力清单

  1. 自动跨域:自带 CORS 跨域,前端浏览器直接调用无报错
  2. OPTIONS 预检自动处理:前端带自定义请求头不会 405
  3. 统一 JSON 返回结构 :所有接口返回 {Code,Msg,Data}
  4. POST JSON 读取工具方法ReadJsonBody<T> 一键解析请求体
  5. 全局异常捕获:代码报错不会直接断开连接,返回 500 JSON
  6. 路由注册分离MapGet / MapPost 清晰管理接口
  7. 并发处理:每个请求独立异步 Task,不会串行阻塞

3. 端口权限说明

  • 监听 80、443 等 1024 以下端口:Windows 需要管理员运行程序,Linux 需要 sudo
  • 8080、9090 高位端口:普通权限即可运行

4. 扩展方向(按需自己加)

  1. 文件上传:读取 req.InputStream 二进制流保存本地
  2. 静态文件访问:判断路径是 .html/.js/.png,读取本地文件返回流
  3. URL 参数解析:req.QueryString["key"] 获取 GET 参数
  4. HTTPS 支持:构造 https://localhost:8443/ 前缀并绑定证书
  5. 日志持久化:把请求日志写入文本文件