浏览器输入URL后,在结果返回浏览器前,主要有以下过程:1、用户输入网址后,浏览器发起DNS查询请求;2、建立TCP连接;3、发送HTTP请求;4、服务器处理请求;5、返回HTTP响应;6、释放TCP连接;7、浏览器解析返回的资源和数据。
1、DNS查询过程
用户访问网页,DNS服务器(域名解析系统)会根据用户提供的域名查找对应的IP地址。域名解析服务器是基于UDP协议实现的一个应用程序,通常通过监听53端口来获取客户端的域名解析请求。
DNS查询过程如下:
(1) 当在浏览器中输入URL后,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
(2) 如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
(3) 如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫它本地DNS服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。
(4) 如果要查询的域名,不由本地DNS服务器区域解析,但本地DNS服务器缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。
(5) 如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找域服务器,重复上面的动作,进行查询,直至找到主机。
(6) 如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
2、建立TCP连接
浏览器通过DNS获取待访问服务器IP地址后,便会向该服务器发起TCP连接请求,通过TCP三次握手后建立好套接字连接。
TCP是面向连接的通信协议,也就是说在进行通信前,发送方和接受方已建立了连接,并在完成通信后,释放连接。
TCP连接建立的过程就是三次握手的过程,因此也将建立TCP连接的过程称为三次握手。三次握手图示如下:
最初,客户端和服务器的TCP进程都处于CLOSED(关闭)状态。对于服务器端来说,为时刻准备接收客户端进程的连接请求,需提前进入LISTEN(监听)状态,注意,此时服务器的连接并未打开。
然后,客户端TCP进程在创建传输控制块TCB后,主动向服务器发出TCP连接请求报文。此时,报文头部中的同步标识位SYN=1,并设置一个初始序列号seq=x。发送完TCP连接请求报文后,客户端TCP进程进入到SYN-SENT(同步已发送状态)状态。注意,这个报文(SYN=1的报文)不能携带数据,且需要消耗掉一个序列号。
接着,服务器TCP进程接收到请求报文,如果同意连接,则发出确认报文。服务器端确认报文中确认标识位ACK=1,同步标识位SYN=1,确认号ack=x+1,同时也要初始化一个序列号 seq=y。发送完确认报文后,服务器TCP进程进入SYN-RCVD(同步收到)状态。注意,这个报文(ACK=1, SYN=1的报文)也不能携带数据,且同样需要消耗一个序号。
之后,客户端TCP进程接收到服务器端确认报文,并要向服务器给出确认。客户端确认报文的确认标识ACK=1,确认号ack=y+1,序列号seq=x+1。客户端发送完确认报文后,进入ESTABLISHED(已建立连接)状态。注意,这个报文(ACK=1的报文)可以携带数据,但如果不携带数据则不消耗序号。
此后,服务器TCP进程接收到客户端的确认报文后也进入ESTABLISHED状态。
最后,双方就可以开始通信。
建立TCP连接过程的三次握手可总结如下:
第一次握手:客户端TCP进程发送请求连接报文(SYN=1, seq=x)的数据包到服务器TCP进程。此时客户端TCP进程进入SYN_SEND状态,并等待服务器确认。注意,此时服务器端仍位LISTEN状态。
第二次握手:服务器TCP进程收到客户端请求连接报文后,发送确认报文,同时也发送一个SYN包(ACK=1, SYN=1, seq=y,ack=x+1),此时服务器进入SYN_RECV状态。注意,此时客户端仍为SYN_SEND状态。
第三次握手:客户端TCP进程收到服务器确认报文后,向服务器发送客户端确认保文(ACK=1, ack=y+1),并进入ESTABLISHED状态,服务器TCP进程在接收到客户端的确认报文后,也进入到ESTABLISHED状态。
3、发送HTTP请求
通过TCP三次握手建立好连接后,浏览器便可以将HTTP请求发送给服务器了。一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
HTTP 请求是客户端向服务端发送请求动作,告知服务器自己的要求。HTTP请求由状态行、消息头、空行、消息正文四个部分组成。其中,状态行包括请求方式Method、资源路径URL、协议版本Version;消息头包括一些访问的域名、用户代理、Cookie等信息;消息正文就是HTTP请求的数据;空行位于消息头和消息正文之间,用于分隔消息头和消息正文。
4、服务器处理请求
服务器端收到请求后由Web服务器(准确说应该是http服务器,也称Web容器)处理请求,诸如Apache Tomcat、Ngnix、IIS等。Web服务器解析用户请求,知道了需要调度哪些资源文件,再通过相应的这些资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过Web服务器返回给浏览器客户端。
当用户从浏览器向服务器发起一个请求,通常会包含如下信息:http://hostname:port/contextpath/servletpath,hostname 和 port 是用来与服务器建立 TCP 连接,而后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。那服务器是如何根据这个 URL 来达到正确的 Servlet 容器中的呢?以Tomcat为例,Tomcat7.0 中这件事很容易解决,因为这种映射工作有专门一个类来完成的,这个就是 org.apache.tomcat.util.http.mapper,这个类保存了 Tomcat 的 Container 容器中的所有子容器的信息,当 org.apache.catalina.connector.Request 类在进入 Container 容器之前,mapper 将会根据这次请求的 hostnane 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。所以当 Request 进入 Container 容器之前,它要访问那个子容器这时就已经确定了。
Http请求转发给Web容器,Web容器收集相关信息后,生成HttpServletRequest、HttpServletResponse对象并将请求信息存入其中,紧接着,web容器判断由哪一个servlet实例处理该请求,并将request、response对象传给该servlet实例的service方法进行中转。以Spring MVC为例,DispatchServlet分发到具体的Service上。
5、返回HTTP响应
Web服务器将资源复本写到TCP套接字,由客户端读取。一个响应报文由响应行、响应头部、空行和响应数据4部分组成。
HTTP 响应是服务器返回给客户端已发送HTTP请求的执行结果。HTTP 响应和HTTP请求一样,由四部分组成:状态行、消息头、空行、消息正文。其中,状态行包括协议版本Version、状态码Status Code、状态消息等内容;消息头包括搭建服务器的软件,发送响应的时间,回应数据的格式等信息;消息正文就是HTTP响应的数据。空行位于消息头和消息正文之间,用于分隔消息头和消息正文。
6、释放TCP连接
为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建TCP连接的3次握手类似,关闭TCP连接,需要4次握手。
数据传输完毕后,双方都需释放连接。TCP释放建立的过程就是四次握手的过程,为与三次握手进行区分,将释放TCP连接的过程称为四次挥手。四次挥手图示如下:
最初,客户端和服务器都处于ESTABLISHED状态。然后客户端TCP进程发出连接释放报文,并且停止发送数据。此时释放连接报文头部中结束标识位FIN=1,并更新序列号seq=u(等于前面已传送数据的最后一个字节的序列号加1),发送完TCP连接释放请求报文后,客户端TCP进程进入FIN-WAIT-1(终止等待1)状态。 注意,这个报文(FIN=1的报文)即使不携带数据,也要消耗一个序列号。
然后,服务器TCP进程接收到连接释放报文后,发出确认报文,确认标识位ACK=1,确认号ack=u+1,并且初始化一个序列号seq=v。服务器TCP进程发送完确认报文后,进入到CLOSE-WAIT(关闭等待)状态。此时,客户端向服务器的连接就释放了,也就是处于半关闭状态:客户端已经没有数据要发送,但服务器若发送数据,客户端依然要接受。
接着,客户端TCP进程接收到服务器的确认请求后,进入到FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
之后,服务器TCP进程将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端确认。
在这之后,客户端TCP进程接收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2*MSL(Maximum Segment Lifetime,最长报文段寿命)时间后,客户端TCP进程才进入CLOSED状态。
而服务器TCP进程在接到客户端的连接释放确认报文后,立即进入CLOSED状态。所以,服务器TCP进程结束TCP连接的时间要比客户端早一些。
释放TCP连接的四次挥手可总结如下:
第一次挥手:客户端TCP进程发送释放连接报文(FIN=1, seq=u) 到服务器TCP进程。此时客户端TCP进程进入FIN-WAIT-1(终止等待1)状态。注意,注意,此时,服务器端仍处于ESTABLISHED状态。
第二次挥手:服务器TCP进程收到客户端释放连接报文后,发送确认报文,同时也发送一个SYN包(ACK=1, ack=u+1, seq=v),然后服务器进入CLOSE-WAIT状态。注意,当前连接已处于半关闭状态。客户端已无法向服务器发送数据,但客户端仍可接收服务器发送到数据。
客户端TCP进程接收到服务器的确认请求后,进入到FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
第三次挥手:服务器TCP进程确认最后的数据发送完毕后,向客户端发送连接释放报文(FIN=1, ack=u+1, seq=w),然后,服务器就进入了LAST-ACK(最后确认)状态,等待客户端确认。
第四次挥手:客户端TCP进程接收到服务器的连接释放报文后,返回确认报文(ACK=1, ack=w+1, seq=u+1),然后客户端就进入TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2*MSL时间后,客户端TCP进程才进入CLOSED状态。而服务器TCP进程在接到客户端的连接释放确认报文后,立即进入CLOSED状态。
四次挥手的状态转换过程如下:
客户端和服务器处于ESTABLISHED——客户端FIN-WAIT-1——服务器CLOSE-WAIT——客户端FIN-WAIT-2——服务器LAST-ACK——客户端TIME-WAIT——服务器和客户端CLOSED
7、浏览器解析返回的资源和数据
服务器返回资源和数据给浏览器后,浏览器需要加载解析html、css、js,以及图片、视频等其他媒体资源。
浏览器通过解析html,生成dom树,解析css,生成css规则树,然后通过dom树和css规则树生成渲染树。渲染树与dom树不同,渲染树中并没有head、display为none等不必显示的节点。
要注意的是,浏览器的解析过程并非是串行的,比如在解析css的同时,可以继续加载解析html,但在解析执行js脚本时,会停止解析后续thml,这就会出现阻塞问题。
这里重点介绍下html、css、js解析过程。
(1) html文档解析dom树
在浏览器没有完整接受全部html文档时,它就已经开始显示这个页面了。生成解析树即dom树,是由dom元素及属性节点组成,树的根是document对象。
(2) 浏览器发送获取嵌入在html中的对象
加载过程中遇到外部css文件,浏览器另外发出一个请求,来获取css文件。遇到图片资源,浏览器也会另外发出一个请求,来获取图片资源。这是异步请求,并不会影响html文档进行加载。
但是当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。
(3) css解析Parser Render Tree
浏览器下载css文件,将css文件解析为样式表对象,并用来渲染dom树。该对象包含css规则,该规则包含选择器和声明对象。css元素遍历的顺序,是从树的低端向上遍历。
(4) js解析
浏览器UI线程:单线程,大多数浏览器(比如chrome)让一个单线程共用于执行javascrip和更新用户界面。
js阻塞页面:浏览器里的http请求被阻塞一般都是由js所引起,具体原因是js文件在下载完毕之后会立即执行,而js执行时候会阻塞浏览器的其他行为,有一段时间是没有网络请求被处理的,这段时间过后http请求才会接着执行,这段空闲时间就是所谓的http请求被阻塞。
js阻塞原因:之所以会阻塞UI线程的执行,是因为js能控制UI的展示,而页面加载的规则是要顺序执行,所以在碰到js代码时候UI线程会首先执行它。
参考
https://blog.csdn.net/root_robot/article/details/53872812 DNS的工作原理及解析
https://www.cnblogs.com/developing/articles/10828890.html 输入URL后的全过程
https://www.jianshu.com/p/f0763a49d64c 浏览器输入一个网址(www.baidu.com)后执行的全过程
https://www.cnblogs.com/Leo_wl/p/3364231.html 页面生命周期
https://www.cnblogs.com/licheng/p/6687326.html 【Tomcat】Servlet 工作原理解析
https://blog.csdn.net/weixin_38753309/article/details/84524937 Web容器处理请求的过程