计算机网络——HTTP协议详解(上)
一、HTTP协议简单介绍
1.1 什么是HTTP协议
HTTP(超文本传输协议)是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它是一种无状态协议,即服务器不会保留与客户端的任何连接状态信息,每个请求都被视为一个独立的事务。
假设你使用Web浏览器(例如Chrome)访问一个网页。当你在浏览器中输入网址并按下"Enter"键时,浏览器会向服务器发送一个HTTP请求。你也可以理解为HTTP协议是在客户端(浏览器)和服务器之间传输数据的基础(约定)。

1.2 再次理解协议
协议是指在通信过程中,参与方之间所达成的一种约定或规范。在网络通信中,协议是用来定义数据传输规则和通信方式的一组规范。
具体来说,基于HTTP协议,它定义了客户端(例如Web浏览器)和服务器之间进行通信时所需遵循的规范。
HTTP协议主要包含以下几个方面的规定:
-
请求方式:HTTP协议定义了一系列的请求方法,如GET、POST、PUT、DELETE等,用于告知服务器进行何种操作。
-
请求和响应格式:HTTP协议规定了请求消息和响应消息的格式。请求消息由请求行、请求头部和请求正文组成,而响应消息由状态行、响应头部和响应正文组成。
-
状态码:HTTP协议定义了一系列的状态码,用于表示服务器对请求的处理结果。例如,200表示成功、404表示资源未找到、500表示服务器内部错误等。
-
头部信息:HTTP协议通过头部字段来携带各种元数据,例如Content-Type用于指示请求或响应的数据类型,Content-Length表示消息正文的长度等。
-
连接管理: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 testchar 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¶m2=value2
解释:
- 协议:URL的第一部分是协议,这里是"http"。协议指定了浏览器与服务器之间的通信规则,常见的有HTTP和HTTPS。
- 域名(或IP地址):在示例中,域名是"www.example.com"。域名是用于标识互联网上特定站点的字符串,也可以使用IP地址来代替。
- 端口号:示例中的端口号是"8080"。默认情况下,HTTP使用80端口,HTTPS使用443端口,但可以使用不同的端口号来访问特定的服务。
- 路径:路径指定了在服务器上资源的位置,示例中是"/path/to/resource"。路径可以是文件、目录或其他资源的位置。
- 查询参数:在示例中,查询参数是"?param1=value1¶m2=value2"。查询参数用于向服务器传递额外的信息,以便执行特定的操作或获取特定的结果。
平时我们俗称的 "网址" ,其实就是说的 URL。 具体也可看下图:

域名就是服务器地址。浏览器会对域名进行解析,解析后就会转换为对应的地址。一个服务器地址,再加上端口号,这就标示了该服务器的唯一进程。端口号后面用 ‘ / ’ 分隔的就是我们所请求资源在该服务器上的路径。
2.3 HTTP 请求格式
服务器收到一个HTTP请求后,请求格式如下:
-
请求行:浏览器发送的第一部分是请求行,它包含了请求的方法(例如GET)、要访问的资源路径(例如/index.html)以及使用的HTTP版本(例如HTTP/1.1)。
-
请求头部:接下来,浏览器发送请求头部,其中包含一些额外的信息,例如浏览器类型、所支持的编码方式、语言首选项等。
-
空行:请求头部之后是一个空行,用于分隔请求头部和请求正文。
-
请求正文(可选):有些请求可能包含请求正文,例如表单数据或上传的文件。
其实我们对照我们刚刚举例的运行结果,也可总结出请求报文的格式,具体如下图:

当服务器拿到请求报文后,会对请求报文进行分析。例如,其中就包含了请求的方法(例如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 testchar 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";elseHttpResponse = "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响应返回给浏览器。
响应状态行:响应的第一部分是状态行,它包含了响应的HTTP版本(例如HTTP/1.1)、响应状态码(例如200表示成功)以及对应的状态消息(例如"OK")。
响应头部:接下来,服务器发送响应头部,其中包含一些额外的信息,例如服务器类型、响应时间、返回的数据类型等。
空行:响应头部之后是一个空行,用于分隔响应头部和响应正文。
响应正文:响应正文包含了服务器返回的实际数据,例如HTML页面、图像、CSS样式表等。
具体也可结合下图理解:

相关文章:
计算机网络——HTTP协议详解(上)
一、HTTP协议简单介绍 1.1 什么是HTTP协议 HTTP(超文本传输协议)是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它是一种无状态协议,即服务器不会保留与客户端的任何连接状态信息,每个请求都被视为一个独立的事务。…...
十九、中介者模式
文章目录 1 基本介绍2 案例2.1 Developer 抽象类2.2 FrontendDeveloper 类2.3 BackendDeveloper 类2.4 Mediator 接口2.5 ProjectManager 类2.6 Client 类2.7 Client 类的运行结果2.8 总结 3 各角色之间的关系3.1 角色3.1.1 Colleague ( 同事 )3.1.2 ConcreteColleague ( 具体的…...
编程参考 - 头文件中使用static inline
在Linux kernel的头文件中,经常使用static inline来声明一个函数。 比如include/linux/delay.h中, static inline void ssleep(unsigned int seconds) { msleep(seconds * 1000); } static Keyword * 范围限制: 当应用于函数或变量时&#…...
Uniapp使用antd组件库
组件库官网 https://www.antdv.com/docs/vue/introduce-cn 安装 在命令行终端输入 npm uni --save ant-design-vue配置 我这里用的是uniapp的vue3版本模板 在main.js里面引入 只要改下面带序号的地方即可 import App from ./App// #ifndef VUE3 import Vue from vue im…...
计算机毕业设计选题推荐-高校实验室管理系统-Java/Python项目实战
✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…...
nest定义响应码message文本
需求 需要对接口的异常响应码,手动设置message文本!!! 例如:项目中使用multer中间件实现文件上传,multer设置了文件大小限制,该中间件校验文件时错误(文件超出)会自动响…...
Java | Leetcode Java题解之第342题4的幂
题目: 题解: class Solution {public boolean isPowerOfFour(int n) {return n > 0 && (n & (n - 1)) 0 && n % 3 1;} }...
【日常开发】java中一个list对象集合 将字段a为 大豆 小麦 玉米等元素放在最前面 并组成新集合
🎈边走、边悟🎈迟早会好 在Java中实现这个功能,可以使用Stream来筛选出符合条件的元素,将它们放在新集合的前面,同时保留其他元素在新集合的后面。以下是如何实现的代码示例: 代码示例: impo…...
C++ 设计模式——原型模式
原型模式 原型模式主要组成部分原型模式的使用步骤原型模式的 UML 图原型模式 UML 图解析优点和缺点适用场景总结 原型模式 原型(Prototype)模式是一种创建型模式。原型模式通过(原型对象)克隆出对个一模一样的对象。实际上,该模式与其说是一种设计模式,…...
【Harmony OS 4.0】待办列表案例
src/main/ets/example1/Models.ets // 定义class类数据模型 export class TaskDataModel {// private 私有属性,在类对象外不允许随意更改数据,必须本地初始化。private tasks: Array<string> [早起晨练, 准备早餐, 阅读名著, 学习ArkTs, 玩游戏…...
快速把文件名统计到excel表的方法
文件名统计到EXCEL表,这似乎很多人都没听说过,因为它与EXCEL表格不沾边,那么这个需求如何实现,用到什么方法,今天给大家介绍一个比较实用的方法,它可以把文件名或文件夹的名快速提取并统计到EXCEL表格上去。…...
开源通用验证码识别OCR —— DdddOcr 源码赏析(一)
文章目录 [toc] 前言DdddOcr环境准备安装DdddOcr使用示例 源码分析实例化DdddOcr实例化过程 分类识别分类识别过程 未完待续 前言 DdddOcr 源码赏析 DdddOcr DdddOcr是开源的通用验证码识别OCR 官方传送门 环境准备 安装DdddOcr pip install ddddocr使用示例 示例图片如…...
上升ECMAScript性能优化技巧与陷阱(下)
4. 深拷贝和浅拷贝的选择不当 在JavaScript中,对象是通过引用传递的,这意味着当你将一个对象赋值给另一个变量时,你实际上是在传递对象的引用,而不是对象本身。这导致了一个常见的问题:当你修改一个对象的属性时&…...
用7EPhone云手机进行TikTok的矩阵运营
“根据市局机构Statista发布的报告显示,截至2024年4月,TikTok全球下载量超过49.2亿次,月度活跃用户数超过15.82亿。TikTok的流量受欢迎程度可想而知,也一跃成为了全球第五大最受欢迎的社交APP。” 人群密集的地方社区也是适合推广…...
谷歌浏览器下载文件被阻止怎么解除
在工作生活中,我们会使用谷歌浏览器下载各种各样的文件,不过偶尔会遇到文件下载被阻止的情况。为了解决这一问题,本文为大家分享了实用的措施建议,一起来了解一下吧。(本文由https://chrome.cmrrs.com/站点的作者进行编…...
apt E: 无法定位软件包 winehq-stable
执行了 添加wine源 wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/jammy/winehq-jammy.sources还需要执行 更新源 apt update...
P2460[SDOI2007] 科比的比赛
第一次做洛谷系列,紧张,请多关照哦 题目传送门:[SDOI2007] 科比的比赛 - 洛谷 思路分析 这道题大概题意是给定我们的主人公 Kobe Bryant 的 mm 个对手,nn 场比赛相对应的获胜概率。求 Kobe Bryant 最大全部获胜概率和打败对手能…...
linux学习--第二天
--Linux文件系统 -显示文件命令 cat 1. cat -b 文件:从1开始对非空输出行编号 2. cat -n 文件:从1开始对所有行编号 3. cat -s 文件:将连续多行空白行合并 more(显示一屏文本内容) 1. more -num 文件ÿ…...
使用 Flask、Celery 和 Python 实现每月定时任务
为了创建一个使用 Flask、Celery 和 Python 实现的每月定时任务,我们需要按照以下步骤进行: 1.安装必要的库 我们需要安装 Flask、Celery 和 Redis(作为消息代理)。我们可以使用 pip 来安装它们: bash复制代码 p…...
【c语言】整数在内存中的储存(大小端字节序)
整数在内存中的储存(大小端字节序) 1.整数在内存中的储存 2.大小端字节序 3.整数在内存中储存例子 4.字节序判断 5.死循环现象 文章目录 整数在内存中的储存(大小端字节序)整数在内存中的储存大小端字节序什么是大小端为什么会有…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
