头像


🎬 个人主页艾莉丝努力练剑

专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平

🎬 艾莉丝的简介:

在这里插入图片描述



在这里插入图片描述


导入语

很多uu初学 HTTP 时,总觉得它是一个很 “高层” 的神秘协议,但实际上,HTTP 底层完完全全依托于 TCP,本质就是一套基于套接字的文本约定。这篇文章我们不搞抽象理论,直接从代码出发,把 TCP 服务器、HTTP 报文格式、请求解析、响应构建、序列化反序列化、URI 路径映射全部串起来,手搓一个可运行的 HTTP 服务器。学完你会真正明白:浏览器发的是什么、服务端怎么解、数据怎么回,HTTP 到底为什么可以被称为 “远程文件访问协议”。


大纲

在这里插入图片描述


1 整体认知:HTTP 底层依托 TCP

1.1 核心定位

HTTP 属于应用层协议,底层完全建立在 TCP 协议 之上,本质依旧是 TCP 套接字通信。

客户端无需手动编写,浏览器即为天然 HTTP 客户端,直接发送标准 HTTP 请求(如下图)。

在这里插入图片描述

1.2 代码复用与解耦

可直接复用原有 TCP 相关组件:

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

与网络计算器项目对比,仅上层应用协议不同,网络通信层完全一致,良好的解耦让协议替换变得简单。

1.3 整体代码模块

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

2 HttpServer 实现与 TCP 联动

2.1 类结构定义

#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;
};

2.2 核心运行逻辑

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

2.3 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;
}

3 HTTP 请求报文格式与解析

3.1 请求报文完整结构

在这里插入图片描述

请求行\r\n
请求报头 Key: Value\r\n
请求报头 Key: Value\r\n
...\r\n
空行\r\n
请求正文 DATA

3.1.1 请求行

格式:请求方法 空格 URI 空格 HTTP 版本 \r\n

示例:GET / HTTP/1.1

3.1.2 请求报头

多行 Key: Value 结构,描述客户端信息、连接方式、接收类型等。

3.1.3 空行

必须存在,用于分隔报头与正文,是判断报头是否完整的标志。

3.1.4 请求正文

可选内容,如登录信息、提交数据,长度由报头中的 Content-Length 字段决定。

3.2 对 HTTP 请求的本质理解

HTTP 请求本质是一串按固定规则组织的字节流(字符串),分行显示仅因 \r\n 分隔符,协议本身只识别分隔符。

请求行 + 请求报头 = 报头部分;请求正文 = 有效载荷部分,二者以空行分隔。

3.3 HTTP请求代码的编写:验证http请求

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

3.4 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

3.5 请求反序列化流程

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

3.6 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 响应报文格式与构建

4.1 响应报文完整结构

在这里插入图片描述

状态行\r\n
响应报头 Key: Value\r\n
响应报头 Key: Value\r\n
...\r\n
空行\r\n
响应正文 DATA

4.1.1 状态行

格式:HTTP 版本 空格 状态码 空格 状态描述\r\n

示例:HTTP/1.0 200 OK\r\n

4.1.2 响应报头

必带 Content-Length 字段,标识响应正文长度。

4.1.3 空行

分隔报头与正文,固定格式要求。

4.1.4 响应正文

浏览器展示的内容,如 HTML、文本、图片、视频等资源。

4.2 基本的应答格式

在这里插入图片描述

4.3 最简响应构建示例

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;

4.4 响应序列化流程

HttpResponse 中的版本、状态码、状态描述、响应报头、空行、响应正文,按 HTTP 响应格式拼接为字符串,返回给 TCP 层发送至客户端。


5 ~> 报文完整性与连接模式

5.1 报文完整性判断

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

5.2 连接模式

默认采用短连接,一次请求 - 响应完成后,服务端主动关闭 TCP 连接,简化逻辑;后续可扩展支持 keep-alive 长连接。

我复用了之前写的代码,这里把while(true)和判断条件里面的break去掉就行。


6 ~> 服务运行、测试与现象

6.1 启动方式

编译后执行命令:./httpserver 端口号(如 8080)

日志输出:socket 创建成功 → bind 成功 → listen 成功

6.2 测试方式

  • 浏览器访问:服务器 IP: 端口,自动发送 GET 请求。
  • telnet 连接:手动模拟 HTTP 请求。

6.3 测试现象

  • 服务端打印完整 HTTP 请求报文。
  • 未返回响应:浏览器提示 ERR EMPTY RESPONSE。
  • 正常返回响应:浏览器展示响应正文。
  • 浏览器会自动请求 /favicon.ico 图标文件

7 ~> HTTP 版本协商

请求携带客户端支持的 HTTP 版本,响应携带服务端支持的 HTTP 版本,双方通过版本号完成握手,决定协议特性的启用与兼容性。


8 ~> HTTP方法 && 核心本质

8.1 HTTP方法

在这里插入图片描述

其中最常用的就是GET方法和POST方法。

8.2 核心本质

HTTP 是纯文本格式的应用层协议, 请求与响应均为格式化字节流;

核心作用是远程资源本地化访问,请求 URI 对应服务器本地文件,通过网络将文件资源传输给浏览器。


9 ~> 总结 && 已经可以跑通的代码(浏览器无响应是因为云服务器端口号没有设置开放) && 后续完善方向

9.1 总结

本文从底层原理到代码实现,完整还原了 HTTP 基于 TCP 的运行模型、请求 / 响应报文结构、反序列化解析流程、URI 映射到本地资源的逻辑,以及服务端构建响应的完整过程。我们不仅看懂了 HTTP 报文,更用 C++ 实现了解析、封装、回调、IO 处理的全套流程,真正做到从协议格式到代码落地,一次性吃透 HTTP 的核心本质

9.1.1 补充问题:可以通过 … 访问到其他文件吗?

可以!浏览器的回退就是cd ..真正上网的时候就是靠点击的,点击来查看某个文件。

9.1.2 补充知识点:index.html

默认情况下,做一个网站,需要有一个首页:

在这里插入图片描述
这个index.html的命名是固定的!

9.2 已经可以跑的代码

  • 浏览器无响应是因为云服务器端口号没有设置开放;
  • telnet可以顺利连接

这里不多赘言,就把有变化或者说新增的代码放上来了。

9.2.1 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

9.2.2 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

9.2.3 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

9.2.4 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;
}

9.2.5 运行

9.2.5.1 客户端: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.
9.2.5.2 服务器端
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

9.3 后续完善

  • 实现完整 Unpack 解包逻辑,解决 TCP 粘包问题。

  • 完善 200、404、403、500 等状态码及对应响应。

  • 实现文件读取功能,根据 URI 返回 wwwroot 目录下真实的 HTML、CSS、JS、图片等资源。

  • 支持 POST、PUT 等更多请求方法。

  • 支持长连接、高并发优化。

  • 将端口、webroot 等配置参数文件化。

在这里插入图片描述


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

艾莉丝努力练剑

C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主


👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。
❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。
【收藏】 把核心知识点存好,在需要时随时查、随时用。
💬 【评论】 分享你的经验或疑问,评论区一起交流避坑!

不要忘记给博主“一键四连”哦!

“今日练剑达成!”

“技术之路难免有困惑,但同行的人会让前进更有方向。”

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾

【Linux网络】Linux 网络编程:HTTP(一)协议初识

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐