×

用 C# Socket 从 0 手写一个 HTTP 服务器,揭开 Web 的神秘面纱

独孤求败 独孤求败 发表于2026-08-03 11:24:38 浏览58 评论0

抢沙发发表评论

前言

嗨,各位程序员小伙伴们,大家早上好啊!️

在现代软件开发中,我们习惯了使用 ASP.NET Core、Spring Boot 这样强大的框架。

只要敲下几行代码,配置好路由,一个功能完备的 Web 服务器就搭好了。

这种“开箱即用”的体验固然爽,但久而久之,Web 开发在我们眼里就成了一个“黑盒”。

你有没有想过:

当你在浏览器地址栏输入网址并按下回车的那一瞬间,到底发生了什么?

服务器又是如何精准地知道你想要哪个页面的?

其实,剥开那些复杂的框架外衣,Web 的本质无非就是 TCP 连接 + 文本协议

今天,老杨就带大家跳出舒适区,用最底层的 C# Socket,从 0 手写一个简易的 HTTP 服务器。

相信我,当你亲自构造出 HTTP 响应报文的那一刻,你对网络编程的理解绝对会上升到一个新的高度!

废话不多说,让我们打开 Visual Studio,开始这场“造轮子”之旅!

Step by Step 步骤

1. 创建控制台项目,命名为 SimpleHttpServer

2. 引入 Socket 相关的命名空间

打开 Program.cs 文件,在顶部添加:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

3. 编写主逻辑

实现一个循环,不断接受客户端连接,并为每个连接启动一个新线程处理,留意代码中的注释:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace SimpleHttpServer
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. 定义监听的 IP 地址和端口号
            //    这里监听本机所有 IP 地址(IPv4),端口 8080
            IPAddress ip = IPAddress.Any;
            int port = 8080;

            // 2. 创建 TcpListener 对象,开始监听
            TcpListener listener = new TcpListener(ip, port);
            listener.Start();
            Console.WriteLine($"HTTP 服务器已启动,监听 {ip}:{port}");
            Console.WriteLine("请在浏览器中访问 http://localhost:8080/");

            // 3. 无限循环,等待客户端连接
            while (true)
            {
                // 4. 接受一个客户端连接(阻塞直到有连接)
                TcpClient client = listener.AcceptTcpClient();
                Console.WriteLine($"客户端已连接:{client.Client.RemoteEndPoint}");

                // 5. 为每个客户端创建一个新线程处理,避免阻塞后续连接
                ThreadPool.QueueUserWorkItem(HandleClient, client);
            }
        }

        /// <summary>
        /// 处理单个客户端请求的方法(由线程池调用)
        /// </summary>
        /// <param name="state">传入的 TcpClient 对象</param>
        static void HandleClient(object state)
        {
            TcpClient client = (TcpClient)state;

            // 确保使用完释放资源
            using (client)
            {
                try
                {
                    // 获取网络流,用于读写数据
                    NetworkStream stream = client.GetStream();

                    // 1. 读取客户端发来的 HTTP 请求
                    //    先分配一个缓冲区,读取前 1024 字节,通常足够包含请求行和头部
                    byte[] buffer = new byte[1024];
                    int bytesRead = stream.Read(buffer, 0, buffer.Length);

                    // 将收到的字节转换为字符串(只取实际读取的部分)
                    string request = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                    Console.WriteLine("收到请求:");
                    Console.WriteLine(request.Substring(0, Math.Min(request.Length, 200))); // 只打印前200字符

                    // 2. 解析请求行(第一行)
                    //    请求行格式:METHOD /path HTTP/1.1
                    string firstLine = request.Substring(0, request.IndexOf("\r\n"));
                    string[] parts = firstLine.Split(' ');
                    string method = parts[0];      // GET / POST 等
                    string path = parts[1];         // 请求的资源路径,如 "/" 或 "/index.html"
                    Console.WriteLine($"请求方法:{method},路径:{path}");

                    // 3. 根据路径构造不同的响应内容
                    string responseBody;
                    if (path == "/")
                    {
                        responseBody = "<html><body><h1>欢迎来到简易 HTTP 服务器</h1><p>这是根路径</p></body></html>";
                    }
                    else if (path == "/about")
                    {
                        responseBody = "<html><body><h1>关于页面</h1><p>这是一个用 .NET Framework 4.8 Socket 实现的服务器</p></body></html>";
                    }
                    else
                    {
                        // 404 Not Found
                        responseBody = "<html><body><h1>404 - 页面未找到</h1></body></html>";
                    }

                    // 4. 构造 HTTP 响应报文
                    //    状态行:HTTP/1.1 200 OK(或 404 Not Found)
                    //    响应头:Content-Type, Content-Length, Connection 等
                    //    空行分隔头和体
                    string statusLine;
                    if (path == "/" || path == "/about")
                        statusLine = "HTTP/1.1 200 OK\r\n";
                    else
                        statusLine = "HTTP/1.1 404 Not Found\r\n";

                    string contentType = "Content-Type: text/html; charset=utf-8\r\n";
                    string contentLength = $"Content-Length: {Encoding.UTF8.GetByteCount(responseBody)}\r\n";
                    string connection = "Connection: close\r\n"// 短连接,处理完即关闭
                    string blankLine = "\r\n";

                    // 拼接完整的响应字符串
                    // 注意:响应头之后必须有一个空行
                    string responseString = statusLine + contentType + contentLength + connection + blankLine + responseBody;

                    // 5. 将响应字符串转为字节数组并发送给客户端
                    byte[] responseBytes = Encoding.UTF8.GetBytes(responseString);
                    stream.Write(responseBytes, 0, responseBytes.Length);
                    stream.Flush(); // 确保数据立即发送

                    Console.WriteLine("响应已发送,关闭连接。");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("处理客户端时出错:" + ex.Message);
                }
            }
        }
    }
}

4. 运行与测试

  1. 按 F5 启动程序,控制台会显示:

    HTTP 服务器已启动,监听 0.0.0.0:8080
    请在浏览器中访问 http://localhost:8080/
  2. 打开浏览器,访问 http://localhost:8080/,应该看到:

    欢迎来到简易 HTTP 服务器
    这是根路径

  3. 访问 http://localhost:8080/about,看到:

    关于页面
    这是一个用 .NET Framework 4.8 Socket 实现的服务器

  4. 访问其他任意路径(如 http://localhost:8080/test),看到 404 页面。

  5. 观察控制台输出,可以看到每次请求的信息。

总结

好了,今天的分享就到这里啦!

通过这几十行代码,我们其实已经抓住了 Web 开发的“灵魂”。

你会发现,所谓的 HTTP 协议,本质上就是客户端和服务器之间按照特定格式(请求行、请求头、空行、正文)进行的文本对话。

当然,我们今天写的只是一个“玩具级”的服务器。它没有处理并发异常、没有支持长连接(Keep-Alive)、也没有处理复杂的 POST 请求体。在真实的生产环境中,我们依然需要依赖 ASP.NET Core 或 Nginx 这样成熟的工业级组件。

但是,“知其然,更要知其所以然”

当你以后在排查复杂的网络问题,或者需要优化服务器性能时,今天这段底层 Socket 编程的经验,绝对会成为你最强有力的武器!

如果觉得这篇文章让你对网络编程有了新的认识,别忘了点个【赞与在看】哦,你的支持是我最大的动力!

我们下期技术分享再见!

如果你有更好的想法或建议,欢迎留言讨论!

关键词

C#、Socket、网络编程、Web底层


群贤毕至

访客