当面试时被问对http了解多少的时候(二)—— http 协议基础篇

1,484 阅读15分钟

前言

本篇文章为 http 系列文章中的第二部分,更多文章参考 juejin.cn/post/684490…

TCP/IP 网络分层模型

TCP/IP 当初的设计者提出 “分层” 的概念,把复杂的网络通信划分为多个层次,再给每一个层次分配不同的职责,层次内只需要专心做好自己的事情就好。用 “分而治之” 的思想把一个 “大麻烦” 拆分成了数个 “小麻烦”,从而解决了网络通信的难题。

下图是 TCP/IP 协议栈的层次图:

TCP/IP 协议栈总共有四层,就像搭积木一样,每一层需要下层的支撑,同时又支撑着上层,任何一层被抽掉都可能导致整个协议栈的坍塌。它的层次是 “从下往上” 数的,所以第一层就是最下面的一层。

1. 第一层:“链接层”(link layer)

这一层负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标记网络上的设备,所以有时候也叫MAC层。

2. 第二层:“网际层”或者“网络互连层”(internet layer)

IP 协议就处在这一层。因为 IP 协议定义了 “IP地址” 的概念,所以就可以在 “链接层” 的基础上,使用 “IP 地址” 代替 “MAC地址”,把许许多多的局域网、广域网连接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再 “翻译” 成 MAC 地址就可以了。

3. 第三层:“传输层”(transport layer)

这个层次协议的职责是保证数据在 IP 地址标记的两点之间进行 “可靠” 地传输,是 TCP、UDP 协议工作的层次。

TCP 与 UDP 的区别

  1. 是否有状态: TCP 是一个有状态的协议,需要先与对象建立连接然后才能发送数据,而且保证数据不丢失不重复。而 UDP 则比较简单,它无状态,不用事先建立连接就可以任意发送数据,但不保证数据会发到对方。
  2. 数据的形式: TCP 的数据是连续的 “字节流”,有先后顺序。而 UDP 则是分散的小数据包,是顺序发,乱序收。

4. 第四层:“应用层”(application layer)

由于下面的三层把基础打得非常好,所以在这一层就 “百花齐放” 了,有各种面向具体应用的协议。例如 SSH、HTTP、FTP 等。

MAC 层的传输单位是帧(frame),IP 层的传输单位是包(package),TCP层的传输单位是段(segment),HTTP 的传输单位则是消息或报文(message)。但这些名词并没有什么本质的区分,可以通称为数据包。

OSI 网络分层模型

OSI 的全称是 “开放式系统互联通信参考模型” (Open System Interconnection Reference Model)。

网络模型不是一开始就有的,在网络刚发展时,网络协议是由各互联网公司自己定义的,比如那时的巨头网络公司 IBM、微软、苹果、思科等等,他们每家公司都有自己的网络协议,各家的协议也是不能互通的,那时候大家觉得这是可以的,但对消费者来说这实际上是技术垄断,因为你买了苹果的设备就不能用微软的设备,因为他们的协议不是一样的,没有统一的标准来规范网络协议,都是这些公司的私有协议。 这样大大的阻碍了互联网的发展,为了解决这个问题,国际标准化组织 1984 提出的模型标准,简称 OSI(Open Systems Interconnection Model),这是一个标准,并非实现。 TCP/IP 协议就是基于此模型设计。

OSI 模型分成了七层,部分层次与 TCP/IP 很像,如下图:

1. 物理层:网络的物理形式,例如电缆、光纤、网卡、集线器等

2. 数据链路层,基本相当于 TCP/IP 的链路层

3. 网络层:相当于 TCP/IP 里的网际层

4. 传输层:相当于 TCP/IP 里的传输层

5. 会话层:维护网络中的连接状态,即保持会话和同步

6. 表示层:把数据转换为合适、可理解的语法和语义

7. 应用层:面向具体的应用传输数据

不过国际标准组织心里也很清楚,TCP/IP 等协议已经在许多网络上实际运行,再推翻重来是不可能的。所以,OSI 分层模型在发布的时候就明确地表明是一个“参考”,不是强制标准。

但 OSI 模型也是有优点的。对比一下就可以看出,TCP/IP 是一个纯软件的栈,没有网络应有的最根基的电缆、网卡等物理设备的位置。而 OSI 则补足了这个缺失,在理论层面上描述网络更加完整。

两个分层模型的映射关系

现在我们有了两个网络分层模型:TCP/IP 和 OSI,新的问题又出现了,一个是四层模型,一个是七层模型,这两者应该如何互相映射或者说互相解释呢?

好在 OSI 在设计之初就参考了 TCP/IP 等多个协议,可以比较容易但不是很精确地实现对应关系。

第一层:物理层,TCP/IP 里无对应;

第二层:数据链路层,对应 TCP/IP的链接层;

第三层:网络层,对应 TCP/IP 的网际层;

第四层:传输层,对应 TCP/IP 的传输层;

第五、六、七层:统一对应到 TCP/IP 的应用层。

TCP/IP 协议栈的工作方式

TCP/IP 协议栈是如何工作的呢?

http 协议的传输过程 通过协议栈逐层向下,依次经过应用层、传输层、网际层、链路层,每一层都添加本层的专有数据,层层打包,然后通过下层发送出去。

http 协议的接收数据过程 则是相反的操作,从下往上依次经过链路层、网际层、传输层、应用层,逐层拆包,每层去掉本层的专有头部,上层就会拿到自己的数据。

下层的传输过程对于上层是完全“透明”的,上层也不需要关心下层的具体实现细节,所以就 HTTP 层次来看,它不管下层是不是 TCP/IP 协议,看到的只是一个可靠的传输链路,只要把数据加上自己的头,对方就能原样收到。

你可以把 http 利用 TCP/IP 协议栈传输数据想象成一个发快递的过程。

假如你想把一件毛绒玩具送给朋友,这件玩具就相当于 http 协议里要传输的内容,比如HTML。为了保护玩具,你会给毛绒玩具套一个塑料袋,这个就相当于 http 协议加了一个 http 的协议头。

你把玩具交给快递小哥,为了保护货物,他加了层包装贴了个标签,相当于在 TCP 层给数据再次打包,加上了 TCP 的头。

接着快递小哥下楼,把包裹放进了三轮车里,运到集散点,然后再装进更大的卡车里,相当于在 IP 层、MAC 层对 TCP 数据包加上了 IP头、MAC 头。

之后经过了漫长的运输,包裹到达了目的地,要卸货再放进另一位快递员的三轮车,就是在 IP 层、MAC 层传输后拆包。

快递员到了你朋友的家门口,撕掉标签,去除了 TCP 层的头,你朋友再拆掉塑料包装,也就是 http 头,最后就拿到了毛绒玩具,也就是真正的 HTML 页面。

HTTP协议的核心 —— HTTP报文

http 协议的核心部分是它传输的报文内容。

http 协议在规范文档里详细定义了报文的格式,规定了组成部分、解析规则,还有处理策略等,所以可以在 TCP/IP 层之上实现更灵活丰富的功能,例如连接控制、缓存管理、数据编码、内容协商等。

TCP 报文结构

拿 TCP 报文举例,它在实际要传输的数据之前附加了一个 20 字节的头部数据,存储 TCP 协议必须的额外信息,例如发送方的端口号、接收方的端口号包序号等。

有了这个附加的 TCP 头,数据包才能够正确的传输,到了目的地后把投不去掉,就可以拿到真正的数据。

http 报文结构

http 协议与 TCP 协议类似,同样也需要在实际传输的数据前附加一些头数据,不过与 TCP 不同的是,它是一个 “纯文本” 的协议,所以头数据都是 ASCLL 码的文本,可以很容易的用肉眼阅读,不用借助程序也能够看懂。

http 协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  1. 起始行:描述请求或相应的基本信息
  2. 头部字段集合(header):使用 key - value 的形式更详细的说明报文
  3. 消息正文:实际传输的数据,不一定是纯文本,可以是图片、视频等二进制数据

这其中前两部分 起始行和头部字段经常被合称为 “请求头”“响应头”“header”,消息正文又称为 “实体”“body”

http 协议规定报文必须有 header,但可以没有 body。就像你邮寄快递的时候可以邮寄空包裹一样。但是规定 header 之后必须要有一个 “空行”,也就是 “CRLF”。虽然 http 协议对 header 的大小没有做限制,但是各个 web 服务器都不允许过大的请求头,因为头部太大会占用大量的服务器资源,影响运行效率。

一个完整的 http 报文就像是下图的这个样子,注意在 header 和 body 之间有一个 空行。

http 报文常用头字段

类型区分

http 协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:

  1. 通用字段:在请求头和响应头里都可以出现
  2. 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件
  3. 响应字段:仅能出现在响应头里,补充说明响应报文的信息
  4. 实体字段:它实际上属于通用字段,但专门描述 body 的额外信息

对 http 报文的解析和处理实际上主要就是对头字段的处理,理解了头字段也就理解了 http 报文。

常用的请求字段 Request Headers

  1. Accept:表示客户端期望服务器返回的媒体格式(MIME),比如 text/html、mage/webp、 / 等,通配符*/* 表示任意类型的数据。

  2. Accept-encoding:向服务器申明客户端(浏览器)接收的编码方法,通常为压缩方法,比如 gzip 等。

  3. Accept-Language:向服务器申明客户端接收的语言,比如 en-US、zh-CN 等。

  4. Cache-control:控制浏览器的缓存,比如 private、no-cache 等。

  5. Cookie:告诉服务器关于 Session 的信息,存储让服务器辨识用户身份的信息等。

  6. Refer:是一个链接,告诉服务器该页面是从哪个页面链接进来的。

  7. User-agent:向服务器发送浏览器的版本、系统、应用程序的信息。

  8. Connection:连接状态,比如 keep-alive 指的是保持连接状态,close 指关闭连接状态。

常用的相应字段 Response Headers

  1. Content-length:是一个数字,表示 http 实体消息(body)的长度。
  2. Content-Type:表示服务器向客户端发送的头,代表内容的媒体类型和编码格式的统一应答,比如 text/plain; charset=UTF-8。
  3. Content-Language:该字段是对 Accept-Language 的应答。服务器通过此字段告知客户端返回的 Body 信息的语言是什么。
  4. Date:如果服务器没有缓存,那么Date就是响应的即时生成时间。如果服务器设有缓存,那么Date就是响应内容被缓存的时间。
  5. Expires:设置响应体的过期时间。如果在过期之前进行访问,就会读取缓存中的版本。
  6. Last-modified:设置该文件在服务器端中最后被修改的时间。

在掘金上看到一片很完整的关于 http 头字段的文章,大家有需要的可以参考:juejin.cn/post/684490…

注:附一张常见媒体格式图

http 的请求方法

请求方法是客户端发出的、要求服务器执行的、对资源的一种操作;

请求方法的实际含义是客户端对服务器端发出了一个 “动作指令”,要求服务端对 URI 定位的资源执行这个动作。目前 HTTP/1.1 规定了八种方法,单词都必须是大写的形式。

  1. GET:获取资源,可以理解为读取或者下载数据;
  2. HEAD:获取资源的元信息;
  3. POST:向资源提交数据,相当于写入或上传数据;
  4. PUT:类似 POST;
  5. DELETE:删除资源;
  6. CONNECT:建立特殊的连接隧道;
  7. OPTIONS:列出可对资源实行的方法;
  8. TRACE:追踪请求 - 响应的传输路径。

请求方法是对服务器的“指示”,真正应如何处理由服务器来决定;

既然请求方法是客户端对服务器的一个 “指示”,那么客户端自然没有决定权,服务端掌握着所有的资源,也就有绝对的决策权力。它收到 http 请求报文后,看到里面的请求方法,可以执行也可以拒绝,会通过 http 状态相应码告诉客户端,比如:

  1. 假如你访问的这个文件不存在,那么会返回给你一个 404
  2. 假如你访问的这个文件存在,但是不允许访问,那么可能会给你返回一个 403 forbidden 等

四种常用的请求方法

最常用的请求方法是 GET 和 POST,分别是获取数据和发送数据;

GET/HEAD

  1. GET 方法应该是 http 协议里最知名也是用的最多的请求方法了。它的含义是请求 从服务端获取资源,这个资源可以是文本、页面、图片、视频、HTML页面等
  2. HEAD 方法与 GET 方法类似,也是请求从服务器获取资源,但服务器不会返回请求的实体数据,只会传回响应头。HEAD 方法可以看作是 GET 方法的 “简化版”,因为它的响应头与 GET 完全相同,所以可以用在很多不需要 body 的场景,避免传输 body 数据的浪费。比如想要检查一个文件是否存在,只要发个 head 请求就可以了,没有必要用 GET 把整个文件都取下来。

POST/PUT

GET 和 HEAD 方法是从服务器获取数据,而 POST 和 PUT 方法则是相反操作,向 URI 指定的资源提交数据,数据就放在报文的 body 里。

  1. POST 也是一个经常用到的请求方法,使用频率应该是仅次于 GET,应用的场景也非常多,只要向服务器发送数据,用到的大多数都是 POST。

比如,你在掘金上写了一篇文章,此时点击发布的时候要上传你的文章到掘金的服务器,此时执行的就是一次 POST 请求。

  1. PUT 的作用与 POST 类似,也可以向服务器提交数据,但与 POST 存在微妙的不同,通常 POST 表示的是 “新建”“create” 的含义,而 PUT 则是 “修改”“update” 的含义。可以对比一下 SQL 来加深理解:把 POST 理解成 INSERT,把 PUT 理解成 UPDATE,多次 INSERT 会添加多条记录,而多次 UPDATE 只操作一条记录,而且效果相同。在实际应用中,PUT 用到的比较少,因为它与 POST 的语义、功能太过近似。

请求方法的两个重要概念:安全 和 幂等

  • 安全

    在 http 协议里,所谓的 “安全” 是指请求方法不会 “破坏” 服务器上的资源,即不会对服务器上的资源造成实质的修改。

    按照这个定义,只有 GET 和 HEAD 方法是 “安全” 的,因为它们是 “只读” 操作,无论被访问多少次,服务器上的数据都是 “安全”的。而 POST、PUT、DELETE 等操作会修改服务器上的资源,增加或删除数据,所以是 “不安全” 的。

  • 幂等

    所谓 “幂等” 实际上是一个数学用语,被借用到了 http 协议里,意思是多次执行相同的操作,结果也都是相同的,即多次 “幂” 后结果 “相等”。

    比如 POST 方法多次操作,每次得到的结果不一样,所以不是 “幂等” 的。

http 的响应状态码

在之前的学习中我们提到了客户端向服务端发起请求,服务端会返回状态码告诉客户端,表示了服务器对请求的处理结果。目前 RFC 标准里规定的状态码是三位数, RFC 标准把状态码分成了五类,用数字的第一位表示分类。这五类的具体含义是:

  1. 1xx:提示信息,表示目前是协议处理的中间状态,还需要后续的操作
  2. 2××:成功,报文已经收到并被正确处理
  3. 3××:重定向,资源位置发生变动,需要客户端重新发送请求
  4. 4××:客户端错误,请求报文有误,服务器无法处理
  5. 5××:服务器错误,服务器在处理请求时内部发生了错误。