应用层协议 HTTP 讲解实战:从0实现HTTP 服务器

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录

  • 一:🔥 HTTP 协议
    • 🦋 认识 URL
    • 🦋 urlencode 和 urldecode
  • 二:🔥 HTTP 协议请求与响应格式
    • 🦋 HTTP 请求
    • 🦋 HTTP 响应
    • 🦋 HTTP 的方法
    • 🦋 HTTP 的状态码
    • 🦋 HTTP 常见 Header
    • 🦋 关于 connection 报头
  • 三:🔥 实现 HTTP 服务器
    • 🦋 完整代码移步我的Gitee仓库
  • 四:🔥 附录
    • 🦋 HTTP 历史及版本核心技术与时代背景
    • 🦋 HTTP/0.9
    • 🦋 HTTP/1.0
    • 🦋 HTTP/1.1
    • 🦋 HTTP/2.0
    • 🦋 HTTP/3.0
  • 五:🔥 共勉

一:🔥 HTTP 协议

🧑‍💻 虽然我们说, 应用层协议是我们程序猿自己定的,但实际上, 已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用. HTTP(超文本传输协议) 就是其中之一。

🧑‍💻 在互联网世界中, HTTP(HyperText Transfer Protocol, 超文本传输协议) 是一个至关重要的协议。 它定义了客户端(如浏览器) 与服务器之间如何通信, 以及交换或传输超文本(如 HTML 文档) 。

🧑‍💻 HTTP 协议是客户端与服务器之间通信的基础。 客户端通过 HTTP 协议向服务器发送请求, 服务器收到请求后处理并返回响应。 HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。

🦋 认识 URL

📚 平时我们俗称的 “网址” 其实就是说的 URL

在这里插入图片描述

🦋 urlencode 和 urldecode

📚 像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现.

比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

📚 转义的规则如下:

  • 将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%, 编码成 %XY 格式

📚 例如:
在这里插入图片描述
🧑‍💻 “+” 被转义成了 “%2B” urldecode 就是 urlencode 的逆过程;
urlencode 工具

二:🔥 HTTP 协议请求与响应格式

🦋 HTTP 请求

在这里插入图片描述

  • 首行: [方法] + [uri] + [版本]
  • Header: 请求报头, 冒号分割的键值对; 每组属性之间使用 \r\n 分隔; 遇到空行表示 Header 部分结束
  • Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果 Body 存在, 则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度;
    在这里插入图片描述

🦋 HTTP 响应

在这里插入图片描述

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 响应报头, 冒号分割的键值对;每组属性之间使用 \r\n 分隔;遇到空行表示 Header 部分结束
  • Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果 Body 存在, 则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度; 如果服务器返回了一个 html 页面, 那么 html 页面内容就是在 body 中.
    在这里插入图片描述

🦋 HTTP 的方法

在这里插入图片描述
📚 其中最常用的就是 GET 方法和 POST 方法.

  1. GET 方法(重点)
  • 用途: 用于请求 URL 指定的资源。
  • 示例: GET /index.html HTTP/1.1
  • 特性: 指定资源经服务器端解析后返回响应内容。
  • form 表单: https://www.runoob.com/html/html-forms.html
  1. POST 方法(重点)
  • 用途: 用于传输实体的主体, 通常用于提交表单数据。
  • 示例: POST /submit.cgi HTTP/1.1
  • 特性: 可以发送大量的数据给服务器, 并且数据包含在请求体中。
  • form 表单: https://www.runoob.com/html/html-forms.htm
  1. PUT 方法(不常用)
  • 用途: 用于传输文件, 将请求报文主体中的文件保存到请求 URL 指定的位置。
  • 示例: PUT /example.html HTTP/1.1
  • 特性: 不太常用, 但在某些情况下, 如 RESTful API 中, 用于更新资源。
  1. HEAD 方法
  • 用途: 与 GET 方法类似, 但不返回报文主体部分, 仅返回响应头。
  • 示例: HEAD /index.html HTTP/1.1
  • 特性: 用于确认 URL 的有效性及资源更新的日期时间等。
  1. DELETE 方法(不常用)
  • 用途: 用于删除文件, 是 PUT 的相反方法。
  • 示例: DELETE /example.html HTTP/1.1
  • 特性: 按请求 URL 删除指定的资源。
  1. OPTIONS 方法
  • 用途: 用于查询针对请求 URL 指定的资源支持的方法。
  • 示例: OPTIONS * HTTP/1.1
  • 特性: 返回允许的方法, 如 GET、 POST 等。

🦋 HTTP 的状态码

在这里插入图片描述
📚 最常见的状态码, 比如 200(OK)404(Not Found)403(Forbidden)302(Redirect, 重定向)504(Bad Gateway)

状态码含义应用样例
100Continue上传大文件时, 服务器告诉客户端可以继续上传
200OK访问网站首页, 服务器返回网页内容
201Created发布新文章, 服务器返回文章创建成功的信息
204No Content删除文章后, 服务器返回“无内容”表示操作成功
301Moved Permanently网站换域名后, 自动跳转到新域名; 搜索引擎更新网站链接时使用
302Found 或 See Other用户登录成功后, 重定向到用户首页
304Not Modified浏览器缓存机制, 对未修改的资源返回304 状态码
400Bad Request填写表单时, 格式不正确导致提交失败
401Unauthorized访问需要登录的页面时, 未登录或认证失败
403Forbidden尝试访问你没有权限查看的页面
404Not Found访问不存在的网页链接
500Internal Server Error服务器崩溃或数据库错误导致页面无法加载
502Bad Gateway使用代理服务器时, 代理服务器无法从上游服务器获取有效响应
503Service Unavailable服务器维护或过载, 暂时无法处理请求

📚 以下是仅包含重定向相关状态码的表格

状态码含义是否为临时重定向应用样例
301Moved Permanently否(永久重定向)网站换域名后, 自动跳转到新域名;搜索引擎更新网站链接时使用
302Found 或 See Other是(临时重定向)用户登录成功后,重定向到用户首页
307Temporary Redirect是(临时重定向)临时重定向资源到新的位置(较少使用)
308Permanent Redirect否(永久重定向)永久重定向资源到新的位置(较少使用)

关于重定向的验证, 以 301 为代表:

HTTP 状态码 301(永久重定向) 和 302(临时重定向) 都依赖 Location 选项。 以下是关于两者依赖 Location 选项的详细说明:

HTTP 状态码 301(永久重定向) :

  • 当服务器返回 HTTP 301 状态码时, 表示请求的资源已经被永久移动到新的位置。
  • 在这种情况下, 服务器会在响应中添加一个 Location 头部, 用于指定资源的新位置。 这个 Location 头部包含了新的 URL 地址, 浏览器会自动重定向到该地址。
  • 例如, 在 HTTP 响应中, 可能会看到类似于以下的头部信息:
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n

HTTP 状态码 302(临时重定向) :

  • 当服务器返回 HTTP 302 状态码时, 表示请求的资源临时被移动到新的位置。
  • 同样地, 服务器也会在响应中添加一个 Location 头部来指定资源的新位置。 浏览器会暂时使用新的 URL 进行后续的请求, 但不会缓存这个重定向。
  • 例如, 在 HTTP 响应中, 可能会看到类似于以下的头部信息:
HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n

🦁 总结: 无论是 HTTP 301 还是 HTTP 302 重定向, 都需要依赖 Location 选项来指定资源的新位置。 这个 Location 选项是一个标准的 HTTP 响应头部, 用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

🦋 HTTP 常见 Header

  • Content-Type: 数据类型(text/html 等)
  • Content-Length: Body 的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

🦋 关于 connection 报头

🧑‍💻 HTTP 中的 Connection 字段是 HTTP 报文头的一部分, 它主要用于控制和管理客户端与服务器之间的连接状态

核心作用

  • 管理持久连接: Connection 字段还用于管理持久连接(也称为长连接) 。 持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接, 以便在同一个连接上发送多个请求和接收多个响应。

持久连接(长连接)

  • HTTP/1.1: 在 HTTP/1.1 协议中, 默认使用持久连接。 当客户端和服务器都不明确指定关闭连接时, 连接将保持打开状态, 以便后续的请求和响应可以复用同一个连接。
  • HTTP/1.0: 在 HTTP/1.0 协议中, 默认连接是非持久的。 如果希望在 HTTP/1.0 上实现持久连接, 需要在请求头中显式设置 Connection: keep-alive。

语法格式

  • Connection: keep-alive: 表示希望保持连接以复用 TCP 连接。
  • Connection: close: 表示请求/响应完成后, 应该关闭 TCP 连接

🧑‍💻 下面附上一张关于 HTTP 常见 header 的表格

字段名含义样例
Accept客户端可接受的响应内容类型Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
AcceptEncoding客户端支持的数据压缩格式Accept-Encoding: gzip, deflate, br
AcceptLanguage客户端可接受的语言类型Accept-Language: zhCN,zh;q=0.9,en;q=0.8
Host请求的主机名和端口号Host: www.example.com:8080
User-Agent客户端的软件环境信息User-Agent: Mozilla/5.0 (Windows NT10.0; Win64; x64)AppleWebKit/537.36 (KHTML, likeGecko) Chrome/91.0.4472.124Safari/537.36
Cookie客户端发送给服务器的 HTTP cookie 信息Cookie: session_id=abcdefg12345;user_id=123
Referer请求的来源 URLReferer: http://www.example.com/previous_page.html
Content-Type实体主体的媒体类型Content-Type: application/x-wwwform-urlencoded (对于表单提交) 或Content-Type: application/json (对于JSON 数据)
Content-Length实体主体的字节大小Content-Length: 150
Authorization认证信息, 如用户名和密码Authorization: BasicQWxhZGRpbjpvcGVuIHNlc2FtZQ== (Base64编码后的用户名:密码)
Cache-Control缓存控制指令请求时: Cache-Control: no-cache 或Cache-Control: max-age=3600; 响应时:Cache-Control: public, maxage=3600
Connection请求完后是关闭还是保持连接Connection: keep-alive 或Connection: close
Date请求或响应的日期和时间Date: Wed, 21 Oct 2023 07:28:00 GMT
Location重定向的目标URL(与 3xx 状态码配合使用)Location:http://www.example.com/new_location.html (与 302 状态码配合使用)
Server服务器类型Server: Apache/2.4.41 (Unix)Last-Modified 资源的最后修改时间Last-Modified: Wed, 21 Oct 202307:20:00 GMT
ETag资源的唯一标识符, 用于缓存ETag: “3f80f-1b6-5f4e2512a4100”
Expires响应过期的日期和时间Expires: Wed, 21 Oct 2023 08:28:00 GMT

三:🔥 实现 HTTP 服务器

🧑‍💻 设计模式:使用 模板方法模式 封装套接字 socket

🦁 模板方法模式是一种行为型设计模式,它在一个抽象类中定义了一个算法(业务逻辑)的骨架,具体步骤的实现由子类提供。它通过将算法的不变部分放在抽象类中,可变部分放在子类中,达到代码复用和扩展的目的。

  • 复用:所有子类可以直接复用父类提供的模板方法,即上面提到的不变的部分。
  • 扩展: 子类可以通过模板定义的一些扩展点就行不同的定制化实现。

模板方法模式的特点:

  1. 算法骨架 : 在基类中定义一个算法的固定执行步骤(模板方法),具体实现步骤交给子类完成。
  2. 复用代码: 子类复用基类中定义的通用逻辑,仅需实现特定步骤。
  3. 遵循开闭原则: 基类的骨架逻辑对扩展开放,对修改关闭。

一般用在什么场景?

  1. 定义算法骨架: 有一个固定的流程,但某些步骤需要根据具体情况自定义
  2. 复用公共逻辑: 多个子类共享相同的算法结构,仅需重写特定步骤。
  3. 控制执行顺序: 需要对子类执行方法的顺序进行控制时,

典型场景:

  • 数据处理流程(如读取数据、处理数据、输出结果)
  • Web 请求处理 (如解析请求、处理逻辑、返回响应)

📦 socket.hpp

#pragma once

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"

namespace SocketModule
{
    using namespace LogModule;

    class Socket;
    using SockPtr = std::shared_ptr<Socket>;

    // 基类,规定创建socket的方法
    // 提供一个/若干个/固定模式的socket方法
    class Socket
    {
    public:
        virtual void SocketOrDie() = 0;
        virtual void SetSocketOpt() = 0;
        virtual bool BindOrDie(int port) = 0;
        virtual bool ListenOrDie() = 0;
        virtual SockPtr Accepter(InetAddr *client) = 0;
        virtual void Close() = 0;
        virtual int Recv(std::string *out) = 0;
        virtual int Send(const std::string &in) = 0;
        virtual int Fd() = 0;
        virtual ~Socket() = default;
        // 其他方法,需要的时候再加

        // 提供一个创建 listensockfd 的固定套路
        // 设计模式:模板方法模式
        void BuildTcpSocketMethod(int port)
        {
            SocketOrDie();
            SetSocketOpt();
            BindOrDie(port);
            ListenOrDie();
        }

        // #ifdef WIN
        //     // 提供一个创建 listensockfd 的固定套路
        //     void BuildTcpSocket()
        //     {
        //         SocketOrDie();
        //         SetSocketOpt();
        //         BindOrDie();
        //         ListenOrDie();
        //     }

        // #else // Linux

        // #endif
        // 提供一个创建 listensockfd 的固定套路
        //     void BuildTcpSocket()
        //     {
        //         SocketOrDie();
        //         SetSocketOpt();
        //         BindOrDie();
        //         ListenOrDie();
        //     }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket() : _sockfd(gdefaultsockfd)
        {}

        TcpSocket(int sockfd) : _sockfd(sockfd)
        {}

        virtual void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::ERROR) << "socket error";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::DEBUG) << "socket create success: " << _sockfd;      
        }

        virtual void SetSocketOpt() override
        {
            // 保证服务器,异常断开之后,可以立即重启,不会有bind问题
            int opt = 1;
            int n = ::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            (void)n;
        }

        virtual bool BindOrDie(int port) override
        {
            if(_sockfd == gdefaultsockfd) return false;
            InetAddr addr(port);
            int n = ::bind(_sockfd, addr.NetAddr(), addr.NetAddrLen());
            if(n < 0)
            {
                LOG(LogLevel::ERROR) << "bind error";
                exit(BIND_ERR);
            }
            LOG(LogLevel::DEBUG) << "bind success: " << _sockfd;      
            return true;
        }

        virtual bool ListenOrDie() override
        {
            if(_sockfd == gdefaultsockfd) return false;
            int n = ::listen(_sockfd, gbacklog);
            if(n < 0)
            {
                LOG(LogLevel::ERROR) << "listen error";
                exit(LISTEN_ERR);
            }
            LOG(LogLevel::DEBUG) << "listen create success: " << _sockfd;      
            return true;
        }

        // 1. 文件描述符 2. client info
        virtual SockPtr Accepter(InetAddr *client) override
        {
            if(!client) return nullptr;
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int newsockfd = ::accept(_sockfd, CONV(&peer), &len);
            if(newsockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                return nullptr;
            }
            client->SetAddr(peer, len);
            return std::make_shared<TcpSocket>(newsockfd);  // accept之后链接好的 sockfd
        }

        virtual void Close() override
        {
            if(_sockfd == gdefaultsockfd) return ;
            ::close(_sockfd);
        }

        virtual int Recv(std::string *out) override
        {
            char buffer[1024 * 8];
            auto size = ::recv(_sockfd, buffer, sizeof(buffer), 0);
            if(size > 0)
            {
                buffer[size] = 0;
                *out = buffer;
            }
            return size;
        }

        virtual int Send(const std::string &in) override
        {
            auto size = ::send(_sockfd, in.c_str(), in.size(), 0);
            return size;
        }

        virtual int Fd() override
        {
            return _sockfd;
        }

        virtual ~TcpSocket()
        {
        }

    private:
        int _sockfd;
    };
}

📦 http协议封装
HttpProtocol.hpp

#pragma  once

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <fstream>
#include "Common.hpp"
#include "Log.hpp"

const std::string Sep = "\r\n";
const std::string LineSep = " ";
const std::string HeaderLineSep = ": ";
const std::string BlankLine = Sep;
const std::string defaulthomepage = "wwwroot";
const std::string http_version = "HTTP/1.0";
const std::string page404 = "wwwroot/404.html";
const std::string firstpage = "index.html";

using namespace LogModule;

class HttpReauest
{
public:
    HttpReauest() {}

    ~HttpReauest() {}

    bool IsHasArgs()
    {
        return _isexec;
    }

    bool ParseHeaderKv()
    {
        std::string key, value;
        for(auto &herder : _req_header)
        {
            if(SplitString(herder, HeaderLineSep, &key, &value))
            {
                _headerkv.insert(std::make_pair(key, value));
            }
        }
        return true;
    }

    bool ParseHeader(std::string &request_str)
    {
        std::string line;
        while(true)
        {
            bool r = ParseOneLine(request_str, &line, Sep);
            if(r && !line.empty())
            {
                _req_header.push_back(line);
            }
            else if(r && line.empty())
            {
                _blank_line = Sep;
                break;
            }
            else 
            {
                return false;
            }
        }
        ParseHeaderKv();
        return true;
    }

    void Deserialize(std::string &request_str)
    {
        if(ParseOneLine(request_str, &_req_line, Sep))
        {
            // 提取请求行中的详细字段
            ParseReqLine(_req_line, LineSep);
            ParseHeader(request_str); // 解析报头
            _body = request_str;

            // 分析请求中是否含有参数
            if(_method == "POST")
            {
                _isexec = true;  // 参数在正文
                _path = _uri;
                _args = _body;
            }
            else if(_method  == "GET")
            {
                auto pos = _uri.rfind('?');
                if(pos != std::string::npos)
                {
                    _isexec = true;
                    //  /login?name=zhangsan&passwd=123456
                    _path = _uri.substr(0, pos);
                    _args = _uri.substr(pos + 1);
                } 
            }
        }
    }

    std::string GetContent(const std::string &path)
    {
        // 二进制读
        std::string content;
        std::ifstream in(path, std::ios::binary);
        if(!in.is_open()) return std::string();
        in.seekg(0, in.end);
        int filesize = in.tellg();
        in.seekg(0, in.beg);

        content.resize(filesize);
        in.read((char*)content.c_str(), filesize);
        in.close();
        LOG(LogLevel::DEBUG) << "content length: " << content.size();
        return content;

        // 暂时做法
        // std::string content;
        // std::ifstream in(_uri);
        // if(!in.is_open()) return std::string();
        // std::string line;
        // while(getline(in, line))
        // {
        //     content += line;
        // }
        // in.close();
        // return content;
    }

    void Print()
    {
        std::cout << "_method: " << _method << std::endl;
        std::cout << "_uri: " << _uri << std::endl;
        std::cout << "_version: " << _version << std::endl;

        for(auto &kv : _headerkv)
        {
            std::cout << kv.first << " # " << kv.second << std::endl;
        }
        std::cout << "_blank_line: " << _blank_line << std::endl;

        std::cout << "_body: " << _body << std::endl;
    }

    std::string Uri()
    {
        return _uri;
    }

    void SetUri(const std::string &newuri)
    {
        _uri = newuri;
    }

    std::string Path() { return _path; }
    std::string Args() { return _args; }

    std::string Suffix()
    {
        auto pos = _uri.rfind(".");
        if(pos == std::string::npos) return std::string(".html");
        else return _uri.substr(pos);
    }
private:
    void  ParseReqLine(const std::string &_req_line, const std::string &LineSep)  // 请求行字段解析 
    {
        (void)LineSep;
        std::stringstream ss(_req_line);
        ss >> _method >> _uri >> _version;
    }

private:
    std::string _req_line;
    std::vector<std::string> _req_header;
    std::string _blank_line;
    std::string _body;

    // 在反序列化的过程中,细化我们解析出来的字段
    std::string _method;
    std::string _uri;      // 用户想要这个
    std::string _path;
    std::string _args;
    std::string _version;
    std::unordered_map<std::string, std::string> _headerkv;
    bool _isexec = false;
};

// 对于http请求,都要有应答
class HttpResponse
{
public:
    HttpResponse() : _version(http_version), _blank_line(Sep)
    {}

    void Build(HttpReauest &req)
    {
        std::string uri = defaulthomepage + req.Uri();
        if(uri.back() == '/')
        {
            uri += firstpage;
            // req.SetUri(uri);
        }

        _content = req.GetContent(uri);
        if(_content.empty()) 
        {
            // 用户请求的资源并不存在
            _status_code = 404;
            _content = req.GetContent(page404);
        }
        else 
        {
            _status_code = 200;
        }
        LOG(LogLevel::DEBUG) << "客户端在请求:" << req.Uri();
        _status_desc = Code2Desc(_status_code);     // 和状态码是强相关的

        if(!_content.empty())
        {
            SetHeader("Content-Length", std::to_string(_content.size()));

            std::string mime_type = Suffix2Desc(req.Suffix());
            SetHeader("Content-Type", mime_type);
        }
        _body = _content;
    }

    void SetCode(int code) 
    {
        _status_code = code;
        _status_desc = Code2Desc(_status_code);
    }

    void SetBody(const std::string &body)
    {
        _body = body;
    }

    void SetHeader(const std::string &k, const std::string &v)
    {
        _header_kv[k] = v;
    }

    void Serialize(std::string *resp_str)
    {
        for(auto &header : _header_kv)
        {
            _resp_header.push_back(header.first + HeaderLineSep + header.second);
        }

        _resp_line = _version + LineSep + std::to_string(_status_code) + LineSep + _status_desc + Sep;   // 第一行

        // 序列化
        *resp_str = _resp_line;
        for(auto &line : _resp_header)
        {
            *resp_str += (line + Sep);
        }
        *resp_str += _blank_line;
        *resp_str += _body;
    }

    ~HttpResponse() {}
private:
    std::string Code2Desc(int code)
    {
        switch (code)
        {
        case 200:
            return "OK";
        case 404:
            return "Not Found";
        case 301:
            return "Move Permanently";
        case 302:
            return "Found";
        default:
            return std::string();
        }
    }

    std::string Suffix2Desc(const std::string &suffix)
    {
        if(suffix == ".html")
            return "text/html";
        else if(suffix == ".jpg")
            return "application/x-jpg";
        else 
            return "text/html";
    }
private:
    // 必备的要素
    std::string _version;
    int _status_code;
    std::string _status_desc;
    std::string _content;
    std::unordered_map<std::string, std::string> _header_kv;

    // 最终要这4部分,构建应答
    std::string _resp_line;
    std::vector<std::string> _resp_header;
    std::string _blank_line;
    std::string _body;
};

🦋 完整代码移步我的Gitee仓库

🧑‍💻 点击跳转 包含代码和详细注释

在这里插入图片描述
🧑‍💻 至此 成功访问http服务器上搭建的网站
在这里插入图片描述

备注:
此处我们使用 8080 端口号启动了 HTTP 服务器. 虽然 HTTP 服务器一般使用 80 端口,
但这只是一个通用的习惯. 并不是说 HTTP 服务器就不能使用其他的端口号.

四:🔥 附录

🦋 HTTP 历史及版本核心技术与时代背景

🧑‍💻 HTTP(Hypertext Transfer Protocol, 超文本传输协议) 作为互联网中浏览器和服务器间通信的基石, 经历了从简单到复杂、 从单一到多样的发展过程。 以下将按照时间顺序, 介绍 HTTP 的主要版本、 核心技术及其对应的时代背景。

🦋 HTTP/0.9

📚 核心技术:

  • 仅支持 GET 请求方法。
  • 仅支持纯文本传输, 主要是 HTML 格式。
  • 无请求和响应头信息。

📚 时代背景:

  • 1991 年, HTTP/0.9 版本作为 HTTP 协议的最初版本, 用于传输基本的超文本 HTML 内容。
  • 当时的互联网还处于起步阶段, 网页内容相对简单, 主要以文本为主。

🦋 HTTP/1.0

📚 核心技术:

  • 引入 POST 和 HEAD 请求方法。
  • 请求和响应头信息, 支持多种数据格式(MIME) 。
  • 支持缓存(cache) 。
  • 状态码(status code) 、 多字符集支持等。

📚 时代背景:

  • 1996 年, 随着互联网的快速发展, 网页内容逐渐丰富, HTTP/1.0 版本应运而生。
  • 为了满足日益增长的网络应用需求, HTTP/1.0 增加了更多的功能和灵活性。
  • 然而, HTTP/1.0 的工作方式是每次 TCP 连接只能发送一个请求, 性能上存在一定局限。

🦋 HTTP/1.1

📚 核心技术:

  • 引入持久连接(persistent connection) , 支持管道化(pipelining) 。
  • 允许在单个 TCP 连接上进行多个请求和响应, 提高了性能。
  • 引入分块传输编码(chunked transfer encoding) 。
  • 支持 Host 头, 允许在一个 IP 地址上部署多个 Web 站点。

📚 时代背景:

  • 1999 年, 随着网页加载的外部资源越来越多, HTTP/1.0 的性能问题愈发突出。
  • HTTP/1.1 通过引入持久连接和管道化等技术, 有效提高了数据传输效率。
  • 同时, 互联网应用开始呈现出多元化、 复杂化的趋势, HTTP/1.1 的出现满足了这些需求。

🦋 HTTP/2.0

📚 核心技术:

  • 多路复用(multiplexing) , 一个 TCP 连接允许多个 HTTP 请求。
  • 二进制帧格式(binary framing) , 优化数据传输。
  • 头部压缩(header compression) , 减少传输开销。
  • 服务器推送(server push) , 提前发送资源到客户端。

📚 时代背景:

  • 2015 年, 随着移动互联网的兴起和云计算技术的发展, 网络应用对性能的要求越来越高。
  • HTTP/2.0 通过多路复用、 二进制帧格式等技术, 显著提高了数据传输效率和网络性能。
  • 同时, HTTP/2.0 还支持加密传输(HTTPS) , 提高了数据传输的安全性。

🦋 HTTP/3.0

📚 核心技术:

  • 使用 QUIC 协议替代 TCP 协议, 基于 UDP 构建的多路复用传输协议。
  • 减少了 TCP 三次握手及 TLS 握手时间, 提高了连接建立速度。
  • 解决了 TCP 中的线头阻塞问题, 提高了数据传输效率。

📚 时代背景:

  • 2022 年, 随着 5G、 物联网等技术的快速发展, 网络应用对实时性、 可靠性的要求越来越高。
  • HTTP/3.0 通过使用 QUIC 协议, 提高了连接建立速度和数据传输效率, 满足了这些需求。
  • 同时, HTTP/3.0 还支持加密传输(HTTPS) , 保证了数据传输的安全性

五:🔥 共勉

以上就是我对 应用层协议 HTTP 讲解&实战:从0实现HTTP 服务器 的理解,想要完整代码可以私信博主噢!觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/960160.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入剖析 Adam 优化器:原理、优势与应用

在深度学习领域&#xff0c;优化器的选择对模型的训练效率和性能起着决定性作用。Adam优化器作为一种自适应优化算法&#xff0c;凭借其根据历史梯度信息动态调整学习率的特性&#xff0c;备受研究者和工程师的青睐。它巧妙融合了RMSProp和Momentum两种优化算法的理念&#xff…

ppp综合实验

IP地址 r1 r2 r3 r4 hdlc封装 pap认证 r2 r3 chap认证 r2 r4 MGRE 主认证 [r1]int Tunnel 0/0/0 [r1-Tunnel0/0/0]ip add 192.168.4.1 24 [r1-Tunnel0/0/0]tunnel-protocol gre p2mp [r1-Tunnel0/0/0]source 12.1.1.1 [r1-Tunnel0/0/0]nhrp entry multicast dynamic [r1-Tu…

C++——list的了解和使用

目录 引言 forward_list与list 标准库中的list 一、list的常用接口 1.list的迭代器 2.list的初始化 3.list的容量操作 4.list的访问操作 5.list的修改操作 6.list的其他操作 二、list与vector的对比 结束语 引言 本篇博客要介绍的是STL中的list。 求点赞收藏评论…

Elasticsearch+kibana安装(简单易上手)

下载ES( Download Elasticsearch | Elastic ) 将ES安装包解压缩 解压后目录如下: 修改ES服务端口&#xff08;可以不修改&#xff09; 启动ES 记住这些内容 验证ES是否启动成功 下载kibana( Download Kibana Free | Get Started Now | Elastic ) 解压后的kibana目…

Ansible自动化运维实战--script、unarchive和shell模块(6/8)

文章目录 一、script模块1.1、功能1.2、常用参数1.3、举例 二、unarchive模块2.1、功能2.2、常用参数2.3、举例 三、shell模块3.1、功能3.2、常用参数3.3、举例 一、script模块 1.1、功能 Ansible 的 script 模块允许你在远程主机上运行本地的脚本文件&#xff0c;其提供了一…

Unity Epplus读取excel表并存入So文件举例

目录 此篇需要你有一定的阅读代码的能力&#xff0c;不然点开了也不知道在做什么 这是读表工具 So文件这么写 使用 此篇需要你有一定的阅读代码的能力&#xff0c;不然点开了也不知道在做什么 在此之前你需要知道epplus是干什么的&#xff0c;然后知道其基本api&#xff0…

Java Web-Cookie与Session

会话跟踪技术 会话跟踪技术是一种在 Web 应用程序中跟踪用户会话状态的机制&#xff0c;它允许服务器在多个请求之间识别和关联属于同一用户的请求&#xff0c;以便在整个会话过程中保持用户相关的信息。以下是几种常见的会话跟踪技术&#xff1a; Cookie 概念&#xff1a;Cook…

Spring Boot(6)解决ruoyi框架连续快速发送post请求时,弹出“数据正在处理,请勿重复提交”提醒的问题

一、整个前言 在基于 Ruoyi 框架进行系统开发的过程中&#xff0c;我们常常会遇到各种有趣且具有挑战性的问题。今天&#xff0c;我们就来深入探讨一个在实际开发中较为常见的问题&#xff1a;当连续快速发送 Post 请求时&#xff0c;前端会弹出 “数据正在处理&#xff0c;请…

YOLOv8改进,YOLOv8检测头融合DynamicHead,并添加小目标检测层(四头检测),适合目标检测、分割等,全网独发

摘要 作者提出一种新的检测头,称为“动态头”,旨在将尺度感知、空间感知和任务感知统一在一起。如果我们将骨干网络的输出(即检测头的输入)视为一个三维张量,其维度为级别 空间 通道,这样的统一检测头可以看作是一个注意力学习问题,直观的解决方案是对该张量进行全自…

C语言编译过程全面解析

今天是2025年1月26日&#xff0c;农历腊月二十七&#xff0c;一个距离新春佳节仅一步之遥的日子。城市的喧嚣中&#xff0c;年味已悄然弥漫——能在这个时候坚持上班的人&#xff0c;真可称为“牛人”了吧&#xff0c;哈哈。。。。 此刻&#xff0c;我在重新审视那些曾被遗忘的…

在php中怎么打开OpenSSL

&#xff08;点击即可进入聊天助手&#xff09; 背景 在使用php做一些项目时,有用到用户邮箱注册等,需要开启openssl的能力 在php系统中openssl默认是关闭状态的,在一些低版本php系统中,有的甚至需要在服务器终端后台,手动安装 要打开OpenSSL扩展&#xff0c;需要进行以下步骤 …

【数据分享】2014-2025年我国水系数据(免费获取/全国/分省)

水系数据是我们在各项研究中经常使用的数据&#xff01;水系数据虽然很常用&#xff0c;但是却基本没有能下载最近年份水系数据的网站&#xff0c;所以很多人不知道如何获得水系数据。 本次我们给大家分享的是2014-2025年的全国范围的水系数据&#xff0c;包括水系线数据和水系…

Office2021下载与安装保姆级教程【Office Tool Plus】

Office Tool Plus安装Office2021 下载Office Tool Plus安装OfficeⅠ. 清除旧版本Ⅱ. 配置安装参数Ⅲ. 安装许可证Ⅳ. 激发&#xff08;JH&#xff09;Office 本文介绍使用Office Tool Plus工具下载、安装、部署Office 2021全过程。 下载Office Tool Plus OfficeToolPlus是一个…

DeepSeek明确学术研究方向效果如何?

明确学术研究方向 在学术写作中&#xff0c;选择一个出色的研究主题至关重要&#xff0c;因为它直接关系到论文是否能登上高级别的学术期刊。不少学者在这个过程中走入了误区&#xff0c;他们往往将大把的时间花在写作本身&#xff0c;而忽略了对选题的深入思考&#xff0c;这…

Spring MVC 综合案例

目录 一. 加法计算器 1. 准备工作 2. 约定前后端交互接口 需求分析 接口定义 3. 服务器端代码 4. 运行测试 二. 用户登录 1. 准备工作 2. 约定前后端交互接口 需求分析 接口定义 (1) 登录界面接口 (2) 首页接口 3. 服务器端代码 4. 运行测试 三. 留言板 1. 准备…

基于OSAL的嵌入式裸机事件驱动框架——消息队列osal_msg

参考B站up主【架构分析】嵌入式祼机事件驱动框架 感谢大佬分享 消息队列 消息分为hdr和bdy&#xff0c;把消息的头dhr和内容bdy做了一个分离的设计 dhr包括指向下一个消息的指针next&#xff0c;len在创建消息的时候使用&#xff0c;dest_id即目标任务&#xff0c;将消息和任务…

Github 2025-01-25Rust开源项目日报Top10

根据Github Trendings的统计,今日(2025-01-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Vue项目1JavaScript项目1Deno: 现代JavaScript和TypeScript运行时 创建周期:2118 天开发语言:Rust, JavaScript协议类型…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.8 随机数奥秘:生成符合现实分布的虚拟数据

1.8 随机数奥秘&#xff1a;生成符合现实分布的虚拟数据 目录 #mermaid-svg-wHqPAE3mMd8HNYmi {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wHqPAE3mMd8HNYmi .error-icon{fill:#552222;}#mermaid-svg-wHqPAE3mM…

使用vitepress搭建自己的博客项目

一、介绍can-vitepress-blog 什么是CAN BLOG CAN BLOG是基于vitepress二开的个人博客系统&#xff0c;他能够方便使用者快速构建自己的博客文章&#xff0c;无需繁琐的配置和复杂的代码编写。 CAN BLOG以antdv为UI设计基础&#xff0c;简洁大方&#xff0c;界面友好&#xf…

STranslate 中文绿色版即时翻译/ OCR 工具 v1.3.1.120

STranslate 是一款功能强大且用户友好的翻译工具&#xff0c;它支持多种语言的即时翻译&#xff0c;提供丰富的翻译功能和便捷的使用体验。STranslate 特别适合需要频繁进行多语言交流的个人用户、商务人士和翻译工作者。 软件功能 1. 即时翻译&#xff1a; 文本翻译&#xff…