很多开发者初学 HTTP 时,总觉得它是一个抽象的“高层”协议。但实际上,HTTP 底层完全基于 TCP,本质就是一套基于套接字的文本约定。本文将从零开始,手搓一个可运行的 HTTP 服务器,带你彻底搞懂请求解析、响应构建、URI 映射等核心原理。

1. HTTP 底层依托 TCP:从云服务到本地代码

HTTP 属于应用层协议,底层完全建立在 TCP 协议之上。无论是云平台上的高并发服务,还是本地开发测试,其本质都是 TCP 套接字通信。浏览器作为天然 HTTP 客户端,自动发送标准 HTTP 请求,而服务端只需解析字节流即可。

代码复用与解耦是关键。我们可以直接复用原有 TCP 组件:

TcpServer.hpp
Socket.hpp
InetAddr.hpp
Logger.hpp(日志)
Mutex.hpp(互斥锁)

与网络计算器项目对比,仅上层应用协议不同,网络通信层完全一致。这种解耦设计让协议替换变得简单,也便于后续向云原生架构迁移。整体代码模块如下:

  • HttpProtocol.hpp:定义 HttpRequest、HttpResponse,负责反序列化(解析请求)与序列化(构建响应)
  • HttpServer.hpp:HTTP 服务器主类,基于 TCP 封装,提供请求处理入口
  • Main.cc:程序入口,完成服务器初始化、端口绑定、服务启动

2. HttpServer 实现与 TCP 联动

类结构定义是核心。TcpServer 仅负责网络 IO——创建套接字、绑定、监听、收发字节流;HttpServer 负责协议处理——解析请求、构建响应。二者通过回调函数联动,TCP 收到的字节流直接交给 HandlerHttpRequest 处理。

#pragma once
#include <iostream>
  #include <string>
    #include <memory>
      #include "TcpServer.hpp"
      #include "Logger.hpp"
      using namespace LogModule;
      class HttpServer {
      public:
      HttpServer(uint16_t port)
      : _port(port), _tsvr(std::make_unique<TcpServer>(port))
        {}
        std::string HandlerHttpRequest(std::string &streamstr);
        void Run() {
        _tsvr->Run([this](std::string &streamstr) -> std::string {
        return this->HandlerHttpRequest(streamstr);
        });
        }
        ~HttpServer(){}
        private:
        uint16_t _port;
        std::unique_ptr<TcpServer> _tsvr;
          };

Main.cc 入口实现简洁明了:

#include "HttpServer.hpp"
void Usage(std::string procname) {
std::cout << "Usage:" << procname << " ServerPort" << std::endl;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
Usage(argv[0]);
exit(1);
}
ENABLE_CONSOLE_LOG_STRATEGY();
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<HttpServer> hsvr = std::make_unique<HttpServer>(port);
  hsvr->Run();
  return 0;
  }

这种分层设计非常适用于云部署场景:你可以在云服务器上运行 TcpServer,而业务逻辑放在 HttpServer 中,便于独立扩展和运维。

3. HTTP 请求报文格式与解析

请求报文完整结构如下:

在这里插入图片描述
请求行\r\n
请求报头 Key: Value\r\n
请求报头 Key: Value\r\n
...\r\n
空行\r\n
请求正文 DATA

请求行格式为:请求方法 空格 URI 空格 HTTP 版本 \r\n,示例:GET / HTTP/1.1。请求报头是多行 Key: Value 结构,描述客户端信息、连接方式等。空行用于分隔报头与正文,是判断报头是否完整的标志。请求正文可选,长度由报头中的 Content-Length 字段决定。

HTTP 请求本质是一串按固定规则组织的字节流,分行显示仅因 \r\n 分隔符。请求行 + 请求报头 = 报头部分;请求正文 = 有效载荷部分。验证代码示例如下:

需要现场基于历史代码,先架构处⼀个基本的HTTP服务器,然后⽤浏览器进⾏验证
需要验证前端内容,直接AI即可

HttpRequest 类定义:

#ifndef _HTTP_PROTOCOL_HPP
#define _HTTP_PROTOCOL_HPP
#include <iostream>
  #include <string>
    #include <unordered_map>
      class HttpRequest {
      public:
      HttpRequest(){}
      ~HttpRequest(){}
      void Deserialize(std::string &streamstr);
      void ParseReqLine(std::string &status_line);
      int ReadOneline(std::string &streamstr, std::string *line);
      void SplitString(std::string &line, std::string *key, std::string *value, std::string sep);
      void PrintDebug();
      std::string operator[](const std::string &key) const;
      private:
      std::string _method;
      std::string _uri;
      std::string _http_version;
      std::unordered_map<std::string, std::string> _headerkv;
        std::string _blankline;
        std::string _body;
        std::string _path;
        };
        class HttpResponse {
        public:
        HttpResponse(){}
        ~HttpResponse(){}
        void Serialize(std::string *outstr);
        private:
        std::string _http_version;
        int _status_code;
        std::string _status_code_desc;
        std::unordered_map<std::string, std::string> _resp_headerkv;
          std::string _blankline;
          std::string _body;
          };
          #endif

反序列化流程:

  1. 按行读取:以 \r\n 为分隔符
  2. 解析请求行:通过 stringstream 按空格拆分出方法、URI、HTTP 版本
  3. 解析请求报头:循环读取非空行,以 : 拆分 Key-Value 存入 unordered_map
  4. 识别空行:读取到空行标志报头读取完毕
  5. 读取正文:根据 Content-Length 截取剩余字符串
  6. 路径映射:将 URI 映射为本地 wwwroot 目录下的路径,_path = webroot + _uri

operator [] 重载实现:

std::string HttpRequest::operator[](const std::string &key) const
{
if (key == "method") return _method;
else if (key == "uri") return _uri;
else if (key == "httpversion") return _http_version;
else if (key == "body") return _body;
else if (key == "path") return _path;
else
{
auto iter = _headerkv.find(key);
if (iter != _headerkv.end()) return iter->second;
}
return std::string();
}

未来在 Web 就可以访问 Request 的内容了(以 [] 的方式)。

4. HTTP 响应报文格式与构建

响应报文完整结构:

在这里插入图片描述
状态行\r\n
响应报头 Key: Value\r\n
响应报头 Key: Value\r\n
...\r\n
空行\r\n
响应正文 DATA

状态行格式:HTTP 版本 空格 状态码 空格 状态描述 \r\n,示例:HTTP/1.0 200 OK\r\n。响应报头必带 Content-Length 字段,标识正文长度。空行分隔报头与正文。响应正文是浏览器展示的内容。

最简响应构建示例:

std::string resp_status = "HTTP/1.0 200 OK\r\n";
std::string resp_content = "hello world, hello http!";
std::string cl = "Content-Length: " + std::to_string(resp_content.size()) + "\r\n";
std::string blankLine = "\r\n";
return resp_status + cl + blankLine + resp_content;

响应序列化流程:将 HttpResponse 中的版本、状态码、状态描述、响应报头、空行、响应正文,按 HTTP 响应格式拼接为字符串,返回给 TCP 层发送至客户端。这种模式非常适合云平台上的微服务架构,可以快速构建轻量级 HTTP 网关。

5. 报文完整性与连接模式

报文完整性判断:

  • 报头完整:读取到 \r\n 构成的空行
  • 正文完整:通过报头中 Content-Length 字段确定长度
  • TCP 粘包:本文做了简化处理,默认读取完整报文。粘包问题确实存在,但属于小概率事件,后续可通过 Unpack 解包解决

连接模式默认采用短连接,一次请求-响应完成后,服务端主动关闭 TCP 连接。将 while(true) 和判断条件中的 break 去掉即可支持长连接。对于云原生应用,建议后续扩展 keep-alive 长连接以提升性能。

6. 服务运行、测试与现象

启动方式:编译后执行 ./httpserver 端口号(如 8080),日志输出 socket 创建成功 → bind 成功 → listen 成功。测试方式包括浏览器访问 服务器 IP: 端口 自动发送 GET 请求,或使用 telnet 手动模拟 HTTP 请求。

测试现象:服务端打印完整 HTTP 请求报文;未返回响应时浏览器提示 ERR EMPTY RESPONSE;正常返回时浏览器展示响应正文。注意浏览器会自动请求 /favicon.ico 图标文件。HTTP 版本协商中,请求携带客户端支持的版本,响应携带服务端支持的版本,完成握手。

HTTP 方法中最常用的是 GETPOST。核心本质是:HTTP 是纯文本格式的应用层协议,请求与响应均为格式化字节流;核心作用是远程资源本地化访问,请求 URI 对应服务器本地文件,通过网络传输给浏览器。

[AFFILIATE_SLOT_1]

总结与后续完善

本文从底层原理到代码实现,完整还原了 HTTP 基于 TCP 的运行模型。我们不仅看懂了 HTTP 报文,更用 C++ 实现了解析、封装、回调、IO 处理的全套流程。补充知识点:浏览器的回退就是 cd ..,默认首页命名固定为 index.html

已经可以跑的代码:

  • HttpProtocol.hpp:
    #ifndef __HTTP_PROTOCOL_HPP
    #define __HTTP_PROTOCOL_HPP
    #include <iostream>
      #include <string>
        #include <unordered_map>   // key_value
          #include <sstream>
            // 日志
            #include "Logger.hpp"
            // 怎么提取?分隔符 --> 我要把分隔符暴露出来
            const std::string linesep = "\r\n";
            // 我需要一个报头的分隔符 
            const std::string headersep = ": ";
            // tuturoot
            const std::string webroot = "tuturoot";
            // 命名空间
            using namespace LogModule;
            // 示例:协议格式
            // 前3行:带长度前缀的自定义协议格式("长度"\r\nJSON内容)
            // "40"\r\n{"float":3.14, "int": 100, "s1": "helloworld", "s2": "hello bit"}
            // "40"\r\n{"float":3.14, "int": 100, "s1": "helloworld", "s2": "hello bit"}
            // "40"\r\n{"float":3.14, "int": 100, "s1": "helloworld", "s2": "hello bit"}
            // 最后1行:纯JSON格式,没有长度前缀
            // {"float":3.14, "int": 100, "s1": "helloworld", "s2": "hello bit"}
            // HTTP协议底层就是TCP协议
            // 也需要请求和应答
            class HttpRequest
            {
            private:
            // 把请求行读出来
            int ReadOneLine(std::string &streamstr,std::string *line)   // \r\n
            {
            // 1. 在输入字节流中查找行分隔符 "\r\n" 的位置
            auto pos = streamstr.find(linesep);
            // 2. 如果没找到行分隔符,说明数据不完整,返回空字符串
            if(pos == std::string::npos)    // 整个字符串里没有分隔符
            // return std::string();   // 返回值字符串是空串
            return -1;  // 参数要匹配
            // 3. 从字节流开头截取到分隔符之前,得到一行完整内容
            *line = streamstr.substr(0,pos); // 截止到pos位置
            // 4. 从字节流中删除已经读取的这一行(包括 "\r\n" 分隔符),
            //    方便下次调用时读取下一行
            streamstr.erase(0,pos + linesep.size());    // 删的时候删到换行符
            //  // 5. 返回读取到的这一行
            // return line;
            return line->size();    // >=(表示截取成功;等于0的时候说明读到空串)
            }
            // 把stringstream以文件的形式写到网络
            void ParseReqLine(std::string &status_line)
            {
            std::stringstream ss(status_line);
            ss >> _method >> _uri >> _http_version;
            // 比如说资源是:/a/b/c.html
            _path = webroot + _uri;    // tuturoot/a/b/c.html
            }
            // 打印
            void PrintDebug()
            {
            std::cout << _method << "#" << _uri << "#" << _http_version << std::endl;
            // 遍历headerkv
            for(auto &item : _header_kv)
            {
            std::cout << item.first << "->" << item.second << std::endl;
              }
              // 把空行打印出来
              std::cout << "blankline: " << _blankline << std::endl;  // 空行这里再加个换行
              // 我的body现在没什么东西,加点东西
              std::cout << "body: " << _body << std::endl;
              }
              // 单独设计一个分割字符串的接口
              void SplitString(std::string &line,std::string *key,std::string *value,std::string sep = headersep)
              {
              // headersep设置为“:”,就是以:为分隔符
              auto pos = line.find(sep);
              if(pos == std::string::npos)    // 没有返回空串
              return;
              *key = line.substr(0,pos);
              *value = line.substr(pos + sep.size());
              }
              public:
              HttpRequest()
              {}
              // 提供一个反序列化的接口
              void Deserialize(std::string &streamstr)
              {
              // 从网络当中传递一个字节流信息,然后转换成结构化信息
              // 1.读取第一行
              std::string status_line;
              int n = ReadOneLine(streamstr,&status_line);
              (void)n;
              // GET /a/b/c/d/e.html HTTP/1.1
              // GET / HTTP/1.1
              // 2.解析请求行
              ParseReqLine(status_line);
              PrintDebug();
              // 3.提取请求报头
              n = 0;  // 第一行已经被移除了
              do
              {
              std::string line;
              n = ReadOneLine(streamstr,&line);
              if(n > 0)
              {
              std::string key,value;
              SplitString(line,&key,&value,headersep);
              // 都不为空说明取到了合法的key_value值
              if(!key.empty() && !value.empty())
              {
              _header_kv[key] = value;
              }
              }
              else if(n == 0)
              {
              _blankline = "\r\n";
              break;
              }
              else
              {
              LOG(LogLevel::DEBUG) << "bug???";
              break;
              }
              }while(n > 0);
              // 请求的正文是否存在
              if(_header_kv.find("Content-Length") != _header_kv.end())
              {
              // 不存在就什么都不做
              int len = std::stoi(_header_kv["Content-Length"]);
              // 前面都已经去掉了,这里就读到body了
              _body = streamstr.substr(0,len);
              // _body = streamstr;
              }
              PrintDebug();
              }
              // 在Request里面做一个运算符重载(只读),重载一个中括号,因为原来获取URI的方式不太优雅
              // 这里引用不能引用常量字符串,把const和&去掉
              std::string operator [](const std::string &key) const   // 未来提供一个key就行
              {
              // 把一堆GET方法,封装从接口,以只读方式访问
              if(key == "method")
              return _method;
              else if(key == "uri")
              return _uri;
              else if(key == "httpversion")
              return _http_version;
              else if(key == "body")
              return _body;
              // else if(key == "uri")
              // return _uri;
              // 不要请求uri了,在当前代码中还存在像/a/b/c.html这样的特殊情况
              else if(key == "path")
              return _path;
              else{
              auto iter = _header_kv.find(key);
              if(iter != _header_kv.end())
              return iter->second;
              }
              return std::string();   // 返回一个空串
              }
              ~HttpRequest()
              {}
              private:
              // 请求方法、URI、HTTP版本、Key:[空格]Value,还有空行
              std::string _method;
              std::string _uri;   // /a/b/c.html   /(特殊情况)
              std::string _http_version;
              std::unordered_map<std::string,std::string> _header_kv;
                std::string _blankline;
                // 除了请求还要有一个请求的正文
                std::string _body;
                private:
                std::string _path;
                };
                // 处理请求,构建应答
                class HttpResponse
                {
                public:
                HttpResponse()  {}
                ~HttpResponse() {}
                void Serialize(std::string *outstr)
                {}
                private:
                // 结构化字段
                std::string _http_version;                // HTTP协议版本,如 "HTTP/1.1"
                int _status_code;                         // 状态码,如 200(成功)、404(未找到)、500(服务器错误)
                std::string _status_code_desc;            // 状态码描述,如 "OK"、"Not Found"、"Internal Server Error"
                std::unordered_map<std::string,std::string> _resp_headerkv; // 响应头键值对,如 Content-Type、Content-Length
                  std::string _blankline;                   // 空行,用于分隔响应头和响应体
                  std::string _body;                        // 正文:响应体,如网页内容、JSON数据等
                  };
                  #endif
  • HttpServer.hpp:
    #ifndef __HTTPSERVER_HPP
    #define __HTTPSERVER_HPP
    // Http就不用写客户端了,因为是和浏览器连接,浏览器就是客户端
    #include <iostream>
      #include <string>
        #include <memory>
          #include "TcpServer.hpp"
          #include "Logger.hpp"
          #include "HttpProtocol.hpp"
          // 命名空间包一下
          using namespace LogModule;
          class HttpServer
          {
          public:
          // ===== 原来的代码(已注释)=====
          HttpServer(uint16_t port)
          : _port(port),
          _tsvr(std::make_unique<TcpServer>(port))
            {}
            // 不想这样写就在类里面写这样一个接口
            std::string HandlerHttpRequest(std::string &streamstr)
            {
            // 1.报文完整性这里不管啦,不是不存在粘包问题,粘包问题是个小概率事件,这里先不考虑,默认报文是完整的
            // 2.反序列化
            HttpRequest httpreq;    // 服务端接收到的是一个请求
            httpreq.Deserialize(streamstr);
            std::cout << "HandlerHttpRequest: " << httpreq["method"] << std::endl;
            std::cout << "HandlerHttpRequest: " << httpreq["uri"] << std::endl;
            std::cout << "HandlerHttpRequest: " << httpreq["httpversion"] << std::endl;
            std::string target = httpreq["path"]; // /a/b/c.html
            std::cout << "http req: " << target << std::endl;
            // 3. 处理请求,构建应答:httpreq --> httpresp
            HttpResponse httpresp;
            // 4.应答序列化:把结构化的字段变成字符串
            std::string httprespstr;
            httpresp.Serialize(&httprespstr);
            // 5.返回:返回应答的序列化字段
            return httprespstr;
            // 函数会回调到这里
            // 这样main函数就简单了,到时候直接包一下头文件HttpServer.hpp
            // 打印日志
            // LOG(LogLevel::DEBUG) << "http request:\r\n";    // 将来把回调的HTTP请求从哪来的?
            // // LOG(LogLevel::DEBUG) << "http request:\r\n" << streamstr;
            // LOG(LogLevel::DEBUG) << "##################################\r\n";
            // LOG(LogLevel::DEBUG) << streamstr;
            // LOG(LogLevel::DEBUG) << "##################################\r\n";
            // =============> 上面第5步返回:就是把这些结构化字段按照这种格式发回去,回到TcpServer <=============
            // // ----> 这个应答测试可以,但是太草率了 <----
            // // 状态行
            // std::string resp_status = "HTTP/1.0 200 OK\r\n";    // HTTP版本 状态码 状态码描述
            // // 响应正文
            // std::string resp_content = "hello world,hello http!";
            // // 响应报头
            // std::string c1 = "Content-Length: " + std::to_string(resp_content.size()) + "\r\n";
            // // 空行
            // std::string blankline = "\r\n";
            // // 怎么返回应答报文,直接拼接一个字符串
            // return resp_status + c1 + blankline + resp_content; // 返回之后就会回到TcpServer的回调当中
            }
            // 回调有了
            void Run()
            {
            // _tsvr->Run();
            // 写一个lambda
            _tsvr->Run([this](std::string &streamstr)->std::string{
            return this->HandlerHttpRequest(streamstr);
            });
            }
            ~HttpServer()
            {}
            private:
            // 端口号
            uint16_t _port;
            // 这里要有一个协议字段
            // std::unique_ptr<HttpProtocol> _protocol; // 不想这样写就在类里面写一个接口
              std::unique_ptr<TcpServer> _tsvr;
                };
                #endif
  • TcpServer.hpp(短连接):
    #ifndef __TcpServer_HPP
    #define __TcpServer_HPP
    #include <iostream>
      #include <string>
        #include "Socket.hpp"
        #include <memory>
          #include <unistd.h>
            #include <signal.h>
              #include "Logger.hpp"
              #include <functional>
                // 处理客户端请求的回调函数类型
                // 参数: 接收到的客户端数据 (inbuffer)
                // 返回值: 要发送给客户端的响应数据 (outbuffer)
                using handler_t = std::function<std::string(std::string &)>;
                // TCP服务器类 - 提供多进程并发的TCP网络服务
                // 核心功能:
                //   1. 在指定端口创建并监听套接字
                //   2. 接收客户端连接请求
                //   3. 为每个客户端fork子进程进行独立处理
                //   4. 通过回调函数实现业务逻辑与网络层的解耦
                class TcpServer
                {
                public:
                TcpServer(uint16_t port, handler_t handler = nullptr)
                : _port(port),
                _listensockfd(std::make_unique<TcpSocket>()),
                  _isrunning(false),
                  _handlerstream(handler)
                  {
                  _listensockfd->BuildSocketMethod(_port);    // 模版方法模式
                  }
                  // =================
                  // 启动TCP服务器,开始监听并接受客户端连接
                  // 功能: 
                  //   1. 进入无限循环,持续等待客户端连接
                  //   2. 每次接收到新连接时,fork子进程处理该客户端的IO
                  //   3. 父进程继续监听下一个连接
                  void Run(handler_t handler)
                  {
                  _handlerstream = handler;
                  _isrunning = true;
                  signal(SIGCHLD,SIG_IGN);
                  while(_isrunning)
                  {
                  InetAddr clientaddr;
                  auto sockfd = _listensockfd->Accept(&clientaddr);    // 1. clientaddress 2. 得到socket
                  if(!sockfd)
                  {
                  LOG(LogLevel::WARNING) << "Accepter error";
                  continue;
                  }
                  LOG(LogLevel::DEBUG) << "client addr: " << clientaddr.StringAddress() << " socket: " << sockfd->Socketfd();
                    // 处理链接的问题
                    if(fork() == 0)
                    {
                    _listensockfd->Close();
                    // 子进程
                    HanderIO(sockfd, clientaddr);
                    exit(0);
                    }
                    // 父进程
                    sockfd->Close();
                    }
                    }
                    // 处理已连接客户端的IO操作
                    // 参数1: 与客户端建立连接的socket对象
                    // 参数2: 客户端的地址信息
                    // 功能: 循环接收客户端数据
                    void HanderIO(std::shared_ptr<Socket> sockfd, InetAddr &clientaddr)
                      {
                      std::string inbuffer;
                      // 改成短连接
                      int n = sockfd->Recv(&inbuffer);    // 假设我们默认读到的就是完整的报文
                      if(n < 0)
                      {
                      LOG(LogLevel::WARNING) << "recv error";
                      }
                      if(n == 0)
                      {
                      LOG(LogLevel::INFO) << "client quit: " << clientaddr.StringAddress() << " sockfd: " << sockfd->Socketfd();
                        }
                        LOG(LogLevel::DEBUG) << inbuffer;
                        // 1. 分析inbuffer保证报文的完整性
                        // 2. a. 完整: 提取,处理 b.不完整:什么都不做
                        // 3:这个工作谁做?协议来做!
                        // 谁不做??TcpServer
                        std::string outbuffer;
                        if(_handlerstream)
                        outbuffer = _handlerstream(inbuffer);   // 回调函数会调出去,也会再回来!
                        if(!outbuffer.empty())
                        sockfd->Send(outbuffer);
                        sockfd->Close();
                        }
                        ~TcpServer()
                        {
                        // 析构
                        }
                        private:
                        int _port;
                        std::unique_ptr<Socket> _listensockfd;
                          bool _isrunning;
                          handler_t _handlerstream;
                          };
                          #endif
  • Main.cc:
    #include "HttpServer.hpp"   // 先把头文件包好,头文件只要包含这个就可以了,日志在这个头文件那里就已经包含了
    void Usage(std::string procname)
    {
    std::cout << "Usage: " << procname << " Serverport" << std::endl;
    }
    int main(int argc,char *argv[])
    {
    // 输出一个手册
    if(argc != 2)
    {
    Usage(argv[0]);
    exit(0);
    }
    // 控制台策略
    ENABLE_CONSOLE_LOG_STRATEGY();
    // 解析命令行参数,将字符串端口号转换为uint16_t整数类型
    uint16_t port = std::stoi(argv[1]);
    // 创建HttpServer智能指针对象,初始化服务器监听端口
    std::unique_ptr<HttpServer> hsvr = std::make_unique<HttpServer>(port);
      // 启动HTTP服务器,开始接收并处理客户端请求
      hsvr->Run();
      return 0;
      }
  • 客户端 telnet 连接:
    alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/HTTP/HttpServer_v2$ telnet 127.0.0.1 8088
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    aaaa
    Connection closed by foreign host.
  • 服务器端:
    alice@VM-4-17-ubuntu:~/Alice/bit_118_ubuntu/HTTP/HttpServer_v2$ ./httpserver 8088
    [2026-05-16 01:19:53][INFO][2221403][Socket.hpp][92]- create socket success
    [2026-05-16 01:19:53][INFO][2221403][Socket.hpp][104]- bind socket success
    [2026-05-16 01:19:53][INFO][2221403][Socket.hpp][115]- listen socket success
    [2026-05-16 01:20:24][DEBUG][2221403][TcpServer.hpp][68]- client addr: [127.0.0.1:39758] socket: 4
    [2026-05-16 01:28:07][DEBUG][2222050][TcpServer.hpp][101]- aaaa
    aaaa##
    blankline:
    body:
    [2026-05-16 01:28:07][DEBUG][2222050][HttpProtocol.hpp][132]- bug???
    aaaa##
    blankline:
    body:
    HandlerHttpRequest: aaaa
    HandlerHttpRequest:
    HandlerHttpRequest:
    http req: tuturoot

后续完善方向:实现完整 Unpack 解包逻辑解决 TCP 粘包问题;完善 200、404、403、500 等状态码;实现文件读取功能,根据 URI 返回 wwwroot 目录下的真实资源;支持 POST、PUT 等方法;支持长连接和高并发优化;将端口、webroot 等配置参数文件化,便于云迁移和云部署。

[AFFILIATE_SLOT_2]

最终你会发现,HTTP 协议并不神秘——它只是 TCP 上的一层文本约定。掌握它,你就掌握了云服务通信的基础。希望本文对学习 Linux 网络编程的读者有所帮助,欢迎一键四连支持!