Linux网络-------3.应⽤层协议HTTP

1.HTTP协议

虽然我们说,应⽤层协议是我们程序猿⾃⼰定的.但实际上,已经有⼤佬们定义了⼀些现成的,⼜⾮常好⽤的应⽤层协议,供我们直接参考使⽤.HTTP(超⽂本传输协议)就是其中之⼀。

在互联⽹世界中,HTTP(HyperText Transfer Protocol,超⽂本传输协议) 是⼀个⾄关重要的协议。

它定义了客⼾端(如浏览器)与服务器之间如何通信,以交换或传输超⽂本(如HTML⽂档)。HTTP协议是客⼾端与服务器之间通信的基础。客⼾端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是⼀个⽆连接、⽆状态的协议,即每次请求都需要建⽴新的连接,且服务器不会保存客⼾端的状态信息。

3.HTTP回应---Response

4.HTTP request----------------客户端如何打开想要访问的资源



  • 左边就是http协议规定的传输的数据类型,右边则是各个主机中存储的数据
  • 这里只用请求做说明-------说白了就是,左边到右边就是反序列化,右边到左边就是序列化!!!!!!!!!!

5.HTTP状态码

  • 从客户端读取之后,需要设置状态码!!!!!!!
  • 404就是典型的客户端错误码,指客户访问了服务器端没有存储的网页,会显示这个错误

以淘宝·网页举例,淘宝的服务端没有存储a.html 所以会显示无法访问 !!!!!

6.HTTP常⻅⽅法

1.GET方法

⽤途:⽤于请求URL指定的资源。

⽰例: GET /index.html HTTP/1.1

特性:指定资源经服务器端解析后返回响应内容。

GET方法详解

2.POST⽅法

⽤途:⽤于传输实体的主体,通常⽤于提交表单数据。

⽰例: POST /submit.cgi HTTP/1.1

特性:可以发送⼤量的数据给服务器,并且数据包含在请求体中。

使用方法

二者对比------以login.html为例

那如果把post改为get呢?

  • 使用get的话,服务端就能拿到登录的数据了,联系数据库,就能做客户注册了!!!!!!!!!!!
  • 使用?做分割符!!!!!!!!

7.代码全览

先看一下格式:

Http.hpp:

cpp 复制代码
// 防止头文件被重复包含
#pragma once

// 包含必要的头文件
#include "Socket.hpp"       // Socket相关功能
#include "TcpServer.hpp"    // TCP服务器实现
#include "Util.hpp"         // 工具函数
#include "Log.hpp"          // 日志模块
#include <iostream>         // 标准输入输出
#include <string>           // 字符串处理
#include <memory>           // 智能指针
#include <sstream>          // 字符串流
#include <functional>       // 函数对象
#include <vector>           // 动态数组
#include <unordered_map>    // 哈希表

// 使用命名空间
using namespace SocketModule;  // Socket模块命名空间
using namespace LogModule;     // 日志模块命名空间

// 定义常量字符串
const std::string gspace = " ";        // 空格
const std::string glinespace = "\r\n"; // HTTP换行符
const std::string glinesep = ": ";     // 头部字段分隔符

// 定义Web根目录和默认页面
const std::string webroot = "./wwwroot";   // 网站根目录
const std::string homepage = "index.html"; // 默认首页
const std::string page_404 = "/404.html";  // 404页面路径

// HTTP请求类
class HttpRequest
{
public:
    // 构造函数,初始化交互标志为false
    HttpRequest() : _is_interact(false)
    {
    }
    // 序列化方法(暂未实现)
    std::string Serialize()
    {
        return std::string();
    }
    // 解析请求行(如 GET / HTTP/1.1)
    void ParseReqLine(std::string &reqline)
    {
        // 使用字符串流分割请求行
        std::stringstream ss(reqline);
        ss >> _method >> _uri >> _version; // 分别提取方法、URI和版本--------以空格为分隔符--------这里method是GET,uri是/--但会被自动翻译为/下的第一个.html文件,verson则是HTTP/1.1
    }
    // 反序列化HTTP请求
    bool Deserialize(std::string &reqstr)
    {
        // 1. 提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);//把第一行读入reqline,glinespace是\r\n-----作为一句的尾部
        LOG(LogLevel::DEBUG) << reqline;  // 记录请求行日志

        // 2. 解析请求行
        ParseReqLine(reqline);

        // 处理URI
        if (_uri == "/")
            _uri = webroot + _uri + homepage; // 默认首页路径
        else
            _uri = webroot + _uri; // 其他资源路径

        // 记录解析结果日志
        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;

        // 检查URI中是否包含参数
        const std::string temp = "?";
        auto pos = _uri.find(temp);
        if (pos == std::string::npos)
        {
            return true;  // 无参数直接返回//----------访问.html,png等静态内容时就会直接返回!!!!!!!!!
        }
        // 分离参数和URI
        _args = _uri.substr(pos + temp.size()); // 提取参数部分
        _uri = _uri.substr(0, pos);            // 提取纯URI部分
        _is_interact = true;                   // 标记为交互请求

        

        return true;
    }
    // 获取URI
    std::string Uri()
    {
        return _uri;
    }
    // 检查是否为交互请求
    bool isInteract()
    { 
        return _is_interact;
    }
    // 获取参数
    std::string Args()
    {
        return _args;
    }
    // 析构函数
    ~HttpRequest()
    {

    }

private:
    std::string _method;    // HTTP方法(GET/POST等)
    std::string _uri;       // 请求资源路径
    std::string _version;   // HTTP版本

    std::unordered_map<std::string, std::string> _headers; // 请求头
    std::string _blankline; // 空行
    std::string _text;      // 请求体

    std::string _args;      // 请求参数
    bool _is_interact;      // 是否为交互请求标志
};

// HTTP响应类
class HttpResponse
{
public:
    // 构造函数,初始化空行和HTTP版本
    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")
    {
    }
    // 序列化HTTP响应
    std::string Serialize()
    {
        // 构建状态行
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;
        // 构建响应头
        std::string resp_header;
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        // 组合状态行、响应头、空行和响应体
        return status_line + resp_header + _blankline + _text;
    }

    // 设置目标文件
    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }
    
    // 设置状态码和描述
    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 301:
            _desc = "Moved Permanently";
            break;
        case 302:
            _desc = "See Other";
            break;
        default:
            break;
        }
    }
    // 添加响应头
    void SetHeader(const std::string &key, const std::string &value)
    {
        auto iter = _headers.find(key);
        if (iter != _headers.end())
            return;
        _headers.insert(std::make_pair(key, value));
    }
    // 根据文件后缀确定Content-Type!!!!!!!!//如果要访问的网页中还有其他资源如图片,音频。。。。。。就需要设置content-type-------可查找mine表!!!!
    std::string Uri2Suffix(const std::string &targetfile)
    {
        // 查找最后一个点号
        auto pos = targetfile.rfind(".");
        if (pos == std::string::npos)
        {
            return "text/html";  // 默认返回HTML类型
        }

        std::string suffix = targetfile.substr(pos);
        if (suffix == ".html" || suffix == ".htm")
            return "text/html";
        else if (suffix == ".jpg")
            return "image/jpeg";
        else if (suffix == "png")
            return "image/png";
        else
            return "";
    }

    // 构建HTTP响应
    bool MakeResponse()
    {
        // 忽略favicon.ico请求
        if (_targetfile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetfile << "忽略它";
            return false;
        }
        // 处理重定向测试
        if (_targetfile == "./wwwroot/redir_test")
        {
            SetCode(301);
            SetHeader("Location", "https://www.qq.com/");
            return true;
        }
        // 读取文件内容
        int filesize = 0;
        bool res = Util::ReadFileContent(_targetfile, &_text);
        if (!res)  // 文件不存在
        {
            _text = "";
            LOG(LogLevel::WARNING) << "client want get : " << _targetfile << " but not found";
            SetCode(404);  // 设置404状态码------客户端访问了不存在的网页!!!!!!!
            _targetfile = webroot + page_404;  // filetarget指向404页面!!!!!!!!
            filesize = Util::FileSize(_targetfile);
            Util::ReadFileContent(_targetfile, &_text);  // 读取404页面内容
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Content-Type", suffix);  // 设置Content-Type---------------注意这个一定要有,不然没办法链接到网页
            SetHeader("Content-Length", std::to_string(filesize));  // 设置内容长度
        }
        else  // 文件存在
        {
            LOG(LogLevel::DEBUG) << "读取文件: " << _targetfile;
            SetCode(200);  // 设置200状态码
            filesize = Util::FileSize(_targetfile);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Conent-Type", suffix);//  设置Content-Type---------------注意这个内容类型一定要有,不然没办法链接到网页
            SetHeader("Content-Length", std::to_string(filesize));
            SetHeader("Set-Cookie", "username=zhangsan;");  // 设置Cookie
        }
        return true;
    }
    // 设置响应体文本
    void SetText(const std::string &t)
    {
        _text = t;
    }
    // 反序列化方法(暂未实现)
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
    // 析构函数
    ~HttpResponse() {}

    // 公有成员变量
public:
    std::string _version;  // HTTP版本
    int _code;             // 状态码
    std::string _desc;     // 状态描述

    std::unordered_map<std::string, std::string> _headers; // 响应头
    std::vector<std::string> cookie;  // Cookie集合
    std::string _blankline;  // 空行
    std::string _text;       // 响应体

    std::string _targetfile; // 目标文件路径
};

// 定义HTTP处理函数类型
using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;

// HTTP服务器类
class Http
{
public:
    // 构造函数,初始化TCP服务器
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))//《2》进一步使用port初始化TcpServer类并返回其指针---------->tcpseerver.hpp
    {
    }
    // 处理HTTP请求
    void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client)//-----------<13>这个才是业务函数!
    {
        // 接收HTTP请求
        std::string httpreqstr;
        int n = sock->Recv(&httpreqstr);  // 接收请求数据--------<14>把客户端发来的内容(需要反序列化)存入httpreqstr
        if (n > 0)  // 接收成功
        {
            std::cout << "##########################" << std::endl;
            std::cout << httpreqstr;  // 打印原始请求
            std::cout << "##########################" << std::endl;

            // 解析请求
            HttpRequest req;
            HttpResponse resp;
            req.Deserialize(httpreqstr);

            // 处理交互请求
            if (req.isInteract())
            {
                // 检查路由是否存在------
                if (_route.find(req.Uri()) == _route.end())//查看uri是否存在于<6>中建立的_route
                {
                    // 可添加重定向逻辑
                }
                else//如果存在------本文中,如果你在网页中点击login界面时会走这一条路!!!!!!!
                {
                    // 调用注册的处理函数
                    _route[req.Uri()](req, resp);//-----------------------------<15>调用<6>传递的键值对的函数--即业务函数----见main.cc的Login(HttpRequest &req, HttpResponse &resp)函数
                    std::string response_str = resp.Serialize();//序列化,准备返回给服务器
                    sock->Send(response_str);  // 发送响应-----------------------<16>最后一步,返回给服务器!!!!!!
                }
            }
            else  // 处理静态资源请求-----例如.html/.png文件
            {
                resp.SetTargetFile(req.Uri());
                if (resp.MakeResponse())  // 构建响应成功
                {
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);  // 发送响应
                }
            }
        }

// 调试模式下的处理
#ifdef DEBUG
        std::string httpreqstr;
        sock->Recv(&httpreqstr);
        std::cout << httpreqstr;

        // 构建简单响应
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;
        resp._desc = "OK";

        std::string filename = webroot + homepage;
        bool res = Util::ReadFileContent(filename, &(resp._text));
        (void)res;
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
#endif
    }
    // 启动HTTP服务器
    void Start()//---------------------<8>调用TcpServer的start,并传递参数是提供服务时用的fd建立的sock类,和客户传递过来的主机地址--------->tcpserver.hpp
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });//业务函数在这呢!!!!!!
    }
    // 注册服务路由
    void RegisterService(const std::string name, http_func_t h)//<7>注册服务路由
    {
        std::string key = webroot + name; // 构建完整路径
        auto iter = _route.find(key);
        if (iter == _route.end())  // 防止重复注册
        {
            _route.insert(std::make_pair(key, h));
        }
    }
    // 析构函数
    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> tsvrp;  // TCP服务器实例
    std::unordered_map<std::string, http_func_t> _route;  // 路由表
};

Main.cc:

cpp 复制代码
#include"Http.hpp"

void Login(HttpRequest &req, HttpResponse &resp)
{
    // req.Args();
    LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
    std::string text = "hello: " + req.Args(); // username=zhangsan&passwd=123456

    // 登录认证

    resp.SetCode(200);
    resp.SetHeader("Content-Type","text/plain");
    resp.SetHeader("Content-Length", std::to_string(text.size()));
    resp.SetText(text);
}
// void Register(HttpRequest &req, HttpResponse &resp)
// {
//     LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
//     std::string text = "hello: " + req.Args();



//     resp.SetCode(200);
//     resp.SetHeader("Content-Type","text/plain");
//     resp.SetHeader("Content-Length", std::to_string(text.size()));
//     resp.SetText(text);
// }
// void VipCheck(HttpRequest &req, HttpResponse &resp)
// {
//     LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
//     std::string text = "hello: " + req.Args();
//     resp.SetCode(200);
//     resp.SetHeader("Content-Type","text/plain");
//     resp.SetHeader("Content-Length", std::to_string(text.size()));
//     resp.SetText(text);
// }

// void Search(HttpRequest &req, HttpResponse &resp)
// {

// }


// http port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);//《1》初始化一个HTTP类,并返回其指针---------》http.hpp
    httpsvr->RegisterService("/login", Login); // <6> 注册服务路由-------->http.hpp,再回到main.cc
    // httpsvr->RegisterService("/register", Register);
    // httpsvr->RegisterService("/vip_check", VipCheck);
    // httpsvr->RegisterService("/s", Search);
    // httpsvr->RegisterService("/", Login);

    httpsvr->Start();//<7>开始接收服务--------->http.hpp
    return 0;
}

Util.hpp:

cpp 复制代码
// 防止头文件重复包含的编译指令
#pragma once

// 包含标准输入输出流库
#include <iostream>
// 包含文件流操作库
#include <fstream>
// 包含字符串处理库
#include <string>

// 工具类声明
class Util
{
public:
    // 静态方法:读取文件内容到字符串中,传递一个文件路径,和一个string,把文件的内容读取到string中
    // 参数:filename - 文件名,out - 输出字符串指针
    // 返回值:成功返回true,失败返回false
    static bool ReadFileContent(const std::string &filename /*std::vector<char>*/, std::string *out)
    {

        // 获取文件大小
        int filesize = FileSize(filename); // FileSize函数用于获取指定文件的大小(以字节为单位)。如果没打开就返回-1!!!!

        // 检查文件大小是否有效
        if (filesize > 0)
        {
            // 创建输入文件流对象
            std::ifstream in(filename); // ifstream#include <fstream>

            // 方式1:先声明后打开
            // std::ifstream in;          // 创建未关联文件的流对象
            // in.open("example.txt");    // 打开文件

            // 方式2:声明时直接打开(推荐)
            // std::ifstream in("example.txt");  // 创建并立即打开文件,不用显示调用open函数打开文件

            // 检查文件是否成功打开
            if (!in.is_open())
                return false;

            // 调整输出字符串大小以容纳文件内容
            out->resize(filesize);

            // 将文件内容读取到字符串中
            // 注意:这里使用了c_str()获取字符串底层指针,并进行强制类型转换
            in.read((char *)(out->c_str()), filesize);
            // istream& read(char* s, streamsize n);
            // 在 C++ 中,out->c_str() 返回的是 const char* 类型指针,而 std::ifstream::read() 需要的是 char* 类型指针,因此需要进行强制类型转换。

            // 关闭文件流
            in.close(); // 记得要关闭!!!
        }
        else
        {
            // 文件大小为0或获取失败时返回false
            return false;
        }

        // 读取成功返回true
        return true;
    }

    // 静态方法:从大字符串中读取一行
    // 参数:bigstr - 输入大字符串,out - 输出行字符串指针,sep - 行分隔符
    // 返回值:成功返回true,失败返回false
    static bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep /*\r\n*/)
    {
        // 查找分隔符位置
        auto pos = bigstr.find(sep);

        // 如果没有找到分隔符则返回false
        if (pos == std::string::npos) // std::string::npos 是 C++ 标准库中 std::string 类的一个静态常量成员,表示"未找到"或"无效位置"的特殊值。它是字符串操作中非常重要的一个标记值。
            return false;

        // 提取分隔符前的内容作为一行
        *out = bigstr.substr(0, pos); // 起始永远是0

        // 从原字符串中删除已读取的行和分隔符
        bigstr.erase(0, pos + sep.size()); // 起始必须是0

        // 读取成功返回true
        return true;
    }

    // 静态方法:获取文件大小
    // 参数:filename - 文件名
    // 返回值:成功返回文件大小(字节),失败返回-1
    static int FileSize(const std::string &filename)
    {
        // 以二进制模式打开文件
        std::ifstream in(filename, std::ios::binary); // std::ios::binary 是 C++ 中文件打开模式的一个标志,它的作用是告诉文件流以二进制模式而非文本模式打开文件
        // 文本模式(默认):
        // 在某些系统(如 Windows)上,会进行换行符转换:
        // 读取时,\r\n(Windows 换行)会被转换为 \n(C++ 标准换行)。
        // 写入时,\n 会被转换为 \r\n。
        // 可能在某些平台上处理特殊字符(如 EOF)时会有额外行为。

        // 二进制模式:
        // 完全按原样读写数据!!!!!,不做任何转换。
        // 适合处理非文本文件(如图片!!!!!!!、音频、视频、压缩包等)。!!!!!!!!!!!!!!!!!!!!!!!
        // 也适合需要精确控制文件内容的场景(如跨平台数据交换)。


        // 检查文件是否成功打开
        if (!in.is_open())
            return -1;

        // 将文件指针移动到文件末尾--------seekg移动函数!!!!
        in.seekg(0, in.end);//in.end:基准位置(seek direction),这里是文件末尾(std::ios::end)。

        // 获取当前指针位置(即文件大小)
        int filesize = in.tellg();

        // 将文件指针移回文件开头
        in.seekg(0, in.beg);

        // 关闭文件流
        in.close();

        // 返回文件大小
        return filesize;
    }
}; // 类定义结束

效果演示


  • 在浏览器中输入115.120.238.130:8081
  • 别忘了要先去云服务器官网开启安全组,这样浏览器才能访问服务端
相关推荐
wanhengidc1 分钟前
高防服务器租用:保障数据安全
服务器·网络·安全
大梦南柯7 分钟前
ospf综合实验
网络·智能路由器
快快网络-三七31 分钟前
第二篇:Linux 文件系统操作:从基础到进阶
linux·运维·服务器·centos·快快网络
Doris_LMS33 分钟前
Linux的访问权限(保姆级别)
linux·运维·服务器·面试
无望__wsk35 分钟前
ospf笔记
服务器·网络·笔记
玖剹42 分钟前
Linux文件系统:从内核到缓冲区的奥秘
linux·c语言·c++·笔记·ubuntu
是阿建吖!1 小时前
【Linux | 网络】传输层(UDP和TCP)
linux·网络·udp·tcp
Hao想睡觉1 小时前
CNN卷积神经网络之VggNet和GoogleNet经典网络模型(四)
网络·人工智能·cnn
dessler1 小时前
RabbitMQ-镜像队列(Mirrored Queues)
linux·运维·rabbitmq
瑾曦1 小时前
Docker相关命令
linux