了解了HTTP协议的基础,这次就来聊一聊从用户输入URL到页面加载完成的过程中都发生了什么事情?
用户输入URL之后大致发生了以下几件事:
-
浏览器查找域名的IP地址。这一步包括DNS具体的查找过程,包括:浏览器缓存 -> 系统缓存 -> 路由器缓存……
-
浏览器向WEB服务器发送一个HTTP请求:三次握手、传送数据、四次挥手;
-
服务器的永久重定向响应:返回真正访问的地址;
-
浏览器跟踪重定向地址:另发一个HTTP请求;
-
服务器处理请求;
-
服务器返回一个HTTP响应;
-
浏览器显示HTML页面:解析HTML以构建DOM树 –> 构建渲染树 –> 布局渲染树 –> 绘制渲染树;
-
浏览器发送请求,获取嵌入在 html 中的资源(如图片、音频、视频、CSS、JS等等);
-
浏览器发送异步请求。
下面我们就详细地了解一下这些过程:
- DNS 查找 IP 地址
那么什么是DNS?
在HTTP客户端向服务器发送报文之前,需要在客户端和服务器之间建立一条TCP/IP连接。
建立一条TCP连接的过程与给公司某个人打电话的过程类似。
首先,要拨打电话号码,其次,拨打分机号。
在TCP中,你需要知道服务器的IP地址,以及与服务器上运行的特定软件相关的TCP端口号。
我们先来看两个URL:
DNS(域名服务器)是进行域名和与之相对应的IP地址转换的服务器。DNS中保存了一张域名和与之相对应的IP地址的表,以解析消息的域名。
DNS 查找过程:
- 浏览器缓存 —— 浏览器会缓存 DNS 记录一段时间,但是操作系统并没有告诉浏览器储存 DNS 记录的时间。于是,不同浏览器会储存各自的一个固定时间( 2 分钟到 30 分钟不等)
- 系统缓存 – 如果在浏览器缓存里没有找到需要的记录,浏览器会做一个系统调用,这样便可获得并查询系统缓存中的记录
- 路由器缓存 – 接着,前面的查询请求发向路由器。路由器一般会有自己的 DNS 缓存
- ISP DNS缓存 – 接下来要 check 的就是 ISP 缓存 DNS 的服务器。ISP 就是互联网服务提供商,在这一般都能找到相应的缓存记录
- 递归搜索 – 你的 ISP 的 DNS 服务器从跟域名服务器开始进行递归搜索,从 .com *域名服务器到 example 的域名服务器。一般 DNS 服务器的缓存中会有 .com 域名服务器中的域名,所以到*服务器的匹配过程不是那么必要了
DNS 进行域名解析的过程:
- 客户端发出 DNS 请求翻译 IP 地址或主机名
- DNS 服务器在收到客户端的请求后,检查 DNS 服务器的缓存,若查到请求的地址或名字,即向客户端发出应答信息
- 若没有查到,则在数据库中查找,若查到请求的地址或名字,即向客户端发出应答信息
- 若没有查到,则将请求发给根域 DNS 服务器,并依序从根域查找*域,由*查找二级域,二级域查找三级,直至找到要解析的地址或名字。然后,向客户端所在网络的 DNS 服务器发出应答信息,DNS 服务器收到应答后,先在缓存中存储,然后,将解析结果发给客户端
- 若没有找到,则返回错误信息
- 缓存
Web缓存或代理缓存是一种特殊的HTTP代理服务器,可以将经过代理传送的常用文档复制保存起来。下一个请求同一文档的客户端就可以享受缓存的私有副本所提供的服务了。
客户端从附近的缓存下载文档会比从远程Web服务器下载快得多。
Web缓存是可以自动保存常见文档副本的HTTP设备。
当Web请求抵达缓存时,如果本地有“已缓存的”副本,就可以从本地存储设备而不是从原始服务区中提取这个文档。
使用缓存有如下优点:
- 缓存减少了冗余的数据传输
- 缓存缓解了网络瓶颈的问题
- 缓存降低了对原始服务器的而要求
- 缓存降低了距离时延
首先我们先了解一下什么是TCP/IP协议:
TCP/IP协议模型,包含了一系列构成互联网基础的网络协议。TCP/IP协议簇是一组不同层次上的多个协议的组合,通常被认为是一个四层协议系统,与OSI的七层模型相对应。
HTTP协议就是基于TCP/IP协议模型来传输信息的。
链路层
也称作数据链路层或网络接口层(在第一个图中为网络接口层和硬件层),通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。ARP(地址解析协议)和RARP(逆地址解析协议)是某些网络接口(如以太网和令牌环网)使用的特殊协议,用来转换IP层和网络接口层使用的地址。
网络层
也称作互联网层(在第一个图中为网际层),处理分组在网络中的活动,例如分组的选路。在TCP/IP协议族中,网络层协议包括IP协议(网际协议),ICMP协议(Internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)
IP是一种网络层协议,提供的是一种不可靠的服务,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。同时被TCP和UDP使用。TCP和UDP的每组数据都通过端系统和每个中间路由器中的IP层在互联网中进行传输。
ICMP是IP协议的附属协议。IP层用它来与其他主机或路由器交换错误报文和其他重要信息。 IGMP是Internet组管理协议。它用来把一个UDP数据报多播到多个主机。
传输层
主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议族中,有两个互不相同的传输协议:TCP(传输控制协议)和UDP(用户数据报协议)
TCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层,确认接收到的分组,设置发送最后确认分组的超时时钟等。由于运输层提供了高可靠性的端到端的通信,因此应用层可以忽略所有这些细节。为了提供可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。
UDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。一个数据报是指从发送方传输到接收方的一个信息单元(例如,发送方指定的一定字节数的信息)。UDP协议任何必需的可靠性必须由应用层来提供。
应用层
应用层决定了向用户提供应用服务时通信的活动。TCP/IP 协议族内预存了各类通用的应用服务。包括 HTTP,FTP(File Transfer Protocol,文件传输协议),DNS(Domain Name System,域名系统)服务。
- TCP三次握手
三次握手建立TCP连接:
在HTTP工作开始之前,浏览器首先要通过网络与服务器建立连接,该连接是通过 TCP 来完成的。该协议与 IP 协议共同构建 Internet ,即著名的 TCP/IP 协议族,因此 Internet 又被称作是 TCP/IP 网络。 http 是比 TCP 更高层次的应用层协议。根据规则,只有低层协议建立之后才能进行更高次层协议的连接。因此,首先要建立 TCP 连接,一般 TCP 连接的端口号是 80。
在 TCP/IP 协议中,TCP 协议提供可靠的连接服务,采用三次握手建立一个连接:
第一次握手:建立连接时,客户端发送 SYN 包(syn=j)到服务器,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)
第二次握手:服务器收到 SYN 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包
第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),此包发送完毕,客户端和服务器进入TCP连接成功状态,完成三次握手
完成三次握手,客户端与服务器开始传送数据。
一旦建立了 TCP 连接,浏览器就会向服务器发送 http 请求命令。浏览器发送其请求命令之后,还要以头信息的形式向服务器发送一些别的信息。此后,浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
为什么要三次握手?
“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。
- 浏览器给WEB服务器发送一个HTTP请求
一个HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成。
请求行
请求行分为三个部分:请求方法、请求地址URL和HTTP协议版本,它们之间用空格分割。例如,GET /index.html HTTP/1.1
请求方法
HTTP/1.1 定义的请求方法有8种:GET(完整请求一个资源)、POST(提交表单)、PUT(上传文件)、DELETE(删除)、PATCH、HEAD(仅请求响应首部)、OPTIONS(返回请求的资源所支持的方法)、TRACE(追求一个资源请求中间所经过的代理)。最常的两种GET和POST,如果是RESTful接口的话一般会用到GET、POST、DELETE、PUT。
GET
当客户端要从服务器中读取文档时,当点击网页上的链接或者通过在浏览器的地址栏输入网址来浏览网页的,使用的都是GET方式。
GET方法要求服务器将URL定位的资源放在响应报文的数据部分,会送给客户端。
使用GET方法时,请求参数和对应的值附加在URL后面,利用一个问号‘?’代表URL的结尾与请求参数的开始,传递参数长度受限制。
例如,/index.jsp?id=100&op=bind。通过GET方式传递的数据直接放在地址中,所以GET方式的请求一般不包含“请求内容”部分,请求数据以地址的形式表现在请求行。地址中‘?’之后的部分就是通过GET发送的请求数据,各个数据之间用‘&’符号隔开。显然这种方式不适合传送私密数据。另外,由于不同的浏览器对地址的字符限制也有所不同,一半最多只能识别1024个字符,所以如果需要传送大量数据的时候,也不适合使用GET方式。如果数据是英文字母/数字,原样发送;如果是空格,转换为+;如果是中文/其他字符,则直接把字符串用BASE64加密,得出:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。
POST
允许客户端给服务器提供信息较多。POST方法将请求参数封装在HTTP请求数据中,以名称/值的形式出现,可以传输大量数据,这样POST方式对传送的数据大小没有限制,而且也不会显示在URL中。
POST方式请求行中不包含数据字符串,这些数据保存在“请求内容”部分,各数据之间也是使用‘&’符号隔开。
POST方式大多用于页面的表单中。因为POST也能完成GET的功能,因此多数人在设计表单的时候一律都使用POST方式,其实这是一个误区。GET方式也有自己的特点和优势,我们应该根据不同的情况来选择是使用GET还是使用POST。
URL
URL:统一资源定位符,是一种资源位置的抽象唯一识别方法。
组成:<协议>://<主机>:<端口>/<路径>
端口和路径有时可以省略(HTTP默认端口号是80)
协议版本
协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1
请求头部
请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔。
请求头部的最后会有一个空行,表示请求头部结束,接下来为请求数据。
请求数据
请求数据不在GET方法中使用,而在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最长使用的请求头部是Cntent-Type和Content-Length。
下面是一个POST方法的请求报文:
POST /index.php HTTP/1.1 (请求行)
(请求头)
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2)
Gecko/20100101 Firefox/10.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-cn,zh;q=0.5 Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost/
Content-Length:25
Content-Type:application/x-www-form-urlencoded
(空行)
username=aa&password=1234(请求数据)
- 服务器的永久重定向响应
服务器给浏览器响应一个 301 永久重定向响应,这样浏览器就会访问 http://www.baidu.com/ 而非 http://baidu.com/
为什么服务器一定要重定向而不是直接发会用户想看的网页内容呢?
其中一个原因跟搜索引擎排名有关。如果一个页面有两个地址,就像 http://www.sina.com/ 和 http://sina.com/ ,搜索引擎会认为它们是两个网站,结果造成每一个的搜索链接都减少从而降低排名。而搜索引擎知道 301 永久重定向是什么意思,这样就会把访问带 www 的和不带 www 的地址归到同一个网站排名下。
还有一个原因是用不同的地址会造成缓存友好性变差。当一个页面有好几个名字时,它可能会在缓存里出现好几次。
- 服务器端响应HTTP请求
HTTP响应报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。
状态行
状态行由3部分组成,分别为:协议版本、状态码、状态码扫描。其中协议版本与请求报文一致,状态码描述是对状态码的简单描述。
响应头部
响应数据
用于存放需要返回给客户端的数据信息。
HTTP/1.1 200 OK (响应行)
(响应头)
Date: Sun, 17 Mar 2013 08:12:54 GMT
Server: Apache/2.2.8 (Win32) PHP/5.2.5
X-Powered-By: PHP/5.2.5
Set-Cookie: PHPSESSID=c0huq7pdkmm5gg6osoe3mgjmm3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 4393
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8
(空行)
<html> 响应数据
<head><title>HTTP响应示例<title></head>
<body>Hello HTTP!</body>
</html>
- 浏览器解析HTML代码,并请求HTML代码中的资源
浏览器拿到HTML文件后,开始解析HTML代码,遇到静态资源时,就向服务器端去请求下载。
- 关闭TCP连接,浏览器对页面进行渲染呈现给用户
浏览器利用自己内部的工作机制,把请求到的静态资源和HTML代码进行渲染,呈现给用户。
- TCP四次挥手
当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那对于TCP的断开连接,这里就有了神秘的“四次分手”
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。这一步表示Client没有数据要发送给Server了
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。Server告诉Client,我“同意”你的关闭请求
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入关闭连接状态,完成四次挥手
四次挥手终止连接
由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。原则是当一方完成它的数据发送任务后,就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动。一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
第一次挥手:TCP 客户端发送一个 FIN,用来关闭客户端到服务器的数据传送。
第二次挥手:服务器收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号。
第三次挥手:服务器关闭客户端的连接,发送一个 FIN 给客户端。
第四次挥手:客户端发回 ACK 报文确认,并将确认序号设置为收到序号加 1 。
为什么要四次挥手?
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当Client发出FIN报文段时,只是表示Client已经没有数据要发送了, Client告诉Server,它的数据已经全部发送完毕了;但是,这个时候Client还是可以接受来自Server的数据;当Server返回ACK报文段时,表示它已经知道Client没有数据发送了,但是Server还是可以发送数据到Client的;当Server也发送了FIN报文段时,这个时候就表示Server也没有数据要发送了,就会告诉Client ,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。