当你在浏览器中输入 rainbowinpaper.com 并且按下回车之后发生了什么?

但在说之前我们还是要先了解一下网络分层的概念。

网络分层

我们最早接触的网络分层应该是OSI七层参考模型。

OSI 模型的数据传输:

发送包的时候,是一层层加上各种头和数据的,接收到包是一层层解开包,一步步拆开拿下包的各种头最终到了相应的应用就变成了数据的。

实际应用过程中我们用到的还是TCP/IP模型:

TCP/IP协议将应用层、表示层、会话层合并为应用层,物理层和数据链路层合并为网络接口层。

TCP/IP协议不仅仅指的是TCP和IP两个协议,⽽是指的⼀个由FTP,SMTP,TCP,UDP,IP,ARP等等协议构成的协议集合

所以一个HTTP请求会经历的协议大概有:HTTP、TCP、IP、ARP……

1.浏览器解析URL

说URL之前先了解一下它的家庭:

URI = Uniform Resource Identifier 统一资源标志符 URL = Uniform Resource Locator 统一资源定位符 URN = Uniform Resource Name 统一资源名称

详细定义这里不展开说了,老生常谈的东西了,想要具体了解的文章最后有相关文章。

URL 主要由 协议主机端口路径查询参数锚点6部分组成。

值得一提的是浏览器会自动补全URL,比如我输入的是rainbowinpaper.com,实际上访问的是http://rainbowinpaper.com/

浏览器通过 URL 能够知道下面的信息:

1.1 输入的是 URL 还是搜索的关键字?

当协议或主机名不合法时,浏览器会将地址栏中输入的文字传给默认的搜索引擎。大部分情况下,在把文字传递给搜索引擎的时候,URL会带有特定的一串字符,用来告诉搜索引擎这次搜索来自这个特定浏览器。

1.2 转换非 ASCII 的 Unicode 字符

  • 浏览器检查输入是否含有不是 a-z, A-Z0-9, - 或者 . 的字符
  • 这里主机名是 rainbowinpaper.com ,所以没有非ASCII的字符;如果有的话,浏览器会对主机名部分使用 Punycode 编码

1.3 检查 HSTS 列表

  • 浏览器检查自带的“预加载 HSTS(HTTP严格传输安全)”列表,这个列表里包含了那些请求浏览器只使用HTTPS进行连接的网站
  • 如果网站在这个列表里,浏览器会使用 HTTPS 而不是 HTTP 协议,否则,最初的请求会使用HTTP协议发送
  • 注意,一个网站哪怕不在 HSTS 列表里,也可以要求浏览器对自己使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后,网站会返回浏览器一个响应,请求浏览器只使用 HTTPS 发送请求。然而,就是这第一个 HTTP 请求,却可能会使用户受到 downgrade attack 的威胁,这也是为什么现代浏览器都预置了 HSTS 列表。

说回输入URL后,浏览器会解析出协议、主机、端口、路径等信息,并构造一个HTTP请求。

// 请求方法是GET,路径为根路径,HTTP协议版本为1.1 GET / HTTP/1.1

但是这个HTTP请求不一定会发送出去,因为浏览器会先检查本地缓存中与该请求头一致的缓存信息。

2.浏览器缓存

HTTP缓存机制

浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的,后面的HTTP请求会详细介绍HTTP。在分析浏览器缓存机制之前,我们先使用图文简单介绍一下HTTP报文,HTTP报文分为两种:

  1. HTTP请求(Request)报文,报文格式为:请求行 – HTTP头(通用信息头,请求头,实体头) – 请求报文主体(只有POST才有报文主体),如下图:第一行是请求行,后面都是HTTP头。
  2. HTTP响应(Response)报文,报文格式为:状态行 – HTTP头(通用信息头,响应头,实体头) – 响应报文主体,如下图:

第一行是状态行,后面都是HTTP头。

缓存过程分析

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:

由上图我们可以知道

1、浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

2、浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

以上两点结论就是浏览器缓存机制的关键,他确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了。

为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存协商缓存 。

强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。当浏览器向服务器发送请求的时候,服务器会将缓存规则放入HTTP响应的报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Conctrol的优先级比Expires高。

这里我通过一个表格来对比这两个字段的差别:

由于Cache-Control的优先级比expires高,那么直接根据Cache-Control的值进行缓存,max-age=600意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。

了解强制缓存的过程后,我们拓展性的思考一下:

浏览器的缓存存放在哪里,如何在浏览器中判断强制缓存是否生效?

这里我们以博客的请求为例,状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache (内存缓存)和 from disk cache(硬盘缓存)。

在浏览器中验证:

访问rainbowinpaper.com/ –> 200 –> 关闭博客的标签页 –> 重新打开 –> 200(from disk cache) –> 刷新 –> 200(from memory cache)

按照上述顺序,可以观察一下调试网络窗口中查看具体请求信息。

不难发现最后一个步骤刷新的时候,不是同时存在着内存缓存和硬盘缓存吗?

对于这个问题,我们需要了解内存缓存(from memory cache)和硬盘缓存(from disk cache),如下:

  1. 内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取时效性

    1. 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
    2. 时效性:一旦该进程关闭,则该进程的内存则会清空。
  2. 硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。

在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。

到这里,浏览器请求如果没有命中强制缓存,那么浏览器就会去找协商缓存是否存在。

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  1. 协商缓存生效,返回304
  2. 协商缓存失败,返回200和请求结果

同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。

  1. Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。
  2. If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。
  3. Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。
  4. If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。

缓存流程图示

缓存的总结如下:

到这里浏览器缓存的处理已经完成,接下来就要进行网络请求了。

3.DNS解析

DNS协议

由于 IP 地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过域名解析协议(DNS,Domain Name System)来将域名和 IP 地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的 IP 地址数串。将域名映射成 IP 地址称为正向解析,将 IP 地址映射成域名称为反向解析。

DNS 分为查询请求和查询响应,请求和响应的报文结构基本相同。DNS 协议可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。但大多数情况下 DNS 都使用 UDP 进行传输。DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议。

3.1查找DNS缓存

浏览器在这个阶段会检查四个地方是否存在缓存,第一个地方是浏览器缓存,这个缓存就是 DNS 记录。

浏览器会为你访问过的网站在固定期限内维护 DNS 记录。因此,它是第一个运行 DNS 查询的地方。浏览器首先会检查这个网址在浏览器中是否有一条对应的 DNS 记录,用来找到目标网址的 IP 地址。

浏览器第二个需要检查的地方就是操作系统缓存。如果 DNS 记录不在浏览器缓存中,那么浏览器将对操作系统发起系统调用,Windows 下就是 getHostName

浏览器第三个需要检查的地方是路由器缓存,如果 DNS 记录不在自己电脑上的话,浏览器就会和与之相连的路由器共同维护 DNS 记录。

如果与之相连的路由器也没有 DNS 记录的话,浏览器就会检查 ISP 中是否有缓存。ISP 缓存就是你本地通信服务商的本地域名服务器的缓存,因为 ISP 维护着自己的 DNS 服务器,它缓存 DNS 记录的本质也是为了降低请求时间,达到快速响应的效果。一旦你访问过某些网站,你的 ISP 可能就会缓存这些页面,以便下次快速访问(注意:主机和本地域名服务器之间的查询方式是递归查询)。

如果上面四个步骤中都不存在 DNS 记录,那么就表示不存在 DNS 缓存,这个时候就需要发起 DNS 查询,以查找目标网址的 IP 地址(注意:本地域名服务器和其他域名服务器之间的查询方式是迭代查询,防止根域名服务器压力过大)。

3.2 DNS查询

3.2.1 上文我们提出了两个概念:递归查询和迭代查询

1)递归查询:本机向本地域名服务器发出一次查询请求,就静待最终的结果。如果本地域名服务器无法解析,自己会以DNS客户机的身份向其它域名服务器查询,直到得到最终的IP地址告诉本机

2)迭代查询:本地域名服务器向根域名服务器查询,根域名服务器告诉它下一步到哪里去查询,然后它再去查,每次它都是以客户机的身份去各个服务器查询。

自己去查询下一步,叫迭代。 自己帮别人请求查,叫递归。总的来说,递归查询是 客户端只发起一次请求,返回结果只有查询成功或失败;迭代查询会返回最佳查询点或主机地址。 

3.2.2 DNS服务器

首先,本机一定要知道DNS服务器的IP地址,否则上不了网。通过DNS服务器,才能知道某个域名的IP地址到底是什么。DNS服务器的IP地址,有可能是动态的,每次上网时由网关分配,这叫做DHCP机制(后面会介绍);也有可能是事先指定的固定地址。

有一些公网的DNS服务器,也可以使用,其中最有名的就是Google的8.8.8.8。

因为 DNS 是分布式域名服务器,每台服务器只维护一部分 IP 地址到网络地址的映射,没有任何一台服务器能够维持全部的映射关系。

大致来说有三种 DNS 服务器:根 DNS 服务器、 顶级域(Top-Level Domain, TLD) DNS 服务器 和 权限 DNS 服务器 。

  • 根 DNS 服务器 ,截至2021年9月28日上午10:33,根服务器系统由12个独立的根服务器运营方的1404个实例组成。13个根域名服务器由12个独立组织运营。根域名服务器的清单和组织机构可以在 root-servers.org/ 中找到,根域名服务器提供 TLD 服务器的 IP 地址。
  • 顶级域 DNS 服务器,对于每个顶级域名比如 com、org、net、edu 和 gov 和所有的国家级域名 uk、fr、ca 和 jp 都有 TLD 服务器或服务器集群。所有的顶级域列表参见 tld-list.com/ 。TDL 服务器提供了权威 DNS 服务器的 IP 地址。
  • 权限 DNS 服务器,在因特网上具有公共可访问的主机,如 Web 服务器和邮件服务器,这些主机的组织机构必须提供可供访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。一个组织机构的权威 DNS 服务器收藏了这些 DNS 记录。

除了上面三种 DNS 服务器,还有一种不在 DNS 层次结构之中,但是很重要的 DNS 服务器,就是本地域名服务器(也被称为权威域名服务器)。本地域名服务器是电脑解析时的默认域名服务器,即电脑中设置的首选 DNS 服务器和备选 DNS 服务器。常见的有电信、联通、谷歌、阿里等的本地 DNS 服务。

3.2.3 域名的层级

DNS服务器怎么会知道每个域名的IP地址呢?答案是分级查询。

www.bilibili.com为例,www是三级域名,bilibili是二级域名,com是顶级域名。

不仅如此,所有域名的尾部,实际上都有一个根域名。

举例来说,www.bilibili.com真正的域名是www.bilibili.com.root,简写为www.bilibili.com.。因为根域名.root对于所有域名都是一样的,所以平时是省略的。

根域名的下一级,叫做"顶级域名"(top-level domain,缩写为TLD),比如.com.net;再下一级叫做"次级域名"(second-level domain,缩写为SLD),比如www.bilibili.com里面的.bilibili,这一级域名是用户可以注册的;再下一级是主机名(host),比如www.bilibili.com里面的www,又称为"三级域名",这是用户在自己的域里面为服务器分配的名称,是用户可以任意分配的。

总结一下,域名的层级结构如下。

主机名.次级域名.顶级域名.根域名

3.2.4 DNS记录类型

域名与IP之间的对应关系,称为"记录"(record)。根据使用场景,"记录"可以分成不同的类型。

域名与IP之间的对应关系,称为"记录"(record)。根据使用场景,"记录"可以分成不同的类型。

常见的DNS记录类型如下。

(1) A:地址记录(Address),返回域名指向的IP地址。

(2) NS:域名服务器记录(Name Server),返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为IP地址。

(3)MX:邮件记录(Mail eXchange),返回接收电子邮件的服务器地址。

(4)CNAME:规范名称记录(Canonical Name),返回另一个域名,即当前查询的域名是另一个域名的跳转。

(5)PTR:逆向查询记录(Pointer Record),只用于从IP地址查询域名。

下面是我自己的域名rainbowinpaper.com的解析配置:

其中有个TTL的配置,这个就是Time To Live的缩写,顾名思义是指域名解析信息在DNS服务器中缓存的时间。

3.2.5 分级查询

DNS服务器根据域名的层级,进行分级查询。

需要明确的是,每一级域名都有自己的NS记录,NS记录指向该级域名的域名服务器。这些服务器知道下一级域名的各种记录。

所谓"分级查询",就是从根域名开始,依次查询每一级域名的NS记录,直到查到最终的IP地址,过程大致如下。

  1. 从"根域名服务器"查到"顶级域名服务器"的NS记录和A记录
  2. 从"顶级域名服务器"查到"次级域名服务器"的NS记录和A记录
  3. 从"次级域名服务器"查出"主机名"的IP地址
  4. 本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来
  5. 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起来
  6. 至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起来

仔细看上面的过程,你可能发现了,没有提到DNS服务器怎么知道"根域名服务器"的IP地址。回答是"根域名服务器"的NS记录和IP地址一般是不会变化的,所以内置在DNS服务器里面。

根据内置的根域名服务器IP地址,DNS服务器向所有这些IP地址发出查询请求,询问www.baidu.com的顶级域名服务器com.的NS记录。最先回复的根域名服务器将被缓存,以后只向这台服务器发请求,以此类推……

这里值得注意的是,DNS 查询报文会经过许多路由器和设备才会达到根域名等服务器,每经过一个设备或者路由器都会使用路由表 来确定哪种路径是数据包达到目的地最快的选择。这里如果继续深究就会涉及到路由选择算法,关于路由选择算法感兴趣的可以去查找相关资料了解,这里不再往下深挖。

总结

按照我们之前所说,1和8这中间还会查询浏览器缓存、操作系统缓存、路由器缓存,再到本地域名服务器。DNS解析完成后我们获得了目标域名的IP地址,接下来我们就可以建立TCP连接。

4.TCP连接

4.1 socket与流套接字

在网络中采用发送方和接收方的套接字组合来识别端点,套接字唯一标识了网络中的一个主机和它上的一个进程。

套接字(Socket)=(主机IP地址,端口号)

当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket ,请求一个 TCP流套接字 。

这个请求首先被交给传输层,在传输层请求被封装成 TCP 报文段。目标端口会被加入头部,源端口会在系统内核的动态端口范围内选取(Linux下是ip_local_port_range)。

什么是Socket?

举一个例子:A跟B两人聊QQ,QQ是一个独立的应用程序,那么它对应了两个Socket,一个在A的电脑上,一个在B的电脑上。当A对B说:”周末我们去玩吧!“,这句话就是一段数据,这段数据会先储存在A电脑Socket上,当A的QQ和B的QQ连接成功后,A的Socket将这段话的数据发送到B的电脑中,但是B暂时还没看到,因为数据会先存放在B电脑的Socket当中,然后Socket会把数据呈现给B看。

数据传送过程中为什么要多出Socket这样东西?

答:因为不同的应用程序对应不同的Socket,而Socket保证了QQ的数据不会到处乱跑,不会一冲动跑到MSN上去了。因为QQ和MSN两个应用程序的Socket内容是完全不同的。

那么Socket里面到底是什么?

答:Socket套接字地址!套接字地址是一个数据结构,我们仅基于TCP传输协议作为例子。套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4种数据。

这里要提醒一点,Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接,超过 6 个的话剩下的请求就得等待。如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待状态,直至进行中的请求完成。

4.2 TCP协议

传输层有2个协议:

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

UDP(User Datagram Protocol),用户数据报协议。

特点

  1. TCP是面向连接(虚连接)的传输层协议。

  2. 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的。

  3. TCP提供可靠交付的服务,无差错、不丢失、不重复、按序到达。可靠有序,不丢不重。

  4. TCP提供全双工通信。

    发送缓存:准备发送的数据&已发送但尚未收到确认的数据。

    接收缓存:按序到达但尚未被接受应用程序读取的数据&不按序到达的数据。

  5. TCP面向字节流。

    流:流入到进程或从进程流出的字节序列。

    TCP把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。

TCP报文段首部格式:

4.3 TCP协议

TCP连接的建立需要经历三次握手的过程:

  • SYN:synchronous 建立联机

    为1时表示是建立连接的请求,不携带应用层数据。

  • ACK:acknowledgement 确认

    为1时表示是回应SYN为1的请求建立连接的请求。

  • seq:sequence 序号

    是一个随机产生的序号。

  • ack:确认序号

    表示此序号之前的序号都已确认,期望获取此序号开始往后的数据。

  1. 从最开始双方都处于CLOSED状态。然后服务端开始监听某个端口,进入了LISTEN状态,准备好接收来自外部的 TCP 连接,等待客户端连接请求。
  2. 客户端向服务器发出连接请求报文段,请求中首部同步位 SYN = 1,同时选择一个初始序号 sequence ,简写 seq = x。SYN 报文段不允许携带数据,只消耗一个序号。此时,客户端进入 SYN-SEND 状态。
  3. 服务器收到客户端连接后,需要确认客户端的报文段。在确认报文段中,把 SYN 和 ACK 位都置为 1 。确认号是 ack = x + 1,同时也为自己选择一个初始序号 seq = y。服务器端为该TCP连接分配缓存和变量,并向客户端返回确认报文段,允许连接。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。此时,TCP 服务器进入 SYN-RECEIVED(同步收到) 状态。
  4. 客户端在收到服务器发出的响应后,还需要给出确认连接。确认连接中的 ACK 置为 1 ,序号为 seq = x + 1,确认号为 ack = y + 1。TCP 规定,这个报文段可以携带数据也可以不携带数据,如果不携带数据,那么下一个数据报文段的序号仍是 seq = x + 1。客户端为该TCP连接分配缓存和变量,并向服务器端返回确认的确认,可以携带数据。这时,客户端进入 ESTABLISHED (已连接) 状态。
  5. 服务器收到客户的确认后,也进入 ESTABLISHED 状态。

第三次握手的时候,客户端已经处于ESTABLISHED状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。

4.4 四次挥手

当然除了建立TCP连接的三次握手,还有对应的断开TCP连接的四次挥手。当本次连接的数据传输完成,就可以关闭本次连接,关闭的发起可以是服务端也可以是客户端。连接结束后,主机中的“资源”(缓存和变量)将被释放。

这里我们也是直接上图:

  • 终止位FIN:FIN=1时,表明此报文段发送方数据已发完,要求释放连接。
  1. 刚开始双方处于ESTABLISHED状态。客户端发送连接释放报文段,停止发送数据,主动关闭TCP连接。发送后客户端变成了FIN-WAIT-1状态。注意, 这时候客户端同时也变成了half-close(半关闭)状态,即无法向服务端发送报文,只能接收。

    FIN=1,seq=u
    
  2. 服务端接收后向客户端确认,回送一个确认报文段,变成了CLOSED-WAIT状态。客户端接收到了服务端的确认,变成了FIN-WAIT2状态。

    ACK=1,seq=v,ack=u+1
    
  3. 随后,服务器端发完数据,服务端向客户端发送连接释放报文段,自己进入LAST-ACK状态,主动关闭TCP连接。

    FIN=1,ACK=1,seq=w,ack=u+1
    
  4. 客户端收到服务端发来的连接释放报文段后,自己变成了TIME-WAIT状态,然后回送一个确认报文段给服务端,再等到时间等待计时器设置的2MSL(最长报文段寿命)后,连接彻底关闭。

    ACK=1,seq=u+1,ack=w+1
    

    注意了,这个时候,客户端需要等待足够长的时间,具体来说,是2个 MSL(Maximum Segment Lifetime,报文最大生存时间),在这段时间内如果客户端没有收到服务端的重发请求,那么表示 ACK 成功到达,挥手结束,否则客户端重发 ACK。

等待2MSL的意义

如果不等待会怎样?

如果不等待,客户端直接跑路,当服务端还有很多数据包要给客户端发,且还在路上的时候,若客户端的端口此时刚好被新的应用占用,那么就接收到了无用数据包,造成数据包混乱。所以,最保险的做法是等服务器发来的数据包都死翘翘再启动新的应用。

那,照这样说一个 MSL 不就不够了吗,为什么要等待 2 MSL?

1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端

1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达

这就是等待 2MSL 的意义。

到这里我们已经说完了TCP的连接管理,这些是面试考察比较多的,我们可以再往下看看TCP数据包的序号是怎么用的,可靠传输是怎么实现的。

4.5 TCP数据包

4.5.1 TCP数据包大小

以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中,1500字节是负载(payload),22字节是头信息(head)。

IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要20字节,所以 IP 数据包的负载最多为1480字节。

TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节,称为MSS(Maximum Segment Size)。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。

因此,一条1500字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进, 就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求报文可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。

4.5.2 TCP数据包的编号(SEQ)

一个包1400字节,那么一次性发送大量数据,就必须分成多个包。比如,一个 10MB 的文件,需要发送7100多个包。

发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。

第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。

4.5.3 TCP 数据包的组装

收到 TCP 数据包以后,组装还原是操作系统完成的。应用程序不会直接处理 TCP 数据包。

对于应用程序来说,不用关心数据通信的细节。应用程序需要的数据放在 TCP 数据包里面,有自己的格式(比如 HTTP 协议)。

TCP 并没有提供任何机制,表示原始文件的大小,这由应用层的协议来规定。比如,HTTP 协议就有一个头信息Content-Length,表示信息体的大小。对于操作系统来说,就是持续地接收 TCP 数据包,将它们按照顺序组装好,一个数据包都不少。

操作系统不会去处理 TCP 数据包里面的数据。一旦组装好 TCP 数据包,就把它们转交给应用程序。TCP 数据包里面有一个端口(port)参数,就是用来指定转交给监听该端口的应用程序。

4.5.4 TCP可靠传输

网络层:提供尽最大努力交付,不可靠传输。

传输层:使用TCP实现可靠传输。

可靠:保证接收方进程从缓存区读出的字节流与发送方发出的字节流是完全一样的。

TCP实现可靠传输的机制:

  1. 校验:与UDP校验一样,增加伪首部。

  2. 序号:一个字节占一个序号。序号字段指的是一个报文段第一个字节的序号。

  3. 确认

  4. 重传:确认重传不分家,TCP的发送方在规定的时间(重传时间)内没有收到确认就要重传已发送的报文段。

超时重传

  1. TCP采用自适应算法,动态改变重传时间RTTs(加权平均往返时间)。

    重传时间过长怎么办?

冗余ACK(冗余确认)

  1. 每当比期望序号大的失序报文段到达时,发送一个冗余ACK,指明下一个期待字节的序号。

    发送方已发送1、2、3、4、5报文段

    接收方收到1,返回给1的确认(确认号为2的第一个字节)

    接收方收到3,仍返回给1的确认(确认号为2的第一个字节)

    接收方收到4,仍返回给1的确认(确认号为2的第一个字节)

    接收方收到5,仍返回给1的确认(确认号为2的第一个字节)

    发送方收到3个对于报文段1的冗余ACK认为2报文段丢失,重传2号报文段(快速重传)

快速重传只解决了一个问题,就是超时的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题。对于上面的示例来说,是重传2号报文呢还是重传2,3,4,5呢?

因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是6,10,20传来的呢。这样,发送端很有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见,这是一把双刃剑。

另外一种更好的方式叫:Selective Acknowledgment (SACK),这种方式需要在TCP头里加一个SACK的东西。

SACK(选择性确定)

  • 告诉发送方哪些数据丢失,哪些数据已经提前收到
  • 使TCP只重新发送丢失的报文段(比如3),不用发送后续所有的分组(比如4、5)

4.5.5 流量控制

目的是让发送方慢点,要让接收方来得及接收。

TCP利用滑动窗口机制实现流量控制。

在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,即接收窗口rwnd(receive window),发送方的发送窗口取接收窗口rwnd和**拥塞窗口cwnd(congestion window)**的最小值。

发送窗口=Min{接收窗口rwnd,拥塞窗口cwnd}发送窗口=Min{接收窗口rwnd,拥塞窗口cwnd}

A向B发送数据,建立连接时,B告诉A:“我的rwnd=400(字节)”,设每个报文段100B,报文段序号初始值为1。

到这里A不再发送数据,如果B主机再次发送了设置新的接收窗口的响应,如果这个响应丢失了是不是,主机A和主机B就进入了无限等待了呢?

答案是否定的。

TCP为每一个连接设置有一个持续计时器,只要TCP连接的一方受到对方的零窗口通知,就启动持续计时器。

若持续计时器设置的时间到期,就发送一个零窗口探测报文段。接收方收到探测报文段时给出现在的窗口值。

若窗口仍然是0,那么发送方就重新设置持续计时器。这就避免了上述问题中的情况发生。

这里值得注意的是TCP协议通过使用数据链路层中的连续ARQ协议和滑动窗口协议,来保证数据传输的正确性,从而提供可靠的传输和流量控制。

ARQ协议,即自动重传请求(Automatic Repeat-reQuest),是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议连续ARQ协议

简单来说,停止等待ARQ协议就是发送一个报文,等待响应,有响应就发送下一个,没有就超时重传。这种方式的缺点很明显,就是信道利用率很低。

连续ARQ协议则是连续发送多组报文,然后通过累计确认的方式实现可靠传输。

连续ARQ协议通常是结合滑动窗口协议来使用的,发送方需要维持一个发送窗口,如下图所示:

滑动窗口协议在在发送方和接收方之间各自维持一个滑动窗口,发送发是发送窗口,接收方是接收窗口,而且这个窗口是随着时间变化可以向前滑动的。它允许发送方发送多个分组而不需等待确认。TCP的滑动窗口是以字节为单位的。

如下图所示,发送窗口中有四个概念::已发送并收到确认的数据(不在发送窗口和发送缓冲区之内)、已发送但未收到确认的数据(位于发送窗口之内)、允许发送但尚未发送的数据(位于发送窗口之内)、发送窗口之外的缓冲区内暂时不允许发送的数据。

接收窗口中也有四个概念:已发送确认并交付主机的数据(不在接收窗口和接收缓冲区之内)、未按序收到的数据(位于接收窗口之内)、允许的数据(位于接收窗口之内)、不允许接收的数据(位于发送窗口之内)。

规则:

(1)凡是已经发送过的数据,在未收到确认之前,都必须暂时保留,以便在超时重传时使用。

(2)只有当发送方A收到了接收方的确认报文段时,发送方窗口才可以向前滑动几个序号。

(3)当发送方A发送的数据经过一段时间没有收到确认(由超时计时器控制),就要使用后退N帧协议(GBN),回到最后接收到确认号的地方,重新发送这部分数据。

4.5.6拥塞控制

有了流量控制为什么还要拥塞控制?

客户端和服务端建立连接后,双方通过流量控制得到发送窗口与拥塞窗口,但是这个窗口只是针对主机与服务器的性能而决定的,整个网络的拥塞程度才决定了发送出去的数据到底能不能到达,所以可以把拥塞控制看成一个共同维护网络通畅的君子协定。

出现拥塞的条件:对资源需求的总和>可用资源

拥塞控制是一个全局性的过程

  • 涉及到所有的主机、路由器
  • 以及与降低网络传输性能有关的所有因素
  • 是大家共同努力的结果

拥塞控制四种算法:

慢开始和拥塞避免

快重传和快恢复

假设:

  1. 1.数据单方向传送,而另一个方向只传送确认。

  2. 2.接收方总是有足够大的缓存空间,因而发送窗口大小取决于拥塞程度。

    拥塞控制:发送方根据自己估算的网络拥塞程度而设置的窗口值,反映网络当前容量。

慢开始和拥塞避免

开始时发送一个数据包,收到响应后再发送两个(指数增长),当到达阈值ssthresh后,变为加法增长。当出现丢包的时候判断为网络拥塞,再回归到初始的一个数据包,并设置新的阈值,重复之前的操作。

快重传和快恢复

快重传与快恢复是针对上面方法的升级版,区别就是当出现网络拥塞时恢复到新的阈值水平加法增长,而不是初始值。

思考

上面我们说到了HTTP缓存、DNS解析、TCP连接,一般的文章下面可能就是HTTP请求了,当然一般面试最深也就会问到拥塞控制、可靠传输,再往下就是网络工程师才会涉及到的了,但是既然我们想要了解我们数据在网络中如何传输的,那么我们还要继续深挖,我们先思考一个问题:

在一个局域网内,我们的手机、电脑用的IP都是路由器分配的局域网IP,那么当我们的电脑请求一个页面,源IP应该怎么填呢?就算我们有了源IP和目标IP,网络这么大,我们的这个请求是怎么在网络中传输,然后又快又准地到达目标服务器呢?收到响应后我们怎么确定这个响应是应该给手机还是电脑呢?

这么一想,到这里充其量我们也只是了解了网络分层中上层的几个常用的协议而已,想了解一个完整网络回路是怎么一回事,我们还要继续深入。接下来的IP数据报、ARP过程就是脱离了浏览器的计算机网络的知识了,也是一些很有意思的知识,我说的尽量通俗易懂,如果不感兴趣的可以跳到HTTP请求。

5.IP数据报

TCP 报文段被送往网络层,网络层会在其中再加入一个 IP 头部,里面包含了目标服务器的IP地址以及本机的IP地址,把它封装成一个IP 数据报。这里我们来以IPv4为例,来介绍一下IP协议的内容。

网络层中重要的协议:IP协议,是TCP/IP体系中最重要的协议之一

IP协议配套的4个协议是:地址解析协议(ARP)、逆地址解析协议(RARP)、网际控制报文协议(ICMP)、网际组管理协议(IGMP)

5.1 IP数据报格式

  • 版本号:IPv4/IPv6

  • 区分服务类型代表了当前包的处理优先级,内核会按照队列里面的优先级顺序来处理包

  • 标识,标志和片偏移,因为数据报长度如果超过网络的 MTU(最大传送单元) 则必须分片,这些都是和分片规则有关的

  • 生存时间TTL,IP分组的保质期。经过一个路由器-1,变成0则丢弃。我们经常会用ping来查询网络情况,ping使用的是ICMP协议,设置差错报文TTL生存时间就是用来帮助侦查网络情况的

  • 协议是标明的 TCP,UDP 协议,下面是部分协议对应的参数。

  • 协议名 ICMP IGMP TCP EGP IGP UDP IPv6 ESP OSPF
    字段值 1 2 6 8 9 17 41 50 89
最大传送单元MTU:链路层数据帧可封装数据的上限。以太网的MTU是1500字节。
所以当IP数据报超过这个值就会分片处理。

IP分组在互联网上的传递过程

  • 源主机创建分组
  • 目的地址放入分组头部
  • 将分组送往相邻的路由器
  • 路由器收到分组
  • 使用目的地址选择下一个路由器并转发
  • 分组到达能将分组传递给最终目的地路由器

5.2 IPv4地址

IP地址:全世界唯一的32位/4字节标识符,标识路由器主机的接口。

IP地址={<网络号><主机号>}IP地址={<网络号><主机号>}

IPv4分类

(提示:图中A类应该是1~127)

IPv6拓展了解

改革动机:

  1. IPv4 定义的有限地址空间将被耗尽
  2. 设备增多,需要地址配置自动化、简单化
  3. 因特网主干网路由器有维护大型路由表的能力,可支持平面路由机制
  4. IP 层安全需求:IPSec 协议不普及
  5. 更好的实时 QoS(服务质量)支持的需求

改革难点:IP 的依赖性以及 IP 带来的后续惰性导致改变 IP 就会改变整个因特网

IPv6 特性:

  1. 庞大的地址空间以及层次化实现:128 位地址,多级子网地址分配方式
  2. 简化的报头和灵活的扩展
  3. 网络层的认证与加密:全面支持 IPSec,用户的数据加密和校验报文的自主权
  4. QoS 的满足:路由器可以标识同一数据流的分组
  5. 对移动性的更好支持:动态获得呼叫/会话时间内的 IP 地址
  6. 高效的层次寻址及路由结构:聚合分址,分段仅由发送主机进行
  7. 增强的组播与流量控制
  8. 自动配置技术
  9. 用于邻节点交互的新协议:邻居发现协议实现相邻节点(同一链路上的节点)的交互管理

IPv4 向 IPv6 的过渡:

  1. 双栈机制:在一台设备上同时运行 IPv4 和 IPv6 协议栈
  2. 隧道机制:在装有双协议栈的网关站点中,将 IPv6 数据包封装在 IPv4 数据包内,由 IPv4 网络传输,到达隧道端点后解封还原为 IPv6 包
  3. 转换机制:转换网关在 IPv4 和 IPv6 网络之间转换 IP 报头的地址,同时根据协议不同对分组做相应的语义翻译

5.3 子网划分与子网掩码

在这里我们想一个问题,我们常用的192.168.0.1所处的子网,按理说可以有256台主机连入,但是实际情况是我们可能只有几台常用设备会连入,这样就造成了大量的IP浪费,那么如何更加合理地划分子网呢?还有,我们的路由器怎么知道请求的目标IP是外网的还是局域网的地址呢?这时候就要引入子网划分与子网掩码的概念。

先看看前面说的分类的IP地址的弱点:

  1. IP地址空间的利用率有时很低。
  2. 两级IP地址不够灵活。

5.3.1 子网划分

某单位划分子网后,对外仍表现为一个网络,即本单位外的网络看不见本单位内子网划分。

子网掩码

假设我们现在一个子网内只需要四个网络号:192.168.0.0~192.168.0.3

我们把网络号部分标为1,主机号部分标为0,就得到一个子网掩码:255.255.255.252

我们再来看看怎么判断一个IP是否是当前网段:

可以发现主机号部分已经和子网掩码冲突了,所以就可以判定当前IP不属于当前网段。

A类的默认子网掩码,255.0.0.0,一个子网最多可以容纳1677万多台电脑。

B类的默认子网掩码,255.255.0.0,一个子网最多可以容纳6万多台电脑。

C类的默认子网掩码,255.255.255.0,一个子网最多可以容纳254台电脑。

但是子网掩码的写法有点太长了,而且不容易看出网段中主机号的数量,还有一种CIDR的方法来表示:

192.168.0.0/30:斜杠前面的是网络标识/网络地址,30代表着有子网掩码30个1,这样一下就清晰多了,而且写起来也很方便。

那么192.168.0.0/30想要和8.8.8.8通信,首先就会判断网段,用30位掩码来按位与8.8.8.8,得到的网段是8.8.8.8,这明显与我们的网络标识192.168.0.0不同,网络标识不一样,即不在同一网段。

这里我们再来看个例子理解一下:

多说一点

我们平时在运营商那办理宽带,可以把运营商想象成一个超大的路由器,我们办理宽带就相当于去路由器那里申请一个私有的IP,通过运营商的代理,达到了上网的效果。运营商再给每个地区每个用户划分成不同的子网,这样就达到了IP地址的高效利用。

其实这样做也有效地缓解了IPV4地址分配的压力,毕竟IPV4的地址有限,如果每个手机、每台电脑、每个能联网的设备都有一个独一无二的IP地址,那么现有的IPV4地址池是远远不够用的。

下图是我自己家的上网信息:

可以看到我这里的子网掩码是255.255.255.255,这就说明我的IP地址只能为117.60.208.89,想象一下,运营商在给我分配IP的时候创建了一个子网,但是这个子网只能有一个IP地址存在,因为我也只需要一个IP就可以了,这样运营商就可以在可用的IPV4地址池内最大化利用每一个IP地址。需要注意的是这个IP地址不是我路由器的IP,这个IP是我访问因特网对外的IP,可能经过了很多台路由器转换才到了家里。

再看看我的电脑连上WiFi的上网信息:

路由器在分配IP地址的时候就比较大方了,从这里的子网掩码可以看出当前子网里面最多可以有两百多台设备接入了,反正所有的请求都是要经过路由器代理然后都转成运营商给的IP的,所以多分配点浪费了也没关系。

5.4 DHCP协议

动态主机配置协议DHCP是应用层协议,使用客户/服务器方式,客户端和服务端通过广播方式进行交互,基于UDP。简单来说,就是我们日常用的路由器,插上网线或者手机连上无线时,都是通过DHCP协议来分配IP、子网掩码等信息的。

DHCP提供即插即用联网的机制,主机可以从服务器动态获取IP地址、子网掩码、默认网关、DNS服务器名称与IP地址,允许地址重用,支持移动用户加入网络,支持在用地址续租

  1. 主机广播DHCP发现报文。“有没有DHCP服务器呀?”,试图找到网络中的服务器,从服务器获得一个IP地址。
  2. DHCP服务器广播DHCP提供报文。“有!”“有!”“有!”,服务器拟分配给主机一个IP地址及相关配置,先到先得。
  3. 主机广播DHCP请求报文。“我用你给我的IP地址啦?”,主机向服务器请求提供IP地址。
  4. DHCP服务器广播DHCP确认报文。“用吧!”,正式将IP地址分配给主机。

5.5 网络地址转换(NAT)

网络地址转换NAT(Network Address Translation):在专有网连接到因特网的路由器上安装NAT软件,安装了NAT软件的路由器叫NAT路由器,它至少有一个有效的外部全球IP地址。NAT分为 SNAT 和 DNAT,分别对应将私有IP转换成公网IP,以及将公网 IP 转化为私网 IP,一发一收。

当局域网中的电脑发送外网服务器数据请求时,具有NAT功能的路由器将我们的数据请求包中的源IP地址换成外网IP地址。外网服务器收到数据请求后将数据返回到我们的外网IP地址。路由器收到数据后再转交给我们,于是局域网主机就实现了上网功能。

再解释具体点,NAT在进行地址替换时不仅仅包含IP地址,还有端口号。具体说来就是,我们在进行连接外网服务器请求的数据包中,除了源、目的IP地址外,还有源、目的端口号。

其中目的端口号是固定的,比如21或80等等。但源端口号是随机生成的(浏览器在创建TCP连接的时候随机分配的)。当数据包到达进行NAT的设备时,除了私有IP地址会被替换成公网IP地址外,端口号也会被替换成NAT随机生成的端口号。

NAT的端口号和局域网中的主机一一对应,同时NAT设备维护一张端口号和主机对应的表。当外网服务器返回数据到NAT设备时,NAT设备通过返回数据包中的端口号找到局域网中的主机并将数据转发。

前面说到我们自家路由器分配的私网IP,就是经过多层NAT转换,最后到一个拥有公网IP的服务器上再转发到因特网的。

总结

上面说到了IP数据报、IPv4、子网划分、子网掩码、NAT、DHCP,现在我们把这些知识串联起来:

首先一般家用电脑是由路由器连接服务商提供的网线入户后通过DHCP服务分配的IP、子网掩码等信息实现联网的。

TCP数据包被送到网络层交付给IP协议处理为IP数据报,根据TCP数据包的大小和网卡的MTU会被分成多个IP分组,IP分组送到数据链路层被封装成帧,再由物理层转化为数字信号,然后发送给路由器。

路由器通过目标地址与子网掩码相与知道了这个数据是发给内网还是外网的,然后通过NAT替换了数据中的源地址与端口,同样在路由器里面留下了本次请求的映射缓存,以便等到响应时,交付给内网的源主机。

接下来路由器要把数据发送到下一个路由器去,那么问题又来了,发给的下一个路由器应该是哪一个呢?接下来我们说的ARP过程便可以解决这一问题。

6.ARP

P地址属于网络层,但IP地址在传输的时候需要跨越不同的物理网络进行交换,此时如果一台主机要将一个帧发送到另一台主机,光知道其ip地址是不够的,还需要知道其有效的“硬件地址”。

IP数据报接下来会进入链路层,链路层会在封包中加入 frame 头部,里面包含了本地内置网卡的MAC地址以及网关(本地路由器)的 MAC 地址。像前面说的一样,如果内核不知道网关的 MAC 地址,它必须进行 ARP 广播来查询其地址。

ARP协议本质上是解决下一跳走哪的问题。 ARP协议自动运行。

ARP协议

ARP(地址解析协议)提供了一种在32位IPv4地址和以太网的48位MAC地址(硬件地址)之间的映射。

ARP高速缓存:每台主机都有ARP高速缓存,在其中存放了一个IP地址-物理地址的映射表,并且不断动态更新。

由于在实际网络的链路上传送数据帧时,最终必须使用MAC地址。

ARP协议:完成主机或路由器IP地址到MAC地址的映射。ARP协议在TCP/IP模型中属于IP层(网络层),在OSI模型中属于数据链路层。

arp -a [主机地址]:查看ARP缓存 arp -d [主机地址]:删除ARP缓存 arp -s 主机地址 MAC地址:增加一条缓存信息(这是静态缓存,存储时间较久,不同系统的存储时间不同)

ARP协议使用过程:

检查ARP高速缓存,有对应表项则写入MAC帧,没有则用目的MAC地址FF-FF-FF-FF-FF-FF的帧封装并广播ARP请求分组,同一局域网中所有主机都能收到该请求。目的主机收到请求后就会向源主机单播一个ARP响应分组,源主机收到后将此映射写入ARP缓存(10-20min更新一次)。

ARP协议4种典型情况:

  1. 发送方是主机,IP数据报要发送到本网络的另一个主机,这时用ARP找到目的物理地址。
  2. 发送方是主机,IP数据报要发送到另一局域网的一个主机,这是ARP先找到局域网中的一个路由器,剩下工作由路由器完成。
  3. 发送方是路由器,IP数据报要发送到本网络的另一个主机,这时用ARP找到目的物理地址。
  4. 发送方是路由器,IP数据报要发送到另一局域网的一个主机,这是ARP先找到局域网中的另一个路由器,剩下工作由这个路由器完成。

上面四种情况加上arp高速缓存总结一下:

  • 目标IP与自己在同一网段

    • arp高速缓存有目标IP的MAC地址:直接发送到该物理地址。
    • arp高速缓存没有目标IP的MAC地址:发送ARP广播请求目标IP的MAC地址,缓存该MAC地址,然后发数据报到该MAC地址。
  • 目标IP与自己不在同一个网段

    这种情况需要将包发给默认网关,所以主要获取网关的MAC地址。

    • arp高速缓存有默认网关的MAC地址:直接发送IP数据报道默认网关,再由网关转发到外网。
    • arp高速缓存没有默认网关的MAC地址 :还是发送ARP广播请求默认网关的MAC地址,缓存该地址,并且发送数据报到网关。

ARP的产生当然有它的优势所在,全世界存在大量不同的网络,它们使用不同形式的物理地址,这些异构网络要互相通信,统一的IP地址格式加上ARP地址解析协议解决了这个问题,ARP的实现过程是计算机软件自动进行的,用户并未感知到。

到这里为止,我们已经大致了解了我们的数据在整个网络中是如何传输的,中间用到了很多网络协议,当然我只是简单说了其中的一种传输过程,而且我这里也只分析到网络层,其实再往下还有数据链路层的封装成帧和物理层中电信号、光信号的转换,但是对于我们底层知识的构建已经打了一个基础了。接下来我们回归浏览器中的计算机网络的话题,说一下我们本篇文章的最后一个重头协议:HTTP协议。

7. HTTP请求

7.1 万维网概述

万维网WWW(World Wide Web)是一个大规模的、联机式的信息储藏所/资料空间,是无数个网络站点和网页的集合。

通过统一资源定位符URL(唯一标识)访问资源(文字、视频、音频)。

用户通过点击超链接获取资源,这些资源通过超文本传输协议(HTTP)传送给使用者。万维网以客户/服务器方式工作,用户使用的浏览器就是万维网客户程序,万维网文档所驻留的主机运行服务器程序。万维网使用超文本标记语言HTML,使得万维网页面设计者可以很方便地从一个界面的链接转到另一个界面,并能够在自己的屏幕上显示出来。

万维网发源于欧洲日内瓦量子物理实验室CERN,正是WWW技术的出现使得因特网得以超乎想象的速度迅猛发展。这项基于TCP/IP的技术在短短的十年时间内迅速成为已经发展了几十年的Internet上的规模最大的信息系统,它的成功归结于它的简单、实用。在WWW的背后有一系列的协议和标准支持它完成如此宏大的工作,这就是Web协议族,其中就包括HTTP超文本传输协议

在1990年,HTTP就成为WWW的支撑协议。

7.2 HTTP发展历史

HTTP/0.9

  • 只有一个命令GET
  • 响应类型:仅超文本
  • 没有header等描述数据的信息
  • 服务器发送完毕,就关闭TCP连接

HTTP的1991原型版本称为HTTP/0.9。这个协议有很多严重的设计缺陷,只应该用于老客户端的交互。HTTP/0.9只支持GET方法,不支持多媒体内容的MIME类型、各种HTTP首部,或者版本号。HTTP/0.9定义的初衷是为了获取简单的HTML对象,它很快就被HTTP/1.0取代了。

HTTP/1.0

  • 支持POST、HEAD等请求方法
  • 增加status code 和 header
  • 多字符集支持、多部分发送、权限、缓存等
  • 响应:不再只限于超文本(支持MIME类型)

1.0是第一个得到广泛使用的HTTP版本。HTTP/1.0添加了版本号、各种HTTP首部、一些额外的方法,以及对多媒体对象的处理。HTTP/1.0使得包含生动图片的Web页面和交互式表格成为可能,而这些页面和表格促使万维网为人们广泛地接受。

HTTP/1.0+&HTTP/1.1

  • 持久连接。TCP三次握手会在任何连接被建立之前发生一次。最终,当发送了所有数据之后,服务器发送一个消息,表示不会再有更多数据向客户端发送了;则客户端才会关闭连接(断开 TCP)
  • 支持的方法: GET , HEAD , POST , PUT ,DELETE , TRACE , OPTIONS
  • 进行了重大的性能优化和特性增强,分块传输、压缩/解压、内容缓存磋商、虚拟主机(有单个IP地址的主机具有多个域名)、更快的响应,以及通过增加缓存节省了更多的带宽

在20世纪90年代中叶,很多流行的Web客户端和服务器都在飞快地向HTTP中添加各种特性,以满足快速扩张且在商业上十分成功的万维网的需要。其中很多特性,包括持久的keep-alive连接、虚拟主机支持,以及代理连接支持都被加入到HTTP之中,并成为非官方的事实标准。这种非正式的HTTP扩展版本通常称为HTTP/1.0+

HTTP/1.1重点关注的是校正HTTP设计中的结构性缺陷,明确语义,引入重要的性能优化措施,并删除一些不好的特性。HTTP/1.1还包含了对20世纪90年代末正在发展中的更复杂的Web应用程序和部署方式的支持。

HTTP/1.1协议的不足

  • 同一时间,一个连接只能对应一个请求
  • 针对同一个域名,大多数浏览器允许同时最多6个并发连接
  • 只允许客户端主动发起请求
  • 一个请求只能对应一个响应
  • 同一个会话的多次请求中,头信息会被重复传输
  • 通常会给每个传输增加 500~800 字节的开销
  • 如果使用 Cookie,增加的开销有时会达到上千字节

SPDY

如果浏览器是 Google 出品的,它不会使用 HTTP 协议来获取页面信息,而是会与服务器端发送请求,商讨使用 SPDY 协议。

SPDY(读作“SPeeDY”)是Google开发的基于TCP的会话层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。

SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。

SPDY是HTTP/2的前身。2015年9月,Google宣布移除对SPDY的支持,拥抱HTTP/2。

HTTP 2

HTTP/2.0的目标是异步连接多路复用、头部压缩、请求/响应管线化,保持与HTTP 1.1语义的向后兼容性也是该版本的一个关键目标。HTTP实现的瓶颈之一是其并发要依赖于多重连接。HTTP管线化技术可以缓解这个问题,但也只能做到部分多路复用。此外,已经证实,由于存在中间干扰,浏览器无法采用管线化技术。

在开放互联网上HTTP 2.0将只用于https://网址,而 http://网址将继续使用HTTP/1.1,目的是在开放互联网上增加使用加密技术,以提供强有力的保护去遏制主动攻击。

根据 W3Techs 的数据,截至2019年6月,全球有36.5%的网站支持了HTTP/2。

下列两个网站可以进行 HTTP/1.1 和 HTTP/2 速度对比

  • www.http2demo.io/
  • http2.akamai.com/demo

HTTP 2还有很多值得我们了解特性,这里有更加详细的细节说明

HTTP 3

  • QUIC“快速UDP互联网连接”(Quick UDP Internet Connections)
  • 通过高链接利用效率减少RTT,提高数据交互速度
  • 在高效的基础上,保证安全需求
  • 解决当前实际网络环境中的适配问题

HTTP 3的主要改进在传输层上。传输层不会再有繁重的 TCP 连接了。现在,一切都会走 UDP。

互联网通信发展史其实是人类与RTT斗争的历史RTTRound Trip Time的缩写,通俗地说,就是通信一来一回的时间。

TCP建立连接需要1.5RTT

HTTP交互一次需要1RTT

那么基于TCP传输的HTTP通信,一共花费的时间总和: 2.5 RTT

基于TLS的HTTPS请求在TCP握手阶段多了四次,所以连接时间是4.5RTT

在没有持久连接之前,请求一个有图片的页面需要建立两次TCP连接就是9RTT

可以重用TCP连接后多次请求时间就减少了4.5RTT

Google开发的QUIC协议集成了TCP可靠传输机制、TLS安全加密、HTTP /2 流量复用技术,其页面的加载时间为2.5RTT,重连要等待的时间是1RTT

HTTP 3基于QUIC,整体页面加载时间为2RTT

7.3 HTTP协议

超文本传输协议(HTTP):

HTTP是应用层协议,是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。设计HTTP最初的目的是提供一种发布和接收HTML页面的方法,由URI来标识具体的资源,后面用HTTP来传递的数据格式不仅仅是HTML,应用非常广泛。

7.3.1 特点

简单快速客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有 GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记。

无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

7.3.2 HTTP报文格式

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

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

HTTP头字段

头部字段是key-value的形式,key和value之间用“:”分隔,最后用CRLF换行表示字段结束。

之前说到的HTTP缓存机制就说到了几个头字段,如:Cache-Control: max-age=600

这里的key就是Cache-Control,value就是max-age=600

HTTP头字段非常灵活,不仅可以使用标准里的Host、Connection等已有头,也可以任意添加自定义头,这就给HTTP协议带来了无限的拓展可能。

头部字段注意事项

  • 字段名不区分大小写,字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值可以有多个空格。
  • 字段的顺序是没有意义的,可以任意排列不影响语义。
  • 字段原则上不能重复,除非这个字段本身的语义允许,例如:Set-Cookie

常用头字段

HTTP协议中有非常多的头字段,但基本上可以分为四大类:

  • 请求字段:请求头中的头字段,如:HostReferer
  • 响应字段:响应头中的头字段,如:ServerDate
  • 通用字段:在请求头和响应头里都可以出现,如:Content-typeConnection

总结来说,只要符合上述条件的一段字符串,都可以作为请求报文或者响应报文被TCP连接发送出去。浏览器和HTTP服务器就是对HTTP协议的实现与应用的技术,比如之前说到的浏览器缓存机制就是HTTP缓存机制,因为浏览器对HTTP协议中缓存相关字段完成了技术上的实现,所以两者是一个概念。

7.4 HTTPS

HTTPS (HyperText Transfer Protocol Secure),译为:超文本传输安全协议

  • 常称为 HTTP over TLSHTTP over SSLHTTP Secure
  • 由网景公司于1994年首次提出
  • HTTPS的默认端口号是 443 (HTTP是80)

需要注意的是HTTPS协议并没有对HTTP做多大改变,重点在于TCP三次握手时加上了TLS握手。

HTTPS 是在 HTTP 的基础上使用 SSL/TLS 来加密报文,对窃听中间人攻击提供合理的防护。

SSL/TLS 也可以用在其他协议上,比如:

  • FTP → FTPS
  • SMTP → SMTPS

7.4.1 TLS (Transport Layer Security),译为:传输层安全性协议

  • 前身是 SSL (Secure Sockets Layer),译为:安全套接层

历史版本信息

  • SSL 1.0:因存在严重的安全漏洞,从未公开过

  • SSL 2.0:1995年,已于2011年弃用

  • SSL 3.0:1996年,已于2015年弃用

  • TLS 1.0:1999年

  • TLS 1.1:2006年

  • TLS 1.2:2008年

  • TLS 1.3:2018年

7.4.2 OpenSSL

OpenSSL 是SSL/TLS协议的开源实现,始于1998年,支持Windows、Mac、Linux等平台

  • Linux、Mac 一般自带 OpenSSL
  • Windows需要下载安装

常用命令

  • 生成私钥openssl genrsa -out mj.key
  • 生成公钥openssl rsa -in mj.key -pubout -out mj.pem

可以使用 OpenSSL 构建一套属于自己的CA,自己给自己颁发证书,称为“自签名证书”

7.4.3 HTTPS的成本

  • 证书的费用

  • 加解密计算

  • 降低了访问速度

有些企业的做法是:包含敏感数据的请求才使用HTTPS,其他保持使用HTTP。

7.4.4 HTTPS的通信过程

SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

但是,这里有两个问题。

(1)如何保证公钥不被篡改?

解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。

(2)公钥加密计算量太大,如何减少耗用的时间?

解决方法:每一次对话(session),客户端和服务器端都生成一个"对话密钥"(session key),用它来加密信息。由于"对话密钥"是对称加密,所以运算速度非常快,而服务器公钥只用于加密"对话密钥"本身,这样就减少了加密运算的消耗时间。

因此,SSL/TLS协议的基本过程总的可以分为3大阶段

  • TCP的3次握手

  • TLS的连接

    1. 客户端向服务器端索要并验证公钥
    2. 双方协商生成"对话密钥"
  • 双方采用"对话密钥"进行加密的HTTP通信

握手阶段的详细过程

"握手阶段"涉及四次通信,我们一个个来看。需要注意的是,"握手阶段"的所有通信都是明文的。

客户端发出请求(ClientHello)

首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。

在这一步,客户端主要向服务器提供以下信息。

(1) 支持的协议版本,比如TLS 1.0版。

(2) 一个客户端生成的随机数,稍后用于生成"对话密钥"。

(3) 支持的加密方法,比如RSA公钥加密。

(4) 支持的压缩方法。

这里需要注意的是,客户端发送的信息之中不包括服务器的域名。也就是说,理论上服务器只能包含一个网站,否则会分不清应该向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。

对于虚拟主机的用户来说,这当然很不方便。2006年,TLS协议加入了一个Server Name Indication扩展,允许客户端向服务器提供它所请求的域名。

服务器回应(SeverHello)

服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。

(1) 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。

(2) 一个服务器生成的随机数,稍后用于生成"对话密钥"。

(3) 确认使用的加密方法,比如RSA公钥加密。

(4) 服务器证书。

除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供"客户端证书"。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。

客户端回应

客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。

如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。

(1) 一个随机数。该随机数用服务器公钥加密,防止被窃听。

(2) 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。

(3) 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称"pre-master key"。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把"会话密钥"。

至于为什么一定要用三个随机数,来生成"会话密钥",dog250解释得很好:

"不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。

对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。

pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。"

此外,如果前一步,服务器要求客户端证书,客户端会在这一步发送证书及相关信息。

服务器的最后回应

服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的"会话密钥"。然后,向客户端最后发送下面信息。

(1)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。

(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用"会话密钥"加密内容。

7.5 持久连接与非持久连接

其实TCP连接经过三次握手后就可以正常发送HTTP请求了,但是我们再想一个问题:

每发送一个HTTP请求就要建立一个TCP连接吗?

当然,答案是否定的。

HTTP存在持久连接与非持久连接两种状态。

  • 非持久连接:HTTP/1.0 中 的首部字段Connection 默认值为 close,即每次请求都会重新建立和断开 TCP 连接。
  • 持久连接:HTTP/1.1 中 的首部字段Connection 默认值为 keep-alive ,连接可以复用,只要发送端、接收端都没有提出断开连接,则保持TCP连接状态。

HTTP1.1中,所有的连接默认为持久连接,但在HTTP1.0中并未标准化,即使有部分的服务器通过非标准化的手段实现了持久连接,但是服务器端不一定支持持久连接。 持久连接的优点: 减少了TCP连接的重复建立和断开所造成的额外开销,减轻了服务器端的负担,其中减少开销的这部分时间实际上也使HTTP请求和响应更早的结束,提高了web页面的显示速度。

注意是否持久连接不是TCP协议决定的而是上层的HTTP协议去控制的TCP断开的时机实现的。

7.6 并发请求

既然实现了TCP持久连接那我们再思考一个问题:

TCP 连接中多个 HTTP 请求可以并行发送吗?

HTTP/1.1中,单个 TCP 连接在同一时刻只能处理一个请求,即两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。上一个请求得到响应之后,才能发送下一个请求。

但是我们依然有方法实现并发请求:

1.管线化技术的出现,实现了同时发送多个HTTP请求,不必等待上一请求返回响应,但是浏览器默认关闭管线化,原因如下:

  • 一些代理服务器不能正确的处理 HTTP Pipelining
  • Head-of-line Blocking 连接头阻塞:在建立起一个 TCP 连接之后,假设客户端在这个连接连续向服务器发送了多个请求。如果按照标准的话,服务器应该按照收到请求的顺序返回结果,假设服务器在处理首个请求时花费了大量时间,那么后面所有的请求都需要等着首个请求结束才能响应,造成了阻塞。

2.多路复用(Multiplexing)

因为HTTP/1.1中的管线化实际上无法使用,因此在HTTP/2.0中出现了Multiplexing 多路传输特性

  • 在 HTTP/2.0 中,有两个非常重要的概念,分别是帧(frame)和流(stream),帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
  • 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

HTTP与TCP的关系

其实到这里我们可以看出来了,HTTP协议是最上层的应用层协议,它所规定的东西都是针对软件需要实现的,它不需要关注数据在网络中是怎么传输以及怎么确保可靠性与网络拥塞,因为这些问题都由下层TCP去解决,之前我们也提到,TCP有发送队列和接收队列,那么HTTP只需要去操作这两个队列就可以发送或者获取自己想要的数据。TCP三次握手建立的连接并不是真实的物理连接,而是虚连接,连接的本质就是在客户端与服务端开辟本次连接所需要的资源(内存、进程等)。调用Socket利用TCP通过三次握手连接建立后,之前准备好的HTTP请求报文被送入发送队列,接下来就交给了TCP完成后续过程。

所以纵观整个网络分层结构,HTTP与TCP之间的协作如此,TCP与IP之间也是如此,下层为上层提供服务,上层向下层传递包装好的数据,各层次只与目标对应层次相互通讯,对于当前层来说,上层传递下来的数据它是不关心的,也不会解析。

总结

现在再回头看一下文章开头的那张总结图,是不是能理解了网络通讯的过程,还有浏览器到底在这期间做了哪些事情。其实TCP下层的种种协议都已经在操作系统层面实现了,我们能操作的就是TCP以及上层协议,那么关于前端的种种网络优化就从socket开始,不管是缓存的使用还是持久连接,优化的目的也只有一个,就是减小网络延迟。弄清楚这些,可以让我们对底层知识有更全面的认识,从而加深对HTTP的理解与其发展方向的认知。

当然这只是网络请求的部分,浏览器最重要的还是V8引擎的解析以及页面的渲染过程,这部分我们将回归浏览器这个软件本身去作深入研究,那我们下篇文章再见……

问题

如果有什么问题都可以提出来,这里用于记录与解答。

如果没有命中强缓存,紧接着是处理协商缓存还是发送DNS请求?

处理协商缓存。

如果DNS请求命中了浏览器缓存或者计算机缓存,那这次DNS请求还是完整的吗?

这里还没有构建DNS请求。

为什么选择在传输层就将数据“大卸八块”分成多个段,而不是等到网络层再分片传递给数据链路层?

因为可以提高重传的性能。需要明确的是:可靠传输是在传输层进行控制的。如果在传输层不分段,一旦出现数据丢失,整个传输层的数据都得重传。如果在传输层分了段,一旦出现数据丢失,只需要重传丢失的那些段即可。

作者:纸上的彩虹
链接:https://juejin.cn/post/7025956944028532743
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

深入浏览器之页面加载中的计算机网络相关推荐

  1. css 实现页面加载中等待效果

    <!DOCTYPE html> <html> <head> <title>css实现页面加载中,请稍候效果</title><meta ...

  2. android圆形点击效果,Android 三种方式实现自定义圆形页面加载中效果的进度条

    [实例简介] Android 三种方式实现自定义圆形页面加载中效果的进度条 [实例截图] [核心代码] ad376a86-a9aa-49bc-8cea-321bcff2c0c3 └── AnimRou ...

  3. android的提示页面,android 页面加载中,友情提示界面-Fun言

    先是布局页面 android:layout_width="fill_parent" android:layout_height="fill_parent" an ...

  4. Android 三种方式实现自定义圆形页面加载中效果的进度条

    转载:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=76872 一.通过动画实现 定义res/anim/loading.xml如 ...

  5. jquery实现页面加载进度条(转)

    实现原理: 根据页面执行js的顺序将遮罩层和loading图片最先显示出来,等到页面加载完成后,用js控制图片消失.既在网页的头部放置一个文字或者图片的 loading 状态,然后页尾载入一段 JS ...

  6. 在一个html加载多个echarts,Echarts一个页面加载多个图表及图表自适应

    Echarts一个页面加载多个图表及图表自适应 模块化加载 //入口 require.config({ paths: { echarts: 'http://echarts.baidu.com/buil ...

  7. 网页Loading,让页面加载完再显示

    原文链接:http://www.iew3c.com/code-sharing/6672.html 一个真正的网页LOADING,不是装模作样的,网页真正加载完才显示,若没加载完则一直显示进度条,你可以 ...

  8. html页面判断其他div为空,将外部html加载到div中 - 页面加载然后变为空白

    我确信这将会变成一件愚蠢的事情,但是自从我成为JavaScript noob以来,这里就变成了一件愚蠢的事情.将外部html加载到div中 - 页面加载然后变为空白 我想外部HTML内容加载到我的索引 ...

  9. 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 3

    备注: 因为文章太长,所以将它分为三部分,本文是第三部分. 第一部分:深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1 第二部分:深入浅出经典面试题:从浏览器中输入URL ...

最新文章

  1. python和R对dataframe的单列数据进行统计:value_counts、table、unique、nunique、min、max、mean、sort、length、var、quantile、
  2. Pytorch中的variable, tensor与numpy相互转化的方法
  3. 数学 —— 巧用进制
  4. PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性
  5. Java常用的知识点就20_JAVA中一些需要记录的知识点
  6. win10安装windows live writer 错误:OnCatalogResult:0x80190194
  7. HttpServletResponse.getWriter().print乱码,request.getHeader乱码,解决方法
  8. bootice 此功能仅在uefi环境下可用_电脑新手必掌握基础知识:BIOS、EFI与UEFI详解!...
  9. c 语言 求文件大小,C程序中如何读取目录中的文件并判断文件大小等信息
  10. iptables学习笔记:端口转发命令优化
  11. 简述 Linux 文件系统的目录结构
  12. linux环境下的TIME_WAIT和CLOSE_WAIT问题解决方法
  13. 欢迎进入测试day01作业
  14. noip2013提高组初赛(答案+选择题题目+个人分析)
  15. 卧槽,我司电商平台又被攻击,年终奖没了
  16. Oracle SYSAUX 表空间 说明
  17. Java基础进阶Day04
  18. CodRED: A Cross-Document Relation Extraction Dataset for Acquiring Knowledge in the Wild
  19. 如何获取主机名和当前登录用户名
  20. 人脸识别:特征脸(Eigenface)

热门文章

  1. PPT配色中国风——长安十二时辰
  2. 4.19.90内核支持pci=reorder这个内核启动参数吗?
  3. 定制和我一样的博客园主题
  4. 东软始业教育内容提纲(2020)(后附题目)
  5. python做流程图_少儿Python编程_第十四讲:开发游戏
  6. 麦肯锡:推动中国自动驾驶革命的七个关键点
  7. 消散效果shader实践含clip阴影pass——UnityShader学习笔记
  8. c语言七巧板编程实验报告,智力七巧板社团活动记录表
  9. Unity发现字体模糊怎么办
  10. Norton DNS