C#实现HTTP服务器的三种方案
作者:_Csharp
C#实现HTTP服务的三种方案:HttpListener轻量跨平台、ASP.NETCore功能完整、Socket底层原理学习,HttpListener适合小型接口服务,ASP.NETCore适用于生产环境,Socket仅限学习,每种方案优缺点对比,示例代码详尽解析,需要的朋友可以参考下
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:监听
- 跨域支持(加在响应头)
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.NETCore(工业级、推荐正式项目)
功能完整:路由、中间件、静态文件、鉴权、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
需要先给端口绑定证书(Windows netsh 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请求体:
{
"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/前缀并绑定证书 - 日志持久化:把请求日志写入文本文件
以上就是C#实现HTTP服务器的三种方案的详细内容,更多关于C#实现HTTP服务器的资料请关注脚本之家其它相关文章!
