目录
一、流程的大致过程
二、流程的详细分析
1. 浏览器先分析超链接中的URL
2. DNS解析
3. 建立TCP连接
建立连接(三次握手)
HTTP中的请求报文
4. 浏览器发送HTTP请求
5. 服务器处理请求并发送响应
HTTP的响应报文
6. 浏览器接收响应
7. 渲染与展示
JavaScript 执行
8. 关闭连接
关闭连接(四次挥手)
总结
这是一个比较常见且经典的问题。当我们在浏览器输入一个网址,然后按下回车,接下来浏览器显示了页面。网速好的话这之间可能就一秒,但在这一秒内到底发生了什么?
用户在网页上输入一个网址,当用户输入网址并按下回车键后,整个页面响应的流程涉及多个步骤。
由于这个问题涉及知识很多,而且我也想把这些知识都一起整理一下,所以本文采用总-分-总的的行文思路,具体内容如下。
一、流程的大致过程
本文主要内容是试图记录一个完整 Web 请求的详细过程,从用户在浏览器中输入 URL 地址说起,然后浏览器如何找到服务器地址的过程,并发起请求;分析请求在达反向代理服务器内部处理过程;最后到请求在服务器端处理完成后,浏览器渲染响应页面过程。
大致过程如下:
二、流程的详细分析
以下是该流程的详细分析。
1. 浏览器先分析超链接中的URL
用户在浏览器的地址栏中输入一个网址(URL),浏览器先分析超链接中的URL。
首先,URL是由协议、主机和端口(默认为80)以及文件名三部门构成。我们可以这样理解:URL就是我们输入的网址,而网址里面含有域名。比如www.baidu.com/veal98
是一个网址,而 www.baidu.com
就是服务器的域名。
URL的各元素组成为:
而这个URL请求的目标服务器上的文件路径就是:
首先,浏览器做的第一步就是会解析URL得到里面的参数,分析域名是否规范,并将域名和需要的请求的资源分离开来,从而了解需要请求的是哪个服务器,请求的是服务器上的什么资源等等。
2. DNS解析
封装好HTTP请求报文之后,就需要获取目标服务器的ip地址(ip包里面有ip地址),虽然解析得到了域名,按理浏览器应该已经知道了目标服务器是谁了。但是实际上,域名并不是目标服务器真正意义上的地址,互联网上每一台计算机都被全世界唯一IP地址标识着,但是IP地址并不方便记忆,所以才设计出了域名。但是虽然域名容易被用户所接受和使用,但是计算机只能识别纯数字构成的IP地址,不能直接读取域名。
所以如果只是知道域名也不知道这个请求会被发送到哪里去。那么就需要解析域名获取目标服务器的IP地址。
接下来,浏览器向DNS请求解析请求解析IP地址。
DNS解析是将域名转换为对应的IP地址的过程。
- 浏览器解析URL:当用户在浏览器中输入网址时,浏览器会解析URL,提取出其中的域名部分。
- 本地DNS缓存查找:浏览器首先会检查本地DNS缓存,看是否已经缓存了该域名的IP地址。如果有缓存,浏览器会直接使用缓存的IP地址,跳过后续的DNS查询过程。
- 向本地DNS服务器发送查询请求:如果本地DNS缓存没有找到对应的IP地址,浏览器会向本地DNS服务器发送一个DNS查询请求。
- 递归查询过程:本地DNS服务器会先检查自己的缓存,如果有缓存,则返回缓存的IP地址。如果没有缓存,本地DNS服务器会向根DNS服务器发起查询请求。
- 根DNS服务器查询:根DNS服务器是整个DNS系统的最高层级,负责管理顶级域名服务器的IP地址。本地DNS服务器会向根DNS服务器发送查询请求,询问它所管理的顶级域名服务器的IP地址。根DNS服务器收到查询请求后,会返回负责相应顶级域名(如com、net、org等)的顶级域名服务器的IP地址。
- 顶级域名服务器查询:本地DNS服务器会向负责相应顶级域名的顶级域名服务器发送查询请求。顶级域名服务器会返回负责解析该域名的权限域名服务器的IP地址。
- 权限域名服务器查询:本地DNS服务器再次向权限域名服务器发送查询请求。权限域名服务器会查找并返回该域名对应的IP地址。
- 返回结果:本地DNS服务器将获取到的IP地址返回给浏览器。
- 建立TCP连接:浏览器使用获取到的IP地址与目标服务器建立TCP连接,开始进行后续的HTTP请求和响应过程。
这是它们的层次结构图,
下图汇总了上面所说的 DNS 解析过程:
递归查询和迭代查询的区别:DNS客户端和本地名称服务器是递归查询,而本地名称服务器和其他名称服务器之间是迭代查询。
DNS递归名称解析:
在DNS递归解析中,当所配置的本地名称服务器解析不了时,后面的查询工作是由本地名称服务器替代DNS客户端进行的(以本地名称服务器为中心),只需要本地名称服务器向DNS客户端返回最终的查询结果即可。
DNS迭代名称解析(查询):迭代查询的所有查询工作全部是DNS客户端自己进行(以DNS客户端自己为中心),在其中一条件满足之后就会采用迭代名称解析方式。
- 在查询本地名称服务器时,如果客户端的请求报文中没有申请使用递归查询,即在DNS请求报头部的RD字段没有置1。相当于说“你都没有主动要求我为你进行递归查询,我当然不会为你工作了”。
- 客户端在DNS请求报文中申请使用的是递归查询(也就是RD字段置1了),但在所配置的本地名称服务器上是禁用递归查询(DNS服务器一般默认支持递归查询的),即在应答DNS报文头部的RA字段置0。
其中,DNS 使用的是 UDP 协议,也就是说上面各种请求的转发,都是基于 UDP 这个无连接协议的。
进行DNS优化可以提高网站的访问速度和性能,下面是一些常见的DNS优化方法:
- 使用快速的DNS解析服务提供商:选择一个可靠且性能良好的DNS解析服务提供商,如Google Public DNS、Cloudflare DNS等。这些服务提供商通常具有全球分布的服务器,能够提供较快的响应时间。
- 设置适当的DNS缓存时间:在DNS记录中设置合理的TTL(Time to Live)值,以控制DNS缓存的存活时间。较短的TTL值能够更快地更新DNS缓存,但可能增加了DNS查询的次数。
- DNS扁平化:对于大型网络环境,可以使用DNS扁平化技术来减少DNS查询的层级。通过将域名的子域名分布到不同的DNS服务器上,可以减少DNS查询链的长度,提高解析速度。
- DNS负载均衡:通过使用多个DNS服务器并将流量均匀分配给它们,可以提高系统的可用性和性能。负载均衡可以避免单点故障,并将请求分散到多个服务器上,减轻服务器的负荷。
- DNS安全增强:实施DNSSEC(DNS Security Extensions)可以提供对DNS查询的身份验证和数据完整性保护,防止DNS欺骗和劫持攻击。
- 预热DNS缓存:在系统启动或重启后,DNS缓存可能为空,需要重新进行解析。为了加快解析速度,可以通过预热DNS缓存,提前进行一些常见域名的解析,以便用户首次访问时能够更快地获取到解析结果。
3. 建立TCP连接
拿到域名对应的 IP 地址后,User-Agent(一般是指浏览器)会以一个随机端口(1024 < 端口 < 65535)向服务器的 WEB 程序发起 TCP 的连接请求。
这里还涉及 ARP地址解析协议:是根据 IP 地址获取物理地址 (MAC 地址) 的一个协议。
当一个数据帧经过多次路由到达目的网络时,路由器只能知道其数据帧中的目的 IP 地址,而不知目标主机的硬件地址,网络层使用的是 IP地址,但是在实际网络链路上传送数据帧时,最终必须使用该网络的硬件地址,此时需要目的主机的硬件地址,就要使用 ARP 来获取到对应 IP 地址主机的物理地址。
这个连接请求(原始的 Http 请求经过 TCP/IP 4层模型的层层封包)到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的 TCP/IP 协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达WEB程序,最终建立了TCP/IP的连接。
TCP(Transmission Control Protocol)连接是一种面向连接的、可靠的网络传输协议,它通过三次握手建立连接,保证数据传输的可靠性和正确性。
建立连接(三次握手)
在建立TCP连接时,客户端首先向服务器发送一个SYN(Synchronize Sequence Numbers)包,其中SYN标志位被设置为1,同时随机生成一个初始序列号(ISN)。服务器收到SYN包后,向客户端发送一个ACK(Acknowledgment)包,其中ACK标志位被设置为1,同时将确认序列号(ACK number)设置为客户端的ISN+1,并随机生成一个自己的ISN。此时服务器进入SYN_RECEIVED状态。
客户端收到服务器的ACK包后,还需要向服务器发送一个ACK包,确认服务器的ISN。该ACK包的ACK标志位被设置为1,确认序列号为服务器的ISN+1。当服务器收到该ACK包后,连接建立成功,进入ESTABLISHED状态。
TCP三次握手完成之后,浏览器与服务器之间就会建立起一个可靠的虚拟通道,于是客户端和服务器可以开始进行数据传输,浏览器就可以发送自己的HTTP请求了。
HTTP 请求报文或者响应报文在 TCP 连接通道上进行传输的时候,由于这些报文比较大,为了更容易和准确可靠的传输,TCP 会将 HTTP 报文按序号分割成若干报文段并加上 TCP 首部,分别进行传输。接收方在收到这些报文段后,按照序号以原来的顺序重组 HTTP 报文。
也就是说,每个TCP报文段都有一个序列号和一个确认序列号。发送方通过序列号标识传输的数据,接收方通过确认序列号确认收到的数据。
HTTP中的请求报文
客户端(浏览器)向web服务器发送的请求报文。报文的所有字段都是ASCII码。请求报文中可以携带数据,也可以不携带数据。 请求报文由请求行、请求头部、空行和请求包体 4 个部分组成。
1)首部行:用来说明浏览器、服务器或报文主体是一些信息。
请求的时候需要携带数据告诉服务器自己需要访问的东西。携带的数据是通过头部字段(header)去访问。
2)请求报文里的字段:
- ①Host:表示访问的主机。表示访问的那个网址---》URL(通过域名dns或者ip地址)
- ②connection:close或者keepalive 表示当前是连接还是断开的状态。
- ③user-agent :表示使用的是什么用户代理---》浏览器(本质上使用的是什么浏览器):请求字段,用来描述发起请求的客户端,比如是Chrome、Mozilla、Safari,或者是spider。
- ④Accept-language.cn :客户端可接受的自然语言。
- ⑤Accept-encode.cn:客户端可接受的编码压缩格式;接收的编码、接收的类型的文件(表示的是压缩)
- ⑥cookie:存储于客户端扩展字段,向同一域名的服务端发送属于该域的cookie;
3)请求行:方法、URL、版本
- 常见的方法为:GET、POST、PUT、DELETE等
- 常见的版本为:HTTP1.0、HTTP1.1、HTTP2.0等
4. 浏览器发送HTTP请求
发送HTTP请求是客户端向服务器请求获取资源的过程。HTTP请求是整个过程中最核心的步骤,它决定了我们最终看到的网页内容。
HTTP连接建立之后,浏览器会向服务器发送HTTP请求,请求服务器返回网页内容。
发送HTTP请求的过程通常是这样的:
- 浏览器向服务器发送一个 HTTP 请求报文,其中包含了请求的资源路径、请求方法等信息。
- 服务器接收到请求报文后,会返回一个 HTTP 响应报文,其中包含了资源内容、响应状态码等信息。
- 浏览器接收到响应报文后,会对资源内容进行解析,并将其显示在浏览器中。
(1)建立TCP连接:HTTP使用TCP作为传输协议,在发送HTTP请求之前,需要先建立与服务器的TCP连接。这通常涉及到解析目标服务器的域名并获取其IP地址,然后通过TCP三次握手建立连接。
(2)构造请求: 浏览器向目标服务器发送HTTP请求。
HTTP请求由请求行、请求头和请求体组成。请求行包括请求方法(如GET或POST)、请求的URL路径和HTTP协议版本。请求头包含一系列的键值对,用来传递附加信息,如请求的主机、用户代理等(如User-Agent、Accept等)。请求体主要用于POST请求,用于传递表单数据或其他需要提交的数据。
HTTP请求报文的例子为:
GET HTTP://facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
DontTrackMeHere: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]
对于这个封装,其中涉及到计算机网络中的知识。就是说发送端在层与层之间传输数据的时候,每经过一层必定会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据的时候,没经过一层就会把该层对应的首部消息消除。
(3)发送请求:将构建好的请求报文发送给服务器。可以使用HTTP库或网络编程语言提供的方法来发送HTTP请求,如Python的requests库、Java的HttpURLConnection类等。
5. 服务器处理请求并发送响应
(4)解析请求:服务器首先需要解析收到的HTTP请求报文,包括请求行、请求头和请求体。通过解析可以获取请求的方法、URL路径、请求头信息以及请求体中的数据(对于POST请求)等。
(5)路由和处理逻辑:根据请求的URL路径和请求方法,服务器需要确定对应的处理逻辑。
这通常涉及到路由的选择和请求处理器的调用。服务器可以根据不同的URL路径和请求方法将请求分发给不同的处理器或控制器进行处理。
(6)执行业务逻辑:服务器根据请求的处理逻辑执行相应的业务操作。
这可能包括从数据库中获取数据、处理用户输入、调用其他服务等。服务器会根据具体的业务需求来执行相应的操作,生成需要返回给客户端的数据或页面。
(7)生成响应:在执行完业务逻辑后,服务器会根据请求的处理结果生成HTTP响应。
响应包括响应状态行、响应头和响应体。响应状态行包含响应的HTTP协议版本、状态码和状态消息。响应头包含服务器返回的一系列键值对信息,如内容类型、内容长度等。响应体包含服务器返回的实际数据,如HTML页面、JSON数据等。
当然,HTTP 响应报文也要经过和 HTTP 请求报文一样的过程。
HTTP的响应报文
响应报文是从Web服务器到客户机(浏览器)的应答。报文的所有字段都是ASCII码。由状态行、响应头部、空行和响应包体 4 个部分组成。
HTTP/1.1 200 OK Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Expires: Sat, 01 Jan 2000 00:00:00 GMT P3P: CP=”DSP LAW” Pragma: no-cache Content-Encoding: gzip Content-Type: text/html; charset=utf-8 X-Cnection: close Transfer-Encoding: chunked Date: Fri, 12 Feb 2010 09:05:55 GMT 2b3Tn@[…]
1)状态行:常见的状态码
- 200 : 响应成功。
- 301:永久重定向,请求资源的url已永久更改,在响应中给出了新的url。
- 302:临时重定向。
- 304:资源未改变,客户端可以使用缓存版本。
- 404:网页不存在。
- 502:bad gateway ,服务器作为网关或代理,从上游服务器接收到无效响应。
- 500:内部服务器错误,无法完成请求。
2)响应头部:响应字段:
- Date:日期,通用字段,但通常出现在响应头里,表示 HTTP 报文创建的时间,客户端可以使用这个时间再搭配其他字段决定缓存策略。
- Server:Server 响应报头域包含了服务器用来处理请求的软件信息及其版本。它和 User-Agent 请求报头域是相对应的,前者发送服务器端软件的信息,后者发送客户端软件(浏览器)和操作系统的信息。
- cache-control: max-age=30。
- content-type:传递过来的类型。
(8)返回响应:服务器将生成的HTTP响应发送回客户端。通过TCP连接,服务器使用HTTP协议将响应报文发送给客户端。响应报文会经过网络传输到客户端,供客户端解析和处理。
6. 浏览器接收响应
(9)接收响应:客户端接收到服务器发送的HTTP响应报文。客户端会解析响应报文,提取响应状态行、响应头和响应体中的数据。
(10)解析响应:客户端会解析服务器返回的响应报文。解析包括解析响应状态行、响应头和响应体中的数据。解析过程可以使用相应的HTTP库或框架来完成,这些工具提供了对响应报文的解析方法。
(11)提取数据:根据解析的结果,客户端可以提取出响应中的相关数据。例如,从响应头中获取内容类型、编码方式等信息,从响应体中获取具体的数据内容。
(12)数据处理:根据需要,客户端可能需要对提取到的数据进行进一步处理。例如,如果响应体中是JSON格式的数据,客户端可以将其转换为对象或进行其他操作。
如果HTML页面中引用了其他资源(如CSS文件、JavaScript文件、图片等),浏览器会对这些资源发送额外的HTTP请求。这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等…。浏览器通常会并发请求这些资源,以提高页面加载速度。
但不像动态页面那样,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取。服务器的响应中包含了静态文件保存的期限信息,所以浏览器知道要把它们缓存多长时间。还有,每个响应都可能包含像版本号一样工作的ETag头(被请求变量的实体值),如果浏览器观察到文件的版本 ETag信息已经存在,就马上停止这个文件的传输。
下面是一个完整的请求,
7. 渲染与展示
(13)渲染页面:HTTP请求完成后,对于HTML页面或其他需要展示的内容,客户端会将数据渲染到页面上。这涉及到将数据填充到指定的HTML元素中,或者使用模板引擎等工具进行页面渲染。
渲染网页的过程通常是这样的:
- 浏览器会解析 HTML 代码,并将其转换为 DOM 树。
- 浏览器会根据 DOM 树,将网页中的元素显示在浏览器中。
- 浏览器会根据 CSS 代码,,生成CSS规则树,对网页中的元素进行美化。
- 浏览器会根据 JavaScript 代码,对网页中的元素进行动态交互。
对于获取到的HTML、CSS、JS、图片等等资源,浏览器通过解析HTML,生成DOM树,解析CSS,生成CSS规则树,然后通过DOM树和CSS规则树生成渲染树。渲染树与DOM树不同,渲染树中并没有head、display为none等不必显示的节点。
在解析CSS的同时,可以继续加载解析HTML,但在解析执行JS脚本时,会停止解析后续HTML,这就会出现阻塞问题,关于JS阻塞相关问题,这里不过多阐述。
根据渲染树布局,计算CSS样式,即每个节点在页面中的大小和位置等几何信息。HTML默认是流式布局的,CSS和js会打破这种布局,改变DOM的外观样式以及大小和位置。这时就要提到两个重要概念:repaint和reflow。
- repaint:屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。
- reflow: 意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。这就是Reflow,或是Layout。
有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。
有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
JavaScript 执行
JavaScript 的执行过程是单线程的,也就是说,同一时间只能执行一个任务。如果遇到耗时的任务,会导致 JavaScript 引擎阻塞,影响用户体验。为了避免这种情况,可以通过异步编程、Web Worker 等方式来实现并发执行。
- 语法解析:JavaScript 引擎会首先对代码进行词法分析和语法分析,生成抽象语法树(AST)。
- 预编译:在 JavaScript 代码执行之前,JavaScript 引擎会对变量和函数声明进行预编译,即将变量和函数声明提升到作用域的顶部,并赋初始值(undefined)。注意,变量赋值、函数表达式等不会被提升。
- 执行代码:按照代码的书写顺序,依次执行代码。执行过程中,变量会被赋予新的值,函数会被调用,并传递参数。
- 作用域链:每个执行上下文都有一个作用域链,用于解析变量名和函数名。当访问变量或函数时,JavaScript 引擎会沿着作用域链向上查找,直到找到对应的变量或函数。
- 垃圾回收:在 JavaScript 执行过程中,引擎会自动进行垃圾回收,即回收不再使用的变量和对象的内存空间。
渲染网页是整个过程中很耗时的步骤,如果网页内容较多,渲染时间可能会比较长。
(14)显示内容:最后,客户端会将渲染好的页面或其他内容展示给用户。这可以通过浏览器显示页面,或者在移动应用程序中以合适的方式呈现。
渲染完成之后,一个完整的网页就出现在我们面前了。
8. 关闭连接
这一步不是所有的网页都会这么做,例如网页版微信就没有关闭 TCP 连接,因为微信上别人可以随时发消息给你,实际上别人先把消息发送到了微信服务器,微信服务器再通过 TCP 链接,把消息推送到你的屏幕上。
试想一下,如果网页版微信关闭了 TCP 连接会怎样?结果是:你不刷新网页,就永远收不到消息了。同时,如果你频繁的发消息给别人,那么就在频繁的创建连接,关闭连接,这是很消耗资源的。所以微信就干脆不关闭 TCP 连接,这样微信服务器就可以给我们的浏览器发消息。
下图是一次 Http 请求报文头部信息,其中 Connection: keep-alive 意味着这次请求结束后不会关闭 TCP 连接。
当然不是所有的 HTTP 请求都没有关闭连接,例如一篇博文,浏览器收到数据显示就可以了,没有那么多动态数据,我看完就关了,这时就应该关闭 TCP 连接,当然这还是取决于请求的服务器。说了这么多,还没说关闭连接。
关闭连接(四次挥手)
关闭 TCP 连接专业点说叫做“四次挥手”,与 TCP 建立连接的“三次握手”相对应。
当客户端和服务器之间数据传输完成,要断开连接时,需要通过四次握来断开,四次握手就是四次交流,客户端和服务器通过这四次交流确定没什么新数据要传了,而且之前的数据都已经传完了,最后就可以断开连接了。
当客户端或服务器需要断开连接时,它会向对方发送一个FIN(Finish)包,其中FIN标志位被设置为1。接收方收到FIN包后,会向发送方发送一个ACK包,确认收到了FIN包。此时接收方进入CLOSE_WAIT状态,发送方进入FIN_WAIT_2状态。
当接收方也需要断开连接时,它会向发送方发送一个FIN包,发送方收到FIN包后会发送一个ACK包进行确认。此时发送方进入TIME_WAIT状态,并等待一段时间后进入CLOSED状态,接收方收到确认后直接进入CLOSED状态。
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个TCP连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
至此,一个 Web 请求的大致流程差不多就是这样。
总结
用户输入网址后的整个流程包括DNS解析、TCP连接建立、HTTP请求与响应、页面渲染等多个步骤。详细说就是用户在网页上输入网址后,浏览器首先通过DNS解析将域名转换为IP地址,然后建立与目标服务器的TCP连接。接着,浏览器发送HTTP请求,服务器处理请求并生成HTTP响应,包含所请求的网页内容。浏览器接收响应后,解析HTML并渲染页面,同时请求其他静态资源(如CSS、JavaScript和图片)。最后,所有资源加载完成后,用户能够看到完整的网页。整个过程涉及多个步骤,包括解析、连接、请求、响应和渲染。
每个步骤都涉及复杂的网络通信和数据处理,确保用户能够快速、准确地访问所需的网页。