小工具知识网

当前位置: 首页 > 行业知识 > 【计算机网络——应用层】http协议

行业知识

【计算机网络——应用层】http协议

2026-10-31 00:14:48 来源:互联网转载

文章目录

  • 1. http协议
    • 1.1 http协议简介
    • 1.2 url组成
    • 1.3 urlencode与urldecode
    • 2. http协议的格式
      • 2.1 http协议的格式
      • 2.2 一些细节问题
      • 3. http的方法、状态码和常见响应报头
        • 3.1 http请求方法
        • 3.2 http状态码
        • 3.3 http常见的响应报头属性
        • 4. 一个非常简单的http协议服务端
        • 5. http长链接
        • 6. http会话保���

          1. http协议

          1.1 http协议简介

          在上一篇文章中我们了解到应用层协议是可以由程序员自己定制的。

          计算机领域经过了这么长时间的发展,肯定会出现很多已经写好的协议,我们直接拿来用就可以了的。事实也确实如此,http协议(超文本传输协议)就是其中之一。

          这个协议是用于客户端向服务端请求“资源”,包括文本、图片、音频、视频等资源的协议。因为它不只能拿文本资源,所以叫超文本传输协议。

          1.2 url组成

          我们平常说的网址,其实就是URL,这个URL有很多个部分组成的

          在客户端向服务端发起通信的时候,通过DNS将这个服务器地址转换成IP地址,在其后面应该有端口号的,但是http协议的端口号固定就是80,https端口号固定是443,就能通过这个I P地址+端口号找到指定服务器的指定进程,然后通过对应的资源地址在web根目录下找到对应的资源

          1.3 urlencode与urldecode

          对于像 / + : ?等字符, 已经被url特殊处理了。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

          转义的规则如下:取出字符的ASCII码,转成16进制,然后前面加上百分号即可。编码成%XY格式。服务器收到url请求,将会对%XY进行解码,该过程称为decode

          2. http协议的格式

          2.1 http协议的格式

          http协议的请求和响应都分为四个部分。对于请求,分为1. 请求行; 2. 请求报头; 3. 一个空行; 4.请求正文;对于响应,分为1. 状态行; 2.响应报头; 3. 一个空行; 4. 响应正文

          其中在请求行,有三个部分内容,通过空格来区分,这三个部分分别是1. 请求方法; 2. url 3. http版本,这个版本现在有1.0;1.1;2.0

          格式是http/版本号,例如http/1.1

          2.2 一些细节问题

          1. 请求和响应怎么保证读取完了?

          每次可以读取完整的一行 ==> 循环读取每一行,直到遇到空行 ==> 此时就读取了所有的请求报头和请求行 ==> 在请求报头里面有一个属性Content-Length表示正文长度,解析这个长度,然后按照指定长度读取正文即可

          2. 请求和响应是怎么做到序列化和反序列化的?

          http不用关注json等序列化和反序列化工具,直接发送即可。服务器解析客户端的请求,获取其中的信息填充至响应缓冲区。服务器通过响应报头的方式返回请求的参数,在响应正文中返回请求的资源。

          3. http的方法、状态码和常见响应报头

          3.1 http请求方法

          请求方法说明支持的http协议版本
          GET获取资源(表单在url中携带)1.0/1.1
          POST传输实体主体(表单在请求正文中携带)1.0/1.1

          其他方法不常用,这里就不列出来了

          我们经常会在网页填写一些内容提交,如果使用GET方法的话,这些内容会被浏览器拼接到url后面(使用?作为分隔符),如果使用PSOT方法的话,这些内容就会在请求正文中

          1、GET方法通过URL传递参数。例如http://ip:port/XXX/YY?key1=value1&key2=value2。像百度的搜索就是用的GET方法。GET方法通过url传递参数,参数注定不能太大,例如上传视频等巨长的二进制文件就不适合用GET了。

          2、POST提交参数通过http请求正文提交参数。请求正文可以很大,可以提交视频等巨长的文件。

          3、POST方法提交参数,用户是看不到的,私密性更高,而GET方法不私密。私密性不等于安全性,POST方法和GET方法其实都不安全!(http请求都是可以被抓到的,想要安全必须加密,使用https协议)

          3.2 http状态码

          http协议在响应的时候就会在状态行给出本次请求的响应状态,可以理解成是这个请求的“退出码”。

          一般来说,http的状态码分为5类

          类别原因短语
          1xxInformational(信息性状态码)接收的请求正在处理
          2xxSuccess(成功状态码)接收的请求处理完毕
          3xxRedirection(重定向状态码)需要进行附加操作以完成请求
          4xxClinet Error(客户端错误状态码)服务器无法完成请求
          5xxServer Error(服务器错误状态码)服务器完成请求出错

          几个比较常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

          3.3 http常见的响应报头属性

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

            4. 一个非常简单的http协议服务端

            设计思路:我们日常使用的浏览器就是http协议的客户端,我们现在只需要实现服务端即可。既然要实现支持http协议的服务端,那么只需要按照tcp协议的方式构建传输层,然后按照http协议的约定来解析客户端发过来的消息,然后按照约定的响应格式发送数据给客户端

            那么其实我们之前实现的socket编程的代码是可以用上的

            enum{    USAGE_ERR = 1,    SOCKET_ERR,    BIND_ERR,    LISTEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5;typedef std::function func_t;class HttpServer{    public:    HttpServer(func_t func, const uint16_t &port = gport) : _port(port), _func(func)    {    }    void initServer()    {        // 1. 创建socket文件套接字对象        _listensock = socket(AF_INET, SOCK_STREAM, 0);        if (_listensock == -1)        {            exit(SOCKET_ERR);        }        // 2.bind自己的网络信息        sockaddr_in local;        local.sin_family = AF_INET;        local.sin_port = htons(_port);        local.sin_addr.s_addr = INADDR_ANY;        int n = bind(_listensock, (struct sockaddr *)&local, sizeof local);        if (n == -1)        {            exit(BIND_ERR);        }        // 3. 设置socket为监听状态        if (listen(_listensock, gbacklog) != 0) // listen 函数        {            exit(LISTEN_ERR);        }    }    void start()    {        while (true)        {            struct sockaddr_in peer;            socklen_t len = sizeof peer;            int sock = accept(_listensock, (struct sockaddr *)&peer, &len);            if (sock  0)                    exit(0);                handleHttp(sock); // 这里就是需要服务端执行的内容了(传输层上层的内容)                close(sock);                exit(0);            }            waitpid(id, nullptr, 0);            close(sock);        }    }    void handleHttp(int sock) // 服务端调用    {        // 1. 读到完整的http请求        // 2. 反序列化        // 3. 调用回调函数        // 4. 将resp序列化        // 5. send        char buffer[4096];        HttpRequest req;        HttpResponse resp;        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);        if(n > 0)        {            buffer[n] = 0; // 添加一个字符串的结尾            req.inbuffer = buffer;            req.parse(); // 解析调用的内容            _func(req, resp); // req -> resp            send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);        }    }    ~HttpServer() {}    private:    uint16_t _port;    int _listensock;    func_t _func;};

            在应用层我们就要设计我们服务端的”http协议了“

            #pragma once#include #include #include #include #include #include #include "Util.hpp" // 这是工具类,提供了一些工具函数// 一些配置文件,这里写***(可以集成为一个配置文件,在服务器启动的时候加载)const std::string sep = "\r\n"; // 分隔符const std::string default_root = "./webroot"; // web根目录const std::string home_page = "index.html"; // 首页const std::string html_404 = "404.html"; // 找不到页面显示的页面class HttpRequest // http请求类{public:    HttpRequest()    {    }    ~HttpRequest()    {    }    bool parse() // 解析    {        // 1. 提取inbuffer中的第一行内容        std::string line = Util::getOneline(inbuffer, sep);        if (line.empty())            return false;        // 2. 解析内容 method url httpversion        std::stringstream ss(line);        ss >> method >> url >> httpversion;        // 3. 添加默认路径        path += default_root;        path += url;        if(path[path.size() - 1] == '/') // 访问不合法资源            path += home_page;        // 4. 获取path对应的资源后缀(资源类型)        auto pos = path.rfind(".");        if(pos == std::string::npos)            suffix = ".html";        else            suffix = path.substr(pos);        // 5. 获取的资源大小        struct stat st;        int n = stat(path.c_str(), &st);        if(n != 0) stat((default_root + html_404).c_str(), &st);        size = st.st_size;        return true;    }public:    std::string inbuffer; // 缓冲区,保存接收到的所有内容    std::string method;      // 浏览器请求方法    std::string url;         // 相对于default_root的资源路径    std::string httpversion; // http协议版本    std::string path;        // 要访问的资源路径    std::string suffix;      // 资源后缀    int size;                // 资源大小};class HttpResponse // http响应类{public:    std::string outbuffer; // 这里保存所有序列化之后的结果,最终发送这个outbuffer中的数据即可};

            同时我们需要设计一下服务端的回调函数

            /*httpServer.cc*/#include #include #include "httpServer.hpp"using namespace Server;using namespace std;static void Usage(std::string proc){    std::cout     std::string ct = "Content-Type: ";    if (suffix == ".html")        ct += "text/html";    else if (suffix == "jpg")        ct += "application/x-jpg";    else        ct += "text/html";    ct += "\r\n";    return ct;}bool Get(const HttpRequest &req, HttpResponse &resp){    cout         // 没有指定资源        Util::readFile(html_404, const_cast    if (argc != 2)    {        Usage(argv[0]);        exit(USAGE_ERR);    }    uint16_t port = atoi(argv[1]);    std::unique_ptrpublic:    static std::string getOneline(std::string &buffer, const std::string &sep) // 获取一行内容    {        auto pos = buffer.find(sep);        if(pos == std::string::npos) return "";        std::string sub = buffer.substr(0, pos);        buffer.erase(0, pos + sep.size());        return sub;    }    static bool readFile(const std::string &resource, char* buffer, int size) // 二进制方式读取文件    {        std::ifstream in(resource, std::ios::binary);        if(!in.is_open()) return false; // open file fail        in.read(buffer, size); // 从in中使用二进制读取的方式读取size个字节到buffer中        in.close();        return true;    }};

上一篇:报表高度怎么调?

下一篇:揭秘单位化的奥秘与核心要素,为何它如此重要?