计算机网络——HTTP协议详解(上)

news2024/9/20 18:37:10

一、HTTP协议简单介绍

1.1 什么是HTTP协议

HTTP(超文本传输协议)是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它是一种无状态协议,即服务器不会保留与客户端的任何连接状态信息,每个请求都被视为一个独立的事务。

假设你使用Web浏览器(例如Chrome)访问一个网页。当你在浏览器中输入网址并按下"Enter"键时,浏览器会向服务器发送一个HTTP请求。你也可以理解为HTTP协议是在客户端(浏览器)和服务器之间传输数据的基础(约定)。

1.2 再次理解协议

协议是指在通信过程中,参与方之间所达成的一种约定或规范。在网络通信中,协议是用来定义数据传输规则和通信方式的一组规范。

具体来说,基于HTTP协议,它定义了客户端(例如Web浏览器)和服务器之间进行通信时所需遵循的规范

  HTTP协议主要包含以下几个方面的规定

  1. 请求方式:HTTP协议定义了一系列的请求方法,如GET、POST、PUT、DELETE等,用于告知服务器进行何种操作。

  2. 请求和响应格式:HTTP协议规定了请求消息和响应消息的格式。请求消息由请求行、请求头部和请求正文组成,而响应消息由状态行、响应头部和响应正文组成。

  3. 状态码:HTTP协议定义了一系列的状态码,用于表示服务器对请求的处理结果。例如,200表示成功、404表示资源未找到、500表示服务器内部错误等。

  4. 头部信息:HTTP协议通过头部字段来携带各种元数据,例如Content-Type用于指示请求或响应的数据类型,Content-Length表示消息正文的长度等。

  5. 连接管理:HTTP协议还定义了一些机制用于管理连接,如持久连接(keep-alive)允许多个请求和响应复用同一个TCP连接,以减少连接建立的开销。

二、HTTP请求

2.1 HTTP的工作过程

我们不妨先来了解一下HTTP的工作过程当你在浏览器中输入一个网址并按下"Enter"键时,浏览器就会向服务器发送一个HTTP请求。请求时,浏览器会给服务器发送请求报文。当服务器收到请求后,它会根据请求报文进行相应的处理,并生成一个HTTP响应(响应报文)返回给浏览器。一个请求再加一个回应,就完成了客户端与服务器的数据传输与交互。

上述讲述的都是概念。下面我们结合一段代码来理解。在看代码之前,强调一下HTTP 是一种应用层协议,是基于 TCP/IP 通信协议来传递数据的。具体也可看下图:

demo代码

首先我们需要基于套接字实现一个服务端HttpServer.hpp:

#include <iostream>
#include <signal.h>
#include "Sock.hpp"
 
class HttpServer
{
public:
    using func_t = std::function<void(int)>;
 
private:
    Sock _serverSock;
    int _sock;
    std::string _ip;
    uint16_t _port;
    func_t _func;
 
public:
    HttpServer(uint16_t port, func_t func, std::string ip = "0.0.0.0")
        :_port(port)
        ,_func(func)
        ,_ip(ip)
    {
        _sock = _serverSock.Socket();
        _serverSock.Bind(_sock, _port, _ip);
        _serverSock.Listen(_sock);
    }
 
    void start()
    {
        signal(SIGCHLD, SIG_IGN);
        while(true)
        {
            std::string clientIP;
            uint16_t clientPort = 0;
            int sockfd = _serverSock.Accept(_sock, &clientIP, &clientPort);
            if(sockfd < 0)
                continue;
            if(fork() == 0)
            {
                close(_sock);
                _func(sockfd);
                close(sockfd);
                exit(0);
            }
            close(sockfd);
        }
    }
 
    ~HttpServer()
    {
        if(_sock >= 0) close(_sock);
    }
};

下面是对套接字操作的封装代码Sock.hpp:


#pragma once
 
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "LogTest.hpp"
 
class Sock
{
private:
    const static int gbacklog = 20;
 
public:
    Sock() {}
 
    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            LogMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        LogMessage(NORMAL, "create socket success, listensock: %d", listensock);
        return listensock;
    }
 
    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LogMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }
    }
 
    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            LogMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
 
        LogMessage(NORMAL, "init server success");
    }
 
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            LogMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }
 
    bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
 
        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

下面我们要做的就是启动服务器,然后用Web浏览器访问我们所启动的服务器,这时候是浏览器向我们所写的服务器发送请求。根据上述HTTP协议的工作过程,这时候会像服务器发送一个请求报文。我们启动服务器HTTPServer.cc:

#include <iostream>
#include <memory>
#include <vector>
#include <fstream>
 
#include "Util.hpp"
#include "HttpServer.hpp"
 
void Usage(std::string name)
{
    std::cout << "\nUsage :" << name << " Port\n" << std::endl;
}
 
void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer << "--------------------\n" << std::endl;
    }
 
}
 
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
 
    std::unique_ptr<HttpServer> httpServer(new HttpServer(atoi(argv[1]),                 HandlerHttpRequest));
    httpServer->start();
    return 0;
}

运行结果:

我们看到确实我们所写的服务器发送了一些信息。该信息就是请求报文。但发现无法打开此页面,是因为我们并没有向浏览器发送任何响应数据。接下来我们详细了解一下HTTP的请求。

2.2 URL介绍

 URL(Uniform Resource Locator)是用于标识和定位互联网上资源的字符串。URL由多个组件构成,包括协议、域名(或IP地址)、端口号、路径和查询参数等。

下面是一个示例URL:http://www.example.com:8080/path/to/resource?param1=value1&param2=value2

解释:

  • 协议:URL的第一部分是协议,这里是"http"。协议指定了浏览器与服务器之间的通信规则,常见的有HTTP和HTTPS。
  • 域名(或IP地址):在示例中,域名是"www.example.com"。域名是用于标识互联网上特定站点的字符串,也可以使用IP地址来代替。
  • 端口号:示例中的端口号是"8080"。默认情况下,HTTP使用80端口,HTTPS使用443端口,但可以使用不同的端口号来访问特定的服务。
  • 路径:路径指定了在服务器上资源的位置,示例中是"/path/to/resource"。路径可以是文件、目录或其他资源的位置。
  • 查询参数:在示例中,查询参数是"?param1=value1&param2=value2"。查询参数用于向服务器传递额外的信息,以便执行特定的操作或获取特定的结果。

平时我们俗称的 "网址" ,其实就是说的 URL。 具体也可看下图:

域名就是服务器地址。浏览器会对域名进行解析,解析后就会转换为对应的地址。一个服务器地址,再加上端口号,这就标示了该服务器的唯一进程。端口号后面用  ‘ / ’ 分隔的就是我们所请求资源在该服务器上的路径

2.3 HTTP 请求格式

服务器收到一个HTTP请求后,请求格式如下:

  1. 请求行:浏览器发送的第一部分是请求行,它包含了请求的方法(例如GET)、要访问的资源路径(例如/index.html)以及使用的HTTP版本(例如HTTP/1.1)。

  2. 请求头部:接下来,浏览器发送请求头部,其中包含一些额外的信息,例如浏览器类型、所支持的编码方式、语言首选项等。

  3. 空行:请求头部之后是一个空行,用于分隔请求头部和请求正文。

  4. 请求正文(可选):有些请求可能包含请求正文,例如表单数据或上传的文件。

其实我们对照我们刚刚举例的运行结果,也可总结出请求报文的格式,具体如下图:

当服务器拿到请求报文后,会对请求报文进行分析。例如,其中就包含了请求的方法(例如GET)、请求的资源路径和协议版本,结合请求报头就会对此进行分析,找到资源并形成响应报文进行返回。其中有许多细节并未解释,后文会详细解释。下面我们先来看一下响应报文的格式。

三、HTTP响应

上述我们例子中并未看到有任何界面。原因就是在于Web浏览器并未收到任何响应。根本在于我们所写的服务器就没有对此进行响应。我们不妨先看一下响应的实例。

3.1 响应demo

HttpServer.cc:

// 一般http都要有自己的web根目录
#define ROOT "./wwwroot" // ./wwwroot/index.html
// 如果客户端只请求了一个/,我们返回默认首页
#define HOMEPAGE "index.html"

void Usage(std::string name)
{
    std::cout << "\nUsage :" << name << " Port\n"
              << std::endl;
}

void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        // std::cout << buffer << "--------------------\n" << std::endl;
    }

    std::vector<std::string> vline;
    Util::cutString(buffer, "\n", &vline);

    std::vector<std::string> vblock;
    Util::cutString(vline[0], " ", &vblock);

    std::string file = vblock[1];
    std::string target = ROOT;

    if (file == "/")
        file = "/index.html";
    target += file;
    std::cout << target << std::endl;

    std::string content;
    std::ifstream in(target);
    if (in.is_open())
    {
        std::string line;
        while (std::getline(in, line))
        {
            content += line;
        }
        in.close();
    }

    std::string HttpResponse;
    if (content.empty())
        HttpResponse = "HTTP/1.1 404 NotFound\r\n";
    else
        HttpResponse = "HTTP/1.1 200 OK\r\n";
    HttpResponse += "\r\n";
    HttpResponse += content;

    // 2. 试着构建一个http的响应
    send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    std::unique_ptr<HttpServer> httpServer(new HttpServer(atoi(argv[1]), HandlerHttpRequest));
    httpServer->start();
    return 0;
}

这里有一个细节:当我们输入URL没有请求资源路径时,浏览器会自动加上一个 ’ / ‘,代表着根目录。这里的根目录与Liunx 的根目录是不同的。一般服务器都会设置默认的Web根目录。这时候就是访问的默认界面。

index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTTP响应</title>
</head>

<body>
    <h3>现在你能够看到我了</h3>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
</body>
</html>

运行结果:

确实有了界面,也正是我们所设计的界面。通过HTTP,客户端可以获取到Web服务器上的各种资源,例如HTML文档、图像、视频、样式表等。

3.2 HTTP响应格式

当服务器收到请求后,它会进行相应的处理,并生成一个HTTP响应返回给浏览器。

  1. 响应状态行:响应的第一部分是状态行,它包含了响应的HTTP版本(例如HTTP/1.1)、响应状态码(例如200表示成功)以及对应的状态消息(例如"OK")。

  2. 响应头部:接下来,服务器发送响应头部,其中包含一些额外的信息,例如服务器类型、响应时间、返回的数据类型等。

  3. 空行:响应头部之后是一个空行,用于分隔响应头部和响应正文。

  4. 响应正文:响应正文包含了服务器返回的实际数据,例如HTML页面、图像、CSS样式表等。

具体也可结合下图理解:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Echarts饼图7.0:图例自定义+取消高亮时放大的状态

1、源代码&#xff1a; let seriseData [{ value: 1048, name: Search Engine },{ value: 735, name: Direct } ] option {color: [#5D9AF1, #D6D6D6],tooltip: {trigger: item,backgroundColor: rgba(0,0,0,0.4),borderColor: transparent,formatter: (item) > {consol…

使用Arduino IDE生成带有bootloader的烧录文件

使用Arduino IDE生成bin&#xff08;烧录&#xff09;文件 1、在“项目”中&#xff0c;选择“导出已编译的二进制文件” 2、在工程目录中&#xff0c;会出现“build”文件夹 3、在build文件夹中&#xff0c;有hex文件&#xff0c;以及包含bootloader的bin和hex文件 bin和h…

ArkUI---Swiper、Grid、List组件简单介绍

前言&#xff1a;ForEach ForEach语法如下&#xff1a; ForEach(arr: Array,itemGenerator: (item: Array, index?: number) > void,keyGenerator?: (item: Array, index?: number) : string > string ) 参数1&#xff1a;数据源&#xff0c;为Array的数组 参数2&am…

【51单片机】让AI识别电路图,帮你进行编码(以51单片机为例)

让AI识别电路图,帮你进行编码&#xff08;以51单片机为例&#xff09; ​ 这里使用的AI大模型使用的是 Copilot。&#xff08;两个前提&#xff1a;1. 科学上网、2. 有微软账号&#xff09; 今天测试了一下Copilot识别图片的能力&#xff0c;能力还是可圈可点的。 首先准备一…

react-antive 項目報錯 [CXX1429] error when building with cmake using

react-antive 項目報錯 [CXX1429] error when building with cmake using修复 错误现场分析原因解决方案举一反三技巧引用参考&#xff08;感谢作者提供思路&#xff09; 错误现场 [CXX1429] error when building with cmake using /Users/sebastiangarcia/Desktop/work/flm/…

基于spring boot的校园商铺管理系统

TOC springboot188基于spring boot的校园商铺管理系统 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这个新的时代&…

springboot颐养居家养老管理系统---附源码19707

摘 要 随着社会的快速发展和人口老龄化趋势的加剧&#xff0c;居家养老已成为越来越多老年人的选择。然而&#xff0c;传统的居家养老方式面临着诸多问题&#xff0c;如服务质量不稳定、信息不对称等。为了解决这些问题&#xff0c;提高居家养老的服务质量和效率&#xff0c;我…

[mysql] 一行变多行

数据表 CREATE TABLE table_main (ID char(36) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,zb_list_str text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci ,kf_list_str text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci,PRIMARY KEY (ID) USI…

在宝塔面板下安装WordPress

宝塔面板是服务器管理好助手&#xff0c;尤其在Linux系统下&#xff0c;提高了管理的可视化&#xff0c;降低了Linux服务器的使用门槛。 WordPress是个非常好的博客系统&#xff0c;由于支持海量主题模板、各种类型的插件&#xff0c;因此已经成为建设各类网站的首选框架。 今…

java 获取request中的json请求体

Java 获取request中的json请求体 简介 在Java开发中&#xff0c;有时我们需要从HTTP请求中获取JSON格式的数据。本文将详细介绍如何在Java中获取request中的json请求体。 流程概览 以下是获取request中的json请求体的整体流程&#xff1a; 步骤 描述 1 获取HttpServletReque…

代码规范 —— 并发编程规范

优质博文&#xff1a;IT-BLOG-CN 【1】【强制】获取单例对象需要保证线程安全&#xff0c;其中的方法也要保证线程安全。 说明&#xff1a; 资源驱动类、工具类、单例工厂类都需要注意。 【2】【强制】创建线程或线程池时请指定有意义的线程名称&#xff0c;方便出错时回溯。…

Adobe Illustrator 2023 for Mac/Win:创意设计的强大引擎

Adobe Illustrator 2023&#xff08;简称AI 2023&#xff09;是一款专为设计师打造的矢量图形编辑软件&#xff0c;无论是Mac还是Windows平台&#xff0c;它都以其卓越的性能和丰富的功能赢得了业界的广泛赞誉。这款软件在设计领域具有举足轻重的地位&#xff0c;为设计师们提供…

算法的学习笔记—删除链表中重复的结点(牛客JZ76)

&#x1f600;前言 在链表操作中&#xff0c;删除重复节点是一个常见的问题。特别是在排序链表中&#xff0c;连续的重复节点不仅会影响链表的结构&#xff0c;还会带来额外的复杂度。本文将介绍一种高效的算法&#xff0c;用于删除链表中所有重复的节点&#xff0c;并保留链表…

GPT-4o mini发布,轻量级大模型如何颠覆AI的未来?

从巨无霸到小巨人&#xff1a;GPT-4o Mini的创新之路 ©作者|潇潇 来源|神州问学 引言 随着人工智能技术的飞速进步&#xff0c;AI领域的竞争日益激烈&#xff0c;大型模型的发布几乎成为常态。然而&#xff0c;这些庞大的模型通常需要大量的计算资源和存储空间&#xff…

如何使用Zabbix API批量修正主机名称

作者 乐维社区&#xff08;forum.lwops.cn&#xff09; 许远 先说为什么要修正&#xff1f; 这其实源自于Ansible安装zabbix agent的一个小Bug&#xff1a;有小伙伴发现&#xff0c;利用ansible批量安装zabbix agent后&#xff0c;zabbix系统上显示的主机名出错了&#xff0c;主…

疫苗发布和接种预约系统

TOC springboot173疫苗发布和接种预约系统 第一章 绪论** 1.1 研究背景 在现在社会&#xff0c;对于信息处理方面&#xff0c;是有很高的要求的&#xff0c;因为信息的产生是无时无刻的&#xff0c;并且信息产生的数量是呈几何形式的增加&#xff0c;而增加的信息如何存储以…

【Next】初识 Next

概述 在Reactr中创建SSR应用&#xff0c;需要调用 ReactDOM.hydrateRoot 函数&#xff0c;而不是 ReactDOM.createRoot。 createRoot:创建一个Root,接着调用其 render 函数将 App 直接加载到页面上hydrateRoot:创建水合 Root, 是在激活的模式下渲染 App 服务端可用 ReactDOM…

如何在 Odoo 16 中修改现有网页

在 Odoo 中&#xff0c;网页是指在 Odoo 网站上可访问的特定页面或 URL。Odoo 中的网页是通过内置网站模块创建和管理的&#xff0c;该模块允许您设计和自定义网页的内容、布局和功能。 Odoo 中的网页是您网站的构建块&#xff0c;可用于呈现信息、展示产品或服务、通过表单收…

「MyBatis」实现留言板

效果预览 界面长这样&#xff0c;每次提交之后&#xff0c;会在下面生成一条记录&#xff0c;刷新页面或者关掉后重新打开&#xff0c;这些记录仍然存在 思路 我们需要在数据库中保存一条一条的消息&#xff0c;那就需要一个类 Data public class MessageInfo {private Integ…

【笔记】Swin-Transformer 的计算量与Transformer的计算量的对比:前者通过使用新颖的窗口技巧,将后者的高阶项变为低阶,大大降低了计算量

补充1&#xff1a; 局部窗口内的自注意力&#xff08;W-MSA&#xff09;: 在 Swin Transformer 中&#xff0c;输入特征图被划分为多个小的窗口&#xff08;例如 7x7 的窗口&#xff09;。在每个窗口内&#xff0c;计算自注意力机制&#xff08;W-MSA, Window-based Multi-Head…