应用层:

HTTP协议:

参考文章:

HTTP报文格式:

HTTP 请求的格式:
  • 请求行(Request Line):

<请求方法> <目标资源路径> <协议版本>

  • 请求头(Request Headers):包含了关于请求的一些附加信息,格式为键值对,每个键值对占据一行。

​ <键1>: <值1>

​ <键2>: <值2>

​ …

空行(空格 + 回车换行):

​ 用于分隔请求头和请求体。

  • 请求体(Request Body):

​ 可选的,适用于一些特定的请求,如 POST 请求,用于传输用户提交的数据。


HTTP 响应的格式:
  • 状态行(Status Line):

<协议版本> <状态码> <状态信息>

  • 响应头(Response Headers):

​ 包含了关于响应的一些附加信息,格式为键值对,每个键值对占据一行。

​ <键1>: <值1>

​ <键2>: <值2>

​ …

  • 空行(空格 + 回车换行):

​ 用于分隔响应头和响应体。

  • 响应体(Response Body):

​ 包含了服务器响应的数据。


HTTP/1.1 里唯一要求必须提供的头字段是 Host,它必须出现在请求头里,标记虚拟主机名。

HTTP请求方法:

仅介绍较为常用的四种请求方法:


GET:

​ 获取资源,可以理解为读取或者下载数据;

GET的含义是请求从服务器获取资源,这个资源既可以是静态的文本、页面、图片、视频,也可以是由 PHP、Java 动态生成的页面或者其他格式的数据。


HEAD:

HEAD方法与 GET 方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返回请求的实体数据,只会传回响应头,也就是资源的“元信息”。

​ HEAD 方法可以看做是 GET 方法的一个“简化版”或者“轻量版”。因为它的响应头与 GET 完全相同,所以可以用在很多并不真正需要资源的场合,避免传输 body 数据的浪费。

应用场景:

  • 想要检查一个文件是否存在,只要发个 HEAD 请求就可以了,没有必要用 GET 把整个文件都取下来。

  • 要检查文件是否有最新版本,同样也应该用 HEAD,服务器会在响应头里把文件的修改时间传回来。


POST:

​ 只要向服务器发送数据,用的大多数都是 POST。


PUT:

​ PUT 的作用与 POST 类似,也可以向服务器提交数据,但与 POST 存在微妙的不同,通常 POST 表示的是“新建”“create”的含义,而 PUT 则是“修改”“update”的含义。

​ 在实际应用中,PUT 用到的比较少。而且,因为它与 POST 的语义、功能太过近似,有的服务器甚至就直接禁止使用 PUT 方法,只用 POST 方法上传数据。

HTTP URI和URL:

URI(Uniform Resource Identifier):统一资源标识符

URI有两种实现方式:URL和URN

URL = Uniform Resource Locator 统一资源定位符

  • 以**位置**定位资源

URN = Uniform Resource Name 统一资源名称

  • 以**名称定**位资源
URI基本组成:

scheme://authority紧跟着path?query

  • scheme:表示协议名称

  • authority:资源所在的主机名(或域名) + 端口号(如果要使用协议的默认端口,则端口号可以省略)

  • path:标记资源所在的位置的路径,以/开头

  • query:多个key=value的键值对形式,键值对间使用&分隔

🌟在 URI 里对“@&/”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP 报文后会无法正确处理。

HTTP响应码(状态码):

详细状态码记录

  • 1××:提示信息,表示目前是协议处理的中间状态,还需要后续的操作;

  • 2××:成功,报文已经收到并被正确处理;

    • 200 OK:表示请求成功,服务器成功处理了请求。
  • 3××:重定向,资源位置发生变动,需要客户端重新发送请求;

    • 301 Moved Permanently:永久重定向,表示请求的资源已经被移动到了新的URL。

    • 302 Found:临时重定向,表示请求的资源暂时被移动到了新的URL。

  • 4××:客户端错误,请求报文有误,服务器无法处理;

    • 400 Bad Request:表示客户端的请求语法错误或无效,服务器无法理解。
    • 401 Unauthorized:表示请求需要身份验证,客户端需要提供有效的凭据。
    • 403 Forbidden:表示服务器理解请求,但拒绝执行,客户端没有访问权限。
    • 404 Not Found:表示服务器无法找到请求的资源。
  • 5××:服务器错误,服务器在处理请求时内部发生了错误。

    • 500 Internal Server Error:表示服务器内部错误,无法完成请求。

    • 502 Bad Gateway是指作为代理或网关的服务器从上游服务器接收到一个无效的响应,导致无法完成请求。

    • 503 Service Unavailable:表示服务器暂时无法处理请求,通常是由于服务器过载或维护。

HTTP连接:

短连接:

​ 每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关闭连接。

​ 建立、关闭连接消耗大,效率低!

长连接:

​ 一个连接执行多个请求

​ 通过设置过期时间(多少时长内未收到请求,则关闭连接)、设置最大请求执行次数来关闭连接,防止过多的空闲长连接占用服务器资源。

队头阻塞问题:

​ 长连接将其要执行的任务从任务队列中取出依次执行,如果正在执行的请求长时间不能执行成功,这会导致后面排队的请求长时间等待,这是我们不希望发生的。

​ 对策:

每个请求都建立多个长连接,防止单一长连接如果阻塞导致效率低(每个请求都发送到多个长连接中,会导致服务器压力过大,不是个好办法)
通过域名分片,将服务器上的资源分类后分散到不同的子服务器中,分摊请求的负担。

HTTP状态:

登录一个页面,服务器怎么知道你是谁?

​ 常用于保存一些让服务器辨识的信息,从而使服务器认识客户端

Cookie由浏览器维护,常见存储方式:
  • 内存存储——非持久化存储
  • 磁盘存储——持久化存储

服务器发送Set-Cookie命令,客户端按要求创建Cookie,之后每次访问该服务器都携带Cookie信息,以便于让服务器辨识。

使用场景:
  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)
缺点:

​ Cookie中信息为明文,容易产生安全问题。

Session:

​ Session机制将用户的所有活动信息、上下文信息、登录信息等都存储在服务端,只是生成一个唯一标识ID发送给客户端,后续的交互将没有重复的用户信息传输,取而代之的是唯一标识ID,暂且称之为Session-ID吧。

​ 当浏览器下次请求别的资源的时候,浏览器会将sessionID放置到请求头中,服务器接收到请求后解析得到sessionID,服务器找到该id的session来确定请求方的身份和一些上下文信息

​ 这个sessionID可以放在Cookie中传输,Session与Cookie之间的关系仅此而已。

如果浏览器被禁用了Cookie,服务端还可以直接将sessionID附加在URL后作为参数传输。—-URL重写

缺点:
  • 海量用户的巨大存储压力
  • 分布式系统中信息共享问题
  • 每个用户的信息存储在服务器,不可避免存在一些 分布式系统的常见的问题
Token:

​ Token是令牌的意思,由服务端生成并发放给客户端,是具有时效性的一种验证身份的手段。

Token避免了Session机制带来的海量信息存储问题,也避免了Cookie机制的一些安全性问题,属于典型的时间换空间的思路。在现代移动互联网场景、跨域访问等场景有广泛的用途。

​ 常使用 **JWT ** (JSON Web Token)

image-20231007152733214

Token设计:

image-20231007153219035

  • Header:
    • 令牌头:存放令牌类型、使用的签名(加密)算法
  • Payload:
    • 负载:存放真正需要向服务端传递的信息——(针对认证问题,负载至少应该包含能够告知服务端“这个用户是谁”的信息,针对授权问题,令牌至少应该包含能够告知服务端“这个用户拥有什么角色/权限”的信息。)
      • iss(Issuer):签发人。
      • exp(Expiration Time):令牌过期时间。
      • sub(Subject):主题。
      • aud (Audience):令牌受众。
      • nbf (Not Before):令牌生效时间。
      • iat (Issued At):令牌签发时间。
      • jti (JWT ID):令牌编号。
      • 还可以放一些自定义信息
      • ⚠️JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
  • Signature
    • 签名:对上述的 (Header以及Payload) 根据header中指定的签名算法 与密钥进行签名运算得到的结果。
1
2
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)
//HMACSHA256为一种签名算法

header和payload的信息不做加密,只做一般的base64编码

客户端第一次请求服务器:未携带Token,服务端根据客户端传来的信息进行身份认证,若验证成功,则生成Token发回给用户使用。

客户端携带Token后请求服务器:服务端收到token后剥离出header和payload获取算法、用户、过期时间等信息,然后根据自己的加密密钥来生成signature,并与客户端的sign进行一致性验证。

这样就实现了用CPU加解密的时间换取存储空间,干净利落,同时服务端密钥的重要性就显而易见,一旦泄露整个机制就崩塌了,这个时候就需要考虑HTTPS了。

优点:
  • Token可以跨站共享,实现单点登录
  • Token机制无需太多存储空间,Token包含了用户的信息,只需在客户端存储状态信息即可,对于服务端的扩展性很好
  • Token机制的安全性依赖于服务端加密算法和密钥的安全性
缺点:
  • 安全性隐患:如果Token被泄露或者被劫持,攻击者可能会利用Token来冒充用户或者进行非法操作。为了减少这种风险,需要采取适当的加密和保护措施,例如使用HTTPS进行通信以及使用安全的Token存储和传输方式。
  • 难以撤销和失效:一旦Token被颁发,除非过期时间到期或者手动撤销,否则Token将一直有效。这可能导致一些安全问题,特别是在Token被偷窃或者用户权限需要立即撤销的情况下。
  • 难以管理和维护:在大规模系统中,需要对大量的Token进行管理和维护。这可能包括Token颁发、撤销和更新等操作,需要投入相应的资源和工具进行管理,以确保系统的安全性和可靠性。
  • 性能问题:Token验证可能需要进行密钥解密、签名验证和状态检查等操作,这些操作可能会增加系统的负载并且消耗一定的计算资源。特别是在高并发的情况下,可能会对性能产生一定影响。
总结:

​ Session方式将用户的身份信息存放于服务端数据库中,不可避免会带来分布式问题,而Token通过服务器使用自己私有的密钥来对用户信息进行加密生成Token给客户端,通过对客户端发来的Token进行再次运算(检查收到的和算出的签名是否一致)来判断该Token是否有效,即用户是否有权限,避免了使用分布式数据库。

HTTPS:

参考文章:

​ 由于HTTP协议在传输信息时有以下三个问题:数据遭受监听数据易被篡改易被冒充

所以可以在HTTP与TCP之间加入TLS协议,来解决上述问题。HTTPS = 有TLS保障的HTTP

TLS 协议是如何解决 HTTP 的风险的呢?
  • 信息加密: HTTP 交互信息是被加密的,第三方就无法被窃取;
  • 校验机制:校验信息传输过程中是否有被第三方篡改过,如果被篡改过,则会有警告提示;
  • 身份证书:证明淘宝是真的淘宝网;

TLS协议一般使用RSA算法实现密钥交换,简而言之:

​ 客户端会生成随机密钥,并使用服务端的公钥加密后再传给服务端。根据非对称加密算法,公钥加密的消息仅能通过私钥解密,这样服务端解密后,双方就得到了相同的密钥,再用它加密应用消息。

具体流程图示🌟:

image-20231009150718081

pre-master : 客户端密钥

什么是数字证书,证书中有什么?
  • 公钥;
  • 持有者信息;
  • 证书认证机构(CA)的信息;
  • CA 对这份文件的数字签名及使用的算法;
  • 证书有效期;
  • 还有一些其他额外信息;

​ 数字证书的作用,是用来认证公钥持有者的身份,以防止第三方进行冒充。说简单些,证书就是用来告诉客户端,该服务端是否是合法的,因为只有证书合法,才代表服务端身份是可信的。

HTTP版本:

参考文章:

image-20231018151006871

HTTP2:
  • 第一点,对于常见的 HTTP 头部通过静态表和 Huffman 编码的方式,将体积压缩了近一半,而且针对后续的请求头部,还可以建立动态表,将体积压缩近 90%,大大提高了编码效率,同时节约了带宽资源。

​ 不过,动态表并非可以无限增大, 因为动态表是会占用内存的,动态表越大,内存也越大,容易影响服务器总体的并发能力,因此服务器需要限制 HTTP/2 连接时长或者请求次数。

  • 第二点,HTTP/2 实现了 Stream 并发,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响。不同的 Stream ID 可以并发,即使乱序发送帧也没问题,比如发送 A 请求帧 1 -> B 请求帧 1 -> A 请求帧 2 -> B 请求帧2,但是同一个 Stream 里的帧必须严格有序。

​ 另外,可以根据资源的渲染顺序来设置 Stream 的优先级,从而提高用户体验。

  • 第三点,服务器支持主动推送资源,大大提升了消息的传输性能,服务器推送资源时,会先发送 PUSH_PROMISE 帧,告诉客户端接下来在哪个 Stream 发送资源,然后用偶数号 Stream 发送资源给客户端。

​ HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似很完美了,但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。

HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。

HTTP/3:

HTTP/2 虽然具有多个流并发传输的能力,但是传输层是 TCP 协议,于是存在以下缺陷:

  • 队头阻塞,HTTP/2 多个请求跑在一个 TCP 连接中,如果序列号较低的 TCP 段在网络传输中丢失了,即使序列号较高的 TCP 段已经被接收了,应用层也无法从内核中读取到这部分数据,从 HTTP 视角看,就是多个请求被阻塞了;
  • TCP 和 TLS 握手时延,TCP 三次握手和 TLS 四次握手,共有 3-RTT 的时延;
  • 连接迁移需要重新连接,移动设备从 4G 网络环境切换到 WiFi 时,由于 TCP 是基于四元组来确认一条 TCP 连接的,那么网络环境变化后,就会导致 IP 地址或端口变化,于是 TCP 只能断开连接,然后再重新建立连接,切换网络环境的成本高;

HTTP/3 就将传输层从 TCP 替换成了 UDP,并在 UDP 协议上开发了 QUIC 协议,来保证数据的可靠传输。

QUIC 协议的特点:

  • 无队头阻塞,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,也不会有底层协议限制,某个流发生丢包了,只会影响该流,其他流不受影响;
  • 建立连接速度快,因为 QUIC 内部包含 TLS 1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与 TLS 密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。
  • 连接迁移,QUIC 协议没有用四元组的方式来“绑定”连接,而是通过「连接 ID 」来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本;

DNS协议:

参考文章:

​ 域名 <===> ip地址的映射表

域名服务器的层级:

根域名服务器(Root DNS Server):

​ 管理顶级域名服务器,返回“com”、“net”、“cn”等顶级域名服务器的 IP 地址;

顶级域名服务器(Top-level DNS Server):

​ 管理各自域名下的权威域名服务器,比如 com 顶级域名服务器可以返回 apple.com 域名服务器的 IP 地址;

权威域名服务器(Authoritative DNS Server):

​ 管理自己域名下主机的 IP 地址,比如 apple.com 权威域名服务器可以返回 www.apple.com 的 IP 地址。

对于一段域名:最右边的被称为“顶级域名”,然后是“二级域名”,层级关系向左依次降低。

如果你要访问“www.apple.com”,就要进行下面的三次查询:

  1. 访问根域名服务器,在其中可以查询到“com”顶级域名服务器的地址;

    • 所有域名都是 根域名子域名,根域名为 .root,一般简写为 . 但是所有域名的根域名都是一样的,所以我们一般都省略根域名不写,DNS解析时计算机会自动为我们加上根域名。

      例如: www.baidu.com 的完整域名应该是 www.baidu.com. 或者 www.baidu.com.root

  2. 访问“com”顶级域名服务器,可以查询到“apple.com”域名服务器的地址;

  3. 最后访问“apple.com”域名服务器,就得到了“www.apple.com”的地址!

  • 许多大公司、网络运行商都会建立自己的 DNS 服务器,作为用户 DNS 查询的代理,代替用户访问核心 DNS 系统。这些“野生”服务器被称为“非权威域名服务器”,可以缓存之前的查询结果,如果已经有了记录,就无需再向根服务器发起查询,直接返回对应的 IP 地址。
  • 计算机操作系统也会对DNS查询的结果进行缓存,以提升查询效率
  • 操作系统里还有一个特殊的“主机映射”文件,通常是一个可编辑的文本,在 Linux 里是“/etc/hosts”,在 Windows 里是“C:\WINDOWS\system32\drivers\etc\hosts”,如果操作系统在缓存里找不到 DNS 记录,就会找这个文件。
image-20231116092454845

域名的应用:

  1. “重定向“,可用于系统维护时切换ip,让同一域名指向不同的主机,保证服务的连续性。
  2. 用域名进行开发,各种服务的ip不用写死
  3. 负载均衡

迭代查询:

迭代查询的过程其实就是:当域名服务器收到迭代查询请求报文时,需求给出主机“你下一步应当向哪一个域名服务器进行查询”的建议,然后由主机进行下一步的查询,当返回内容即没有确切的结果也没有下一步的建议时,DNS失败。

递归查询:

所谓递归查询就是:如果主机所询问的本地域名服务器不知道被查询的域名的 IP地址,那么本地域名服务器就以 DNS 客户的身份,向其它根域名服务器继续发出查询请求报文(即替主机继续查询),而不是让主机自己进行下一步查询。因此,递归查询返回的查询结果是所要查询的 IP地址 ,或者是报错,表示无法查询到所需的 IP地址

🌟DNS查询的完整过程是怎么样的?

  1. 浏览器将会检查缓存中有没有这个域名对应的解析过的 IP 地址,如果有该解析过程将会结束。浏览器缓存域名也是有限制的,包括缓存的时间、大小,可以通过 TTL 属性来设置。
  2. 如果用户的浏览器中缓存中没有,操作系统会先检查自己本地的 DNS 解析器缓存和 hosts 文件是否有这个网址映射关系,如果有,就先调用这个 IP 地址映射,完成域名解析。
  3. 如果都没有,会找 TCP/IP 参数中设置的首选 DNS 服务器,我们叫它本地 DNS 服务器。通过递归查询的方式向本地 DNS 服务器发起查询,如果本地 DNS 服务器中有 A记录 或者该域名的映射缓存,则返回
  4. 如果都没有,本地域名服务器会开始迭代查询的过程,会先向 13 台根域名服务器查询该域名,根域名服务器会返回该域名的顶级域名服务器的 IP 地址,也就是 NS 记录。然后本地域名服务器再向顶级域名服务器发起查询,顶级域名服务器返回二级域名服务器的 NS 记录,重复这个过程直到返回 A 记录为止,最后把 A 记录中的 IP 地址返回给主机

RPC(远程调用):

参考资料:

  • 既然有 HTTP 协议,为什么还要有 RPC?

  • 纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义消息边界。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。

  • RPC 本质上不算是协议,而是一种调用方式,而像 gRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC 有很多种实现方式,不一定非得基于 TCP 协议

  • 从发展历史来说,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。

  • RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP/1.1 性能要更好,所以大部分公司内部都还在使用 RPC。

  • HTTP/2.0HTTP/1.1 的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。

传输层:

TCP:

网络层: