网络:5.应用层协议HTTP

应用层协议HTTP

  • (http是一个应用层协议,底层用的tcp)

一.HTTP补充知识:

域名和IP之间的关系

二.HTTP协议

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

在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。

它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。

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

三.认识URL

平时我们俗称的 "网址" 其实就是说的 URL(统一资源定位符)也就是超链接

四.urlencode和urldecode(了解)

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

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

转义的规则如下:

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

例如:

"+" 被转义成了 "%2B"

urldecode就是urlencode的逆过程;

urlencode工具

浏览器urlencode;服务器urldecode;-- 这种模式叫作B/S模式

五.HTTP协议请求与响应格式

1. HTTP请求

  • http协议,序列和反序列化用的是特殊字符(空格或换行符)进行子串拼接,且不依赖任何第三方库
  • 首行: [方法] + [url] + [版本]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用 \r\n 分隔;遇到空行表示 Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

2. HTTP响应

完整请求获取方法:没法直接确定读到原完整报文,但是我有方法读到当前请求的完整报头!

  1. 读取到完整的请求报头[空行];
  2. 对报头进行反序列化,提取一个属性:Content-Length:有效载荷的长度;
  3. 在从剩余的字符串内容中,提取content-length个字符。
  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\r\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.

基本的应答格式

六.HTTP的方法

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

请求是怎么表示自己要请求什么?资源服务端,资源在哪里?
资源位置用URI表示;资源都放在web(http)根目录下;
如果是GET/服务器要自动拼接首页(/index.html或/index.htm);
网页内容,必须是在服务器特定路径下的文件(图片,视频...,CSS,jS...).

1. HTTP常见方法

(1).GET方法(重点)

(GET: 获取资源)

用途:用于请求URL指定的资源。
示例: GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。
form表单:https://www.runoob.com/html/html-forms.html

cpp 复制代码
std::string GetFileContentHelper(const std::string &path)
{
    // 一份简单的读取二进制文件的代码
    std::ifstream in(path, std::ios::binary);
    if (!in.is_open())
        return "";
    in.seekg(0, in.end);
    int filesize = in.tellg();
    in.seekg(0, in.beg);
    std::string content;
    content.resize(filesize);
    in.read((char *)content.c_str(), filesize);
    // std::vector<char> content(filesize);
    // in.read(content.data(), filesize);
    in.close();
    return content;
}
(2).POST方法(重点)

用途:用于传输实体的主体,通常用于提交表单数据。
示例: POST /submit.cgi HTTP/1.1
特性:可以发送大量的数据给服务器,并且数据包含在请求体中。
form表单:https://www.runoob.com/html/html-forms.html

(3).PUT方法(不常用)

防止用户给服务器乱上传东西,大多数浏览器都把这个方法禁掉了,
用途:用于传输文件,将请求报文主体中的文件保存到请求URL指定的位置。
示例: PUT /example.html HTTP/1.1
特性:不太常用,但在某些情况下,如RESTful API中,用于更新资源。

(4).HEAD方法

用途:与GET方法类似,但不返回报文主体部分,仅返回响应头。
示例: HEAD /index.html HTTP/1.1
特性:用于确认URL的有效性及资源更新的日期时间等。

shell 复制代码
// curl -i 显示
$ curl -i www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Date: Sun, 16 Jun 2024 08:38:04 GMT
Etag: "588604dc-94d"
Last-Modified: Mon, 23 Jan 2017 13:27:56 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<!DOCTYPE html>
...

// 使用head方法,只会返回响应头
$ curl --head www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 277
Content-Type: text/html
Date: Sun, 16 Jun 2024 08:43:38 GMT
Etag: "575e1f71-115"
Last-Modified: Mon, 13 Jun 2016 02:50:25 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
(5).DELETE方法(不常用)

用途:用于删除文件,是PUT的相反方法。
示例: DELETE /example.html HTTP/1.1
特性:按请求URL删除指定的资源。

(6).OPTIONS方法

用途:用于查询针对请求URL指定的资源支持的方法。
示例: OPTIONS * HTTP/1.1
特性:返回允许的方法,如GET、POST等。

不支持的效果

shell 复制代码
// 搭建一个nginx用来测试
// sudo apt install nginx
// sudo nginx -- 开启
// ps ajx | grep nginx -- 查看
// sudo nginx -s stop -- 停止服务

$ sudo nginx -s stop
$ ps ajx | grep nginx
2944845 2945390 2945389 2944845 pts/1 2945389 S+ 1002 0:00 grep --color=auto nginx

$ sudo nginx
$ ps axj | grep nginx
1        2945393 2945393 2945393 ?        -1 Ss  0   0:00 nginx: master process nginx
2945393  2945394 2945393 2945393 ?        -1 S   33  0:00 nginx: worker process
2945393  2945395 2945393 2945393 ?        -1 S   33  0:00 nginx: worker process
2944845  2945397 2945396 2944845 pts/1    2945396 S+ 1002 0:00 grep --color=auto nginx

// -X(大x) 指明方法
$ curl -X OPTIONS -i http://127.0.0.1/
HTTP/1.1 405 Not Allowed
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 16 Jun 2024 08:48:22 GMT
Content-Type: text/html
Content-Length: 166
Connection: keep-alive

<html>
<head><title>405 Not Allowed</title></head>
<body>
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>

支持的效果

shell 复制代码
HTTP/1.1 200 OK
Allow: GET, HEAD, POST, OPTIONS
Content-Type: text/plain
Content-Length: 0
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 16 Jun 2024 09:04:44 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

// 注意:这里没有响应体,因为Content-Length为0

七.HTTP的状态码

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

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

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

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

重定向的htp的请求,至少是三部分:状态行,报头,空行;正文常常没有.

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

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

  1. HTTP状态码301(永久重定向)
  • 网站更换域名,或者更换网址
  • 当服务器返回HTTP 301状态码时,表示请求的资源已经被永久移动到新的位置。
  • 在这种情况下,服务器会在响应中添加一个Location头部,用于指定资源的新位置。这个Location头部包含了新的URL地址,浏览器会自动重定向到该地址。
  • 例如,在HTTP响应中,可能会看到类似于以下的头部信息:
shell 复制代码
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n
  1. HTTP状态码302(临时重定向)
  • 临时重定向--不改变任何信息; 多用于登录跳转, 页面跳转之类的工作。

  • 当服务器返回HTTP 302状态码时,表示请求的资源临时被移动到新的位置。

  • 同样地,服务器也会在响应中添加一个Location头部来指定资源的新位置。浏览器会暂时使用新的URL进行后续的请求,但不会缓存这个重定向。

  • 例如,在HTTP响应中,可能会看到类似于以下的头部信息:

shell 复制代码
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

1. 常见Header

  • Content-Type: 数据类型(text/html等); 查表:https://tool.oschina.net/commons

    一张网页内,可能会有多种资源,网页自己+图片,获得网页,识别网页内还有其他资源,浏览器会发起二次请求。应答要告诉对方,我的有效载荷是什么。

  • Content-Length: Body的长度

  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;

  • User-Agent: 声明用户的操作系统和浏览器版本信息;

  • Referer: 当前页面是从哪个页面跳转过来的;

  • Location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;

  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

  • Accept:表示客户端能接受的响应内容类型(MIME类型查HTTPContent-type对照表);

  • Accept-Encoding:表示客户端支持的内容压缩格式(编码方式).

2. 关于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/xm l;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding 客户端支持的数据压缩格式 Accept-Encoding: gzip, deflate, br
Accept-Language 客户端可接受的语言类型 Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Host 请求的主机名和端口号 Host: www.example.com:8080
User-Agent 客户端的软件环境信息 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Cookie 客户端发送给服务器的 HTTP cookie信息 Cookie: session_id=abcdefg12345; user_id=123
Referer 请求的来源URL Referer: http://www.example.com/previous_page.html
Content-Type 实体主体的媒体类型 Content-Type: application/x-www-formurlencoded<br(对于表单提交) 或 Content-Type: application/json (对于JSON数据)
Content-Length 实体主体的字节大小 Content-Length: 150
Authorization 认证信息,如用户名和密码 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== (Base64编码后的用户 名:密码)
Cache-Control 缓存控制指令 请求时: Cache-Control: no-cache 或 Cache- Control: max-age=3600 ;响应时: Cache- Control: public, max-age=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 2023 07:20:00 GMT
ETag 资源的唯一标识符,用于缓存 ETag: "3f80f-1b6-5f4e2512a4100"
Expires 响应过期的日期和时间 Expires: Wed, 21 Oct 2023 08:28:00 GMT

九.最简单的HTTP服务器

实现一个最简单的HTTP服务器, 只在网页上输出 "hello world"; 只要我们按照HTTP协议的要求构造数据, 就很容易能做到;

client&&server,是如何保证自己读到的报文是完整的?
step1:读取字节流,分析读到的字节流,确认是否存在空行;
step2:提取Content-Length:获得正文长度,然后在读取或者截取指定长度的内容;

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void Usage() {
    printf("usage: ./server [ip] [port]\n");
}
int main(int argc, char* argv[]) {
    if (argc != 3) {
        Usage();
        return 1;
    }
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return 1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    
    if (ret < 0) {
        perror("bind");
        return 1;
    }
    ret = listen(fd, 10);
    if (ret < 0) {
        perror("listen");
        return 1;
    }
    for (;;) {
        struct sockaddr_in client_addr;
        socklen_t len;
        int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }
        char input_buf[1024 * 10] = {0}; // 用一个足够大的缓冲区直接把数据读完.
        ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
        if (read_size < 0) {
            return 1;
        }
        printf("[Request] %s", input_buf);
        char buf[1024] = {0};
        const char* hello = "<h1>hello world</h1>";
        sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", strlen(hello),
                hello);
        write(client_fd, buf, strlen(buf));
    }
    return 0;
}

编译, 启动服务. 在浏览器中输入 http://[ip]:[port], 就能看到显示的结果 "Hello World"

备注:

此处我们使用 9090 端口号启动了HTTP服务器. 虽然HTTP服务器一般使用80端口,

但这只是一个通用的习惯. 并不是说HTTP服务器就不能使用其他的端口号.

使用chrome测试我们的服务器时, 可以看到服务器打出的请求中还有一个 GET /favicon.ico HTTP/1.1 这样的请求.
favicon.ico的作用: 网站标签页上的小图标.

十.完整http服务器

HTTP服务器(HttpServer)

实现一个基于TCP的HTTP服务器,支持静态资源返回和动态交互功能,支持长连接(keep-alive)和短连接

实现了一个完整的HTTP协议解析器,能够处理HTTP请求并返回响应,支持GET和POST方法,能够返回HTML、图片、视频等多种资源类型。

(1). Common.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <functional>
#include <string>
#include <memory>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERROR,
    OPEN_ERROR
};

class NoCopy
{
public:
    NoCopy() {}
    ~NoCopy() {}
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};

#define CONV(addr) ((struct sockaddr*)&addr)
(2). Inet_Addr.hpp

和网络版本计算器唯一的区别:添加 SetAddr 方法

cpp 复制代码
#pragma once
#include "Common.hpp"

// 网络地址和主机地址之间进行转化的类

class InetAddr
{
public:
    InetAddr(){}
    // 网络转主机
    InetAddr(struct sockaddr_in& addr)
    {
        SetAddr(addr);
    }

    // 主机转网络
    InetAddr(const std::string& ip ,uint16_t port)
        :_ip(ip)
        ,_port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        
        //法一(线程不安全)
        //_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
        //法二(线程安全)
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_port);
    }
    
    InetAddr(uint16_t port)
        :_ip("0")
        ,_port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_addr.s_addr = INADDR_ANY;
        _addr.sin_port = htons(_port);
    }

    void SetAddr(struct sockaddr_in& addr)
    {
        _addr = addr; //浅拷贝不会有影响
        _port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列
        // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
        //法一(线程不安全)
        // _ip = inet_ntoa(_addr.sin_addr); 
        
        //法二(线程安全)
        char ipbuffer[64];
        inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(ipbuffer));
        _ip = ipbuffer;
    }
    
    uint16_t Port() const { return _port; }
    std::string Ip() const { return _ip; }
    // NetAddr需要引用,是因为Route.hpp的MessageRoute函数中
    // sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&user.NetAddr(), sizeof(user.NetAddr()));
    // 的第五个参数需要可以修改,不能传右值(临时变量)
    const struct sockaddr_in& NetAddr() { return _addr; }
    const struct sockaddr* NetAddrPtr() { return CONV(_addr); }
    socklen_t NetAddrLen()
    {
        return sizeof(_addr);
    }
    bool operator==(const InetAddr& addr)
    {
        return _ip == addr._ip && _port == addr._port;
    }

    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    
    ~InetAddr()
    {}
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};
(3). Log.hpp

日志模块,支持控制台和文件两种输出策略(与远程控制和字典服务器项目(TcpEchoServer)和网络计算器(NetCal)中的Log.hpp完全相同)

(4). Mutex.hpp

互斥锁封装模块(与远程控制和字典服务器项目(TcpEchoServer)和网络计算器(NetCal)中的Mutex.hpp完全相同)

(5). Socket.hpp(模板方法模式)

和网络版本计算器唯一的区别:接收缓冲区大小扩大了

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Log.hpp"
#include "Common.hpp"
#include "Inet_Addr.hpp"

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog = 16;

    // 模板方法模式(固定套路代码常用)
    // 基类socket,大部分方法,都是纯虚方法
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int blacklog) = 0;
        virtual std::shared_ptr<Socket> Accept(InetAddr* client) = 0;
        virtual void Close() = 0;
        virtual int Recv(std::string *out) = 0;
        virtual int Send(const std::string& message) = 0;
        virtual int Connect(const std::string &server_ip, uint16_t server_port) = 0;

    public:
        void BuildTcpSocketMethod(uint16_t port, int blacklog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(blacklog);
        }
        
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
    };

    const static int defaultfd = -1;
    class TcpSocket : public Socket
    {
    public:
        TcpSocket():_sockfd(defaultfd)
        {}
        TcpSocket(int fd):_sockfd(fd)
        {}
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); // ::表示默认使用更外部(全局)的socket函数
            if(_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "socket error";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::INFO) << "socket success";
        }
        
        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if(n < 0)
            {
                LOG(LogLevel::FATAL) << "bind error";
                exit(BIND_ERR);
            }
            LOG(LogLevel::INFO) << "bind success";
        }

        void ListenOrDie(int blacklog) override
        {
            int n = ::listen(_sockfd, blacklog);
            if(n < 0)
            {
                LOG(LogLevel::FATAL) << "listen error";
                exit(LISTEN_ERR);
            }
            LOG(LogLevel::INFO) << "listen success";
        }

        std::shared_ptr<Socket> Accept(InetAddr* client) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = ::accept(_sockfd, CONV(peer), &len);
            if(fd < 0)
            {
                LOG(LogLevel::WARNING) << "accept warning ...";
                return nullptr; //TODO
            }
            client->SetAddr(peer);
            return std::make_shared<TcpSocket>(fd);
        }

        int Recv(std::string *out) override //返回值等同read的返回值
        {
            // 流式读取,并不关心读到的是什么
            char buffer[4096*4];
            ssize_t n = ::recv(_sockfd,&buffer,sizeof(buffer)-1, 0);
            if(n > 0)
            {
                buffer[n] = 0;
                *out += buffer;
            }
            return n;
        }

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

        void Close() override
        {
            if(_sockfd > 0)
                ::close(_sockfd);
        }

        int Connect(const std::string &server_ip, uint16_t server_port) override
        {
            InetAddr server(server_ip, server_port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }

    private:
        int _sockfd; // _sockfd,listensockfd,sockfd
    };
}
(6). TcpServer.hpp
cpp 复制代码
#include "Socket.hpp"
#include "Log.hpp"
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <sys/types.h>
#include <functional>

using namespace SocketModule;
using namespace LogModule;

using ioservice_t = std::function<void(std::shared_ptr<Socket> &sock, InetAddr &client)>;

class TcpServer
{
public:
    TcpServer(uint16_t port) 
        :_port(port)
        ,_listensockptr(std::make_unique<TcpSocket>())
        ,_isrunning(false)
    {
        _listensockptr->BuildTcpSocketMethod(_port);
    }
    
    void Start(ioservice_t callback)
    {
        _isrunning = true;
        while(_isrunning)
        {
            InetAddr client;
            auto sock = _listensockptr->Accept(&client); // 获得1.和client通信的sockfd 2.client网络地址
            if(sock == nullptr)
            {
                continue;                
            }
            LOG(LogLevel::DEBUG) << "accept success ..." << client.StringAddr();

            // 获得了:1.与客户端通信socket;2.客户端地址和端口号
            pid_t id = fork();
            if(id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error ...";
                exit(FORK_ERROR);
            }
            else if(id == 0)
            {
                //子进程 ->关闭listen socket
                _listensockptr->Close();
                if(fork() > 0)
                    exit(0);
                //孙子进程在执行任务,已经是孤儿进程了
                callback(sock,client);
                sock->Close();
                exit(OK);
            }
            else
            {
                //父进程 ->关闭clinet socket(即:auto sock)
                sock->Close();
                pid_t rid = ::waitpid(-1, nullptr, 0);
                (void)rid;
            }
        }
        _isrunning = false;
    }
    
    ~TcpServer() {}
private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensockptr;
    bool _isrunning;
};
(7). Util.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <fstream>
#include <string>

// 工具类
class Util
{
public:
    Util() {}
    ~Util() {}
    
    static bool ReadFileContent(const std::string &filename, std::string* out/*实际std::vector<char>常用*/)
    {
        // version 1:默认以文本方式读取文件的.图片是二进制的不能用这种方式读.
        // std::ifstream in(filename, std::ios::out | std::ios::app);
        // if (!in.is_open())
        // {
        //     return false;
        // }
        // std::string line;
        // while (std::getline(in,line))
        // {
        //     *out += line;
        // }
        // in.close();
        // return true;
        
        // version 2:以二进制方式进行读取
        int filesize = FileSize(filename);
        if(filesize > 0)
        {
            std::ifstream in(filename, std::ios::binary);
            if(!in.is_open())
                return false;
            out->resize(filesize);
            in.read(&(*out)[0], filesize); //或in.read((char *)out->c_str(), filesize);
            in.close();
            return true;
        }
        else
        {
            return false;
        }
    }

    static bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep/*\r\n*/)
    {
        auto pos = bigstr.find(sep);
        if(pos == std::string::npos)
            return false;
        *out = bigstr.substr(0, pos);
        bigstr.erase(0, pos+sep.size());
        return true;
    }

    static int FileSize(const std::string& filename)
    {
        std::ifstream in(filename,std::ios::binary);
        if(!in.is_open())
            return -1;
        in.seekg(0, in.end);
        int filesize = in.tellg();
        in.seekg(0, in.beg);
        in.close();
        return filesize;
    }

private:
        
};
(8). Http.hpp
cpp 复制代码
#pragma once
#include "TcpServer.hpp"
#include "Util.hpp"
#include <sstream>
#include <memory>
#include <functional>
#include <unordered_map>
#include <cctype>

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";

class HttpRequest
{
public:
    HttpRequest()
        :_is_interact(false)
        ,_has_header(false)
        ,_has_body(false)
    {}
    
    // 服务端浏览器写好了
    std::string Serialize()
    {
        return std::string();
    }

    // 获取请求行
    void ParseReqLine(std::string& reqline)
    {
        // GET / HTTP/1.1
        std::stringstream ss(reqline);
        ss >> _method >> _uri >> _version;
    }
    
    // 获取请求报头与正文
    bool ParseReqHeadersAndBody(std::string& reqline)
    {
        std::string line;
        int content_len = 0;

        // 读取并解析 Header,直到空行
        while (true)
        {
            bool ret = Util::ReadOneLine(reqline, &line, glinespace);
            if (!ret)
            {
                LOG(LogLevel::DEBUG) << "请求报头为空";
                return true;
            }
            if (line.empty()) break; // 空行:头结束(因为ReadOneLine已去掉\r\n)

            auto sep = line.find(glinesep);
            if (sep != std::string::npos)
            {
                std::string key = line.substr(0, sep);
                std::string value = line.substr(sep + glinesep.size());
                _headers[key] = value;
                if(!_has_header) _has_header = true;
                if (key == "Content-Length" || key == "content-length")
                {
                    content_len = std::stoi(value);
                }
            }
        }

        // 按 Content-Length 读取正文到 _text
        _text.clear();
        if (content_len > 0)
        {
            if ((int)reqline.size() >= content_len)
            {
                _has_body = true;
                _text = reqline.substr(0, content_len);
                reqline.erase(0, content_len);
            }
            else
            {
                LOG(LogLevel::FATAL) << "报文异常";
                return false;
            }
        }
        
        return true;
    }
    
    // 实现(我们今天认为,reqstr是一个完整的http,没有写decode)
    bool Deserialize(std::string& reqstr)
    {
        // 1.提取请求中的请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline,glinespace);
        LOG(LogLevel::DEBUG) << reqline;
        
        // 2.对请求行进行反序列化
        // 获得请求行
        ParseReqLine(reqline);
        if(_uri=="/")
            _uri = webroot + _uri + homepage;
        else
            _uri = webroot + _uri;
        // 获得请求报头与正文
        ParseReqHeadersAndBody(reqstr);
        
        /*日志打印请求信息*/
        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;
        if(_has_header)
        {
            for(const auto &header : _headers)
            {
                LOG(LogLevel::DEBUG) << "_header: " << header.first << glinesep << header.second;
            }
        }
        if(_has_body)
            LOG(LogLevel::DEBUG) << "_text: " << _text;
        /*日志打印请求信息*/
            
        // (1).POST特殊处理:
        if (_method == "POST" || _method == "post")
        {
            _args = _text;       //参数由请求正文发送
            _is_interact = true;
            return true;
        }

        // (2).GET特殊处理:
        // 注:可能有这种_uri: ./wwwroot/login?username=zhangsan&password=123456
        if (_method == "GET" || _method == "get")
        {
            const std::string temp = "?";
            auto pos = _uri.find(temp);
            if(pos == std::string::npos)
            {
                return true;
            }
            // _uri解析:
            // _args: username=zhangsan&password=123456
            // _uri: ./wwwroot/login
            _args = _uri.substr(pos + temp.size());
            _uri = _uri.substr(0, pos);
            _is_interact = true;
            return true;
        }
        
        // 其他请求方法(PUT、DELETE、HEAD等)
        
        return true;
    }

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

    bool isInteract() { return _is_interact; }

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

    // 检查是否支持长连接
    bool KeepAlive()
    {
        auto iter = _headers.find("Connection");
        if(iter != _headers.end())
        {
            // Connection字段:conn_val
            std::string conn_val = iter->second;
            // 转换为小写进行比较
            for(char& c : conn_val)
            {
                c = std::tolower(c);
            }
            if(conn_val == "keep-alive")
                return true;
        }
        // 检查 HTTP 版本,HTTP/1.1 默认支持长连接
        if(_version == "HTTP/1.1" || _version == "http/1.1")
        {
            // HTTP/1.1 如果没有明确指定 Connection: close,则默认支持 keep-alive
            auto iter2 = _headers.find("Connection");
            if(iter2 != _headers.end())
            {
                std::string conn_val = iter2->second;
                for(char& c : conn_val)
                {
                    c = std::tolower(c);
                }
                if(conn_val == "close")
                    return false;
            }
            return true;
        }
        return false;
    }

    ~HttpRequest()
    {}
private:
    std::string _method;
    std::string _uri;
    std::string _version;
    std::unordered_map<std::string, std::string> _headers; //请求报头
    std::string _blankline; //空行
    std::string _text; //正文

    bool _has_header;
    bool _has_body;

    std::string _args; //uri后面跟的参数
    bool _is_interact; //是否需要交互
};

class HttpResponse
{
public:
    HttpResponse()
        :_blankline(glinespace)
        ,_version("HTTP/1.0")
        ,_keep_alive(false)
    {}
    
    // 实现:成熟的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;
    }

    // 服务端浏览器写好了
    bool Deserialize()
    {
        return true;
    }
    
    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.emplace(key, value);
    }

    void SetText(const std::string & t)
    {
        _text = t;
    }

    // 设置是否保持连接
    void SetKeepAlive(bool keep_alive)
    {
        _keep_alive = keep_alive;
        if(_keep_alive)
        {
            SetHeader("Connection", "keep-alive");
            // HTTP/1.1 版本以支持长连接
            _version = "HTTP/1.1";
        }
        else
        {
            SetHeader("Connection", "close");
        }
    }

    std::string Uri2Suffix(const std::string& targetfile)
    {
        // targetfile: ./wwwroot/a/b/c.html
        auto pos = targetfile.rfind(".");
        if(pos == std::string::npos)
        {
            return "text/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 if (suffix == ".mp4")
            return "video/mpeg4";
        else
            return "text/html";//应该填完Content-Type整张表的,简写默认是网页了
    }

    bool MakeResponse()
    {
        if(_targetfile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetfile << "忽略它";
            return false;
        }
        
        // 临时重定向
        if(_targetfile == "./wwwroot/redir_test")
        {
            SetCode(302);
            SetHeader("Location", "https://www.qq.com/");
            return true;
        }

        int filesize = 0;
        bool res = Util::ReadFileContent(_targetfile, &_text); //ReadFileContent给_targetfile加好了./wwwroot
        if(!res)
        {
            // 法一:
            _text = "";
            LOG(LogLevel::WARNING) << "client want get : " << _targetfile << " but not found";
            SetCode(404);
            _targetfile = webroot + page_404;
            Util::ReadFileContent(_targetfile, &_text);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Content-Type", suffix);
            
            // 法二:
            // SetCode(302);
            // SetHeader("Location", "http://115.190.2.155:8080/404.html"); //注意:这里没有域名,端口写死的,要注意!!!
            // return true;
        }
        else
        {
            LOG(LogLevel::DEBUG) << "读取文件: " << _targetfile;
            SetCode(200);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Content-Type", suffix);
        }
        filesize = Util::FileSize(_targetfile);
        SetHeader("Content-Length", std::to_string(filesize));
        return true;
    }

    ~HttpResponse(){}
// private:
public:
    std::string _version;
    int _code; //404
    std::string _desc; //"Not Found"
    std::unordered_map<std::string, std::string> _headers; //请求报头
    std::string _blankline; //空行
    std::string _text; //正文
    // 其他属性
    std::string _targetfile; //要获取资源的地址
    bool _keep_alive; //是否保持连接
};

// Http要做到:
// 1.返回静态资源
// 2.提供动态交互的能力

using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;

class Http
{
public:
    Http(uint16_t port)
        :tsvrp(std::make_unique<TcpServer>(port))
    {
        
    }
    
    // 从缓冲区中提取一个完整的 HTTP 请求
    // 返回值: true 表示提取到完整请求, false 表示数据不完整
    bool ExtractOneRequest(std::string& buffer, std::string& request)
    {
        // 先检查是否至少有一个完整的请求行
        size_t first_line_end = buffer.find(glinespace);
        if(first_line_end == std::string::npos)
        {
            // 还没有收到完整的请求行
            return false;
        }

        // 查找请求头结束标志 \r\n\r\n
        std::string header_end = glinespace + glinespace; // "\r\n\r\n"
        size_t header_end_pos = buffer.find(header_end);
        if(header_end_pos == std::string::npos)
        {
            // 还没有收到完整的请求头
            return false;
        }

        // 提取请求头部分(用于解析 Content-Length)
        std::string header_part = buffer.substr(0, header_end_pos);
        
        // 检查是否有 Content-Length
        int content_len = 0;
        size_t content_pos = header_part.find("Content-Length:");
        if(content_pos == std::string::npos)
        {
            content_pos = header_part.find("content-length:");
        }
        
        if(content_pos != std::string::npos && content_pos < header_end_pos)
        {
            // 找到 Content-Length 头,解析其值
            size_t len_start = header_part.find(":", content_pos) + 1;
            // 跳过空格
            while(len_start < header_part.size() && (header_part[len_start] == ' ' || header_part[len_start] == '\t'))
                len_start++;
            size_t len_end = header_part.find(glinespace, len_start);
            if(len_end == std::string::npos)
                len_end = header_part.size();
            
            if(len_start < len_end)
            {
                std::string len_str = header_part.substr(len_start, len_end - len_start);
                try {
                    content_len = std::stoi(len_str);
                } catch(...) {
                    content_len = 0;
                }
            }
        }

        // 计算完整请求的结束位置
        // header_end_pos 是 \r\n 的位置,header_end.size() 是 \r\n 的长度
        // 所以请求头结束后的位置是 header_end_pos + header_end.size()
        size_t header_end_offset = header_end_pos + header_end.size();
        size_t request_end = header_end_offset + content_len;
        
        if(buffer.size() < request_end)
        {
            // 数据不完整,还需要继续接收
            return false;
        }

        // 提取完整的请求
        request = buffer.substr(0, request_end);
        buffer.erase(0, request_end);
        return true;
    }

    void HandlerHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {   
        // 接收缓冲区,用于处理粘包
        std::string recv_buffer;
        bool should_close = false;

        // 循环处理多个请求(支持长连接)
        while(!should_close)
        {
            // 尝试从缓冲区提取完整请求
            std::string httpreqstr;
            bool has_complete_request = ExtractOneRequest(recv_buffer, httpreqstr);

            // 如果没有完整请求,尝试接收更多数据
            if(!has_complete_request)
            {
                std::string new_data;
                int n = sock->Recv(&new_data);
                
                if(n <= 0)
                {
                    // 连接已关闭或出错
                    should_close = true;
                    break;
                }
                
                recv_buffer += new_data;
                
                // 再次尝试提取完整请求
                has_complete_request = ExtractOneRequest(recv_buffer, httpreqstr);
            }

            if(!has_complete_request)
            {
                // 如果还是没有完整请求,继续接收
                continue;
            }

            std::cout << std::endl << "##########################" << std::endl;
            std::cout << httpreqstr;
            std::cout << "##########################" << std::endl << std::endl;

            // 对字符串请求反序列化
            HttpRequest req;
            if(!req.Deserialize(httpreqstr))
            {
                LOG(LogLevel::WARNING) << "请求解析失败,跳过该请求,继续处理下一个";
                // 跳过这个有问题的请求,继续处理缓冲区中的下一个请求
                continue;
            }

            // 构建http应答
            HttpResponse resp;
            
            // 根据请求决定是否保持连接
            bool keep_alive = req.KeepAlive();
            resp.SetKeepAlive(keep_alive);

            if(req.isInteract())
            {
                // 1.交互
                // _args: username=zhangsan&password=123456
                // _uri: ./wwwroot/login
                
                if(_route.find(req.Uri()) == _route.end())
                {
                    // (1).无对应方法
                    resp.SetTargetFile(webroot + page_404);
                    if (resp.MakeResponse())
                    {
                        std::string response_str = resp.Serialize();
                        sock->Send(response_str);
                    }
                }
                else
                {
                    // (2).有对应方法
                    _route[req.Uri()](req, resp);
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }
            else
            {
                // 2.静态
                resp.SetTargetFile(req.Uri());
                if (resp.MakeResponse())
                {
                    // 所以我们就不在担心,用户访问一个服务器上不存在的资源了(html,css,js,图片,视频这种资源--静态资源!)
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }

            // 如果不保持连接,处理完这个请求后关闭
            if(!keep_alive)
            {
                should_close = true;
            }
        }
    }
    
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client){
            this->HandlerHttpRequest(sock, client);
        });
    }

    void RegisterService(const std::string name, http_func_t h)
    {
        std::string key = webroot + name;
        auto iter = _route.find(key);
        if(iter == _route.end())
        {
            _route.emplace(key, h);
        }
    }

    ~Http()
    {
        
    }
private:
    std::unique_ptr<TcpServer> tsvrp;
    std::unordered_map<std::string, http_func_t> _route;
};
(9). Main.cc
cpp 复制代码
#include "Common.hpp"
#include "Http.hpp"

void Login(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 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)
{
    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 Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

// http port
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    // std::cout << "服务器已经启动,已经是一个守护进程了" << std::endl;
    // 守护进程化
    // daemon(1, 0);
    // Enable_File_Log_Strategy();
    
    uint16_t port = std::stoi(argv[1]);

    
    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->RegisterService("/login",Login);
    httpsvr->RegisterService("/register", Register);
    httpsvr->RegisterService("/vip_check", VipCheck);
    httpsvr->RegisterService("/s", Search);
    
    httpsvr->Start();
    return 0;
}
(10). Makefile
makefile 复制代码
myhttp:Main.cc
    g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
    rm -rf myhttp #/var/log/my.log 
(11). 项目结构
shell 复制代码
Http/
├── Common.hpp
├── html
│   └── 20251027A041HW00
├── Http.hpp
├── Inet_Addr.hpp
├── Log.hpp
├── Main.cc
├── Makefile
├── Mutex.hpp
├── myhttp
├── Socket.hpp
├── TcpServer.hpp
├── Util.hpp
└── wwwroot
    ├── 404.html
    ├── About.html
    ├── board1.html
    ├── Contact.html
    ├── favicon.ico
    ├── image
    ├── index.html
    ├── landscape.html
    ├── Login.html
    ├── Register.html
    ├── robots.txt
    ├── test.html
    └── video
(12). 结果

十一.附录:

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

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

2. HTTP/0.9

(1).核心技术:
  • 仅支持GET请求方法。
  • 仅支持纯文本传输,主要是HTML格式。
  • 无请求和响应头信息。
(2).时代背景:
  • 1991年,HTTP/0.9版本作为HTTP协议的最初版本,用于传输基本的超文本HTML内容。
  • 当时的互联网还处于起步阶段,网页内容相对简单,主要以文本为主。

3. HTTP/1.0

(1).核心技术:
  • 引入POST和HEAD请求方法。
  • 请求和响应头信息,支持多种数据格式(MIME)。
  • 支持缓存(cache)。
  • 状态码(status code)、多字符集支持等。
(2).时代背景:
  • 1996年,随着互联网的快速发展,网页内容逐渐丰富,HTTP/1.0版本应运而生。
  • 为了满足日益增长的网络应用需求,HTTP/1.0增加了更多的功能和灵活性。
  • 然而,HTTP/1.0的工作方式是每次TCP连接只能发送一个请求,性能上存在一定局限。

4. HTTP/1.1

(1).核心技术:
  • 引入持久连接(persistent connection),支持管道化(pipelining)。
  • 允许在单个TCP连接上进行多个请求和响应,提高了性能。
  • 引入分块传输编码(chunked transfer encoding)。
  • 支持Host头,允许在一个IP地址上部署多个Web站点。
(2).时代背景:
  • 1999年,随着网页加载的外部资源越来越多,HTTP/1.0的性能问题愈发突出。
  • HTTP/1.1通过引入持久连接和管道化等技术,有效提高了数据传输效率。
  • 同时,互联网应用开始呈现出多元化、复杂化的趋势,HTTP/1.1的出现满足了这些需求。

5. HTTP/2.0

(1).核心技术:
  • 多路复用(multiplexing),一个TCP连接允许多个HTTP请求。
  • 二进制帧格式(binary framing),优化数据传输。
  • 头部压缩(header compression),减少传输开销。
  • 服务器推送(server push),提前发送资源到客户端。
(2).时代背景:
  • 2015年,随着移动互联网的兴起和云计算技术的发展,网络应用对性能的要求越来越高。
  • HTTP/2.0通过多路复用、二进制帧格式等技术,显著提高了数据传输效率和网络性能。
  • 同时,HTTP/2.0还支持加密传输(HTTPS),提高了数据传输的安全性。

6. HTTP/3.0

(1).核心技术:
  • 使用QUIC协议替代TCP协议,基于UDP构建的多路复用传输协议。
  • 减少了TCP三次握手及TLS握手时间,提高了连接建立速度。
  • 解决了TCP中的线头阻塞问题,提高了数据传输效率。
(2).时代背景:
  • 2022年,随着5G、物联网等技术的快速发展,网络应用对实时性、可靠性的要求越来越高。
  • HTTP/3.0通过使用QUIC协议,提高了连接建立速度和数据传输效率,满足了这些需求。
  • 同时,HTTP/3.0还支持加密传输(HTTPS),保证了数据传输的安全性。

7.爬虫

爬虫,本质就是用http客户端,来模拟浏览器行为,获取指定链接下的网页!
比如我们使用:wget+网页,就可以获取该网页的所有内容。

相关推荐
fuze23332 小时前
解决在虚拟机的ensp中启动路由器,卡0%且出现虚拟机卡死的方法
网络·华为·ensp
希赛网2 小时前
HCIP—Datacom面试技术常问问题
网络·hcip·面试问题·路由交换·stp生成树
捷米研发三部4 小时前
新能源激光焊接工作站西门子1500系列PLC通过Profinet转CANopen智能网关和机器人进行通讯案例
网络·自动化
开开心心就好4 小时前
无需函数:Excel数据筛选工具推荐
xml·网络·pdf·华为云·word·excel·音视频
liebe1*14 小时前
第四章 防火墙设备管理
网络·防火墙
冰糖拌麻子5 小时前
Metasploitable2靶场全部漏洞超详细讲解(含Metasploitable2靶场下载)
网络·安全·网络安全·系统安全·网络攻击模型
liu****5 小时前
18.HTTP协议(一)
linux·网络·网络协议·http·udp·1024程序员节
洛_尘5 小时前
JAVA EE初阶 6: 网络编程套接字
网络·1024程序员节
alwaysuzybaiyy5 小时前
物联网定位技术实验报告|实验一 Wi-Fi指纹定位
网络·人工智能·物联网