从输入网址到浏览器返回内容(一),服务器处理篇
本文基于从输入网址到浏览器呈现页面内容,中间发生了什么?整理而得。
从输入网址到浏览器返回内容
浏览器与服务器建立连接
当你在浏览器中输入了网址(www.baidu.com)以后,浏览器首先要把www.baidu.com的IP地址获取到。
也就是发一个UDP包给DNS服务器,然后获得相应的IP,浏览器会把IP地址缓存起来,以加快下次访问的速度。接下来就浏览器就可以发送HTTP请求了,但是HTTP必须要通过TCP,所以首先需要建立“虚拟的"的TCP连接,需要知道4个元素:本机IP,本机端口,服务器IP,服务器端口,现在本机IP和服务器IP已经知道了。本机端口可以由操作系统随机分配一个,服务器端口可以用默认端口,也就是HTTP的80。经过三次握手之后,浏览器和服务器的TCP连接就建立起来了。
Web服务器进行处理
当HTTP GET请求经过多次转发以后到了服务器端,Web服务器开始进行处理,可以有三种方式:
- 用一个线程来处理所有请求,同一时刻只能处理一个,性能非常差。
- 使用多进程/多线程来进行处理,也就是为每个请求分配应该 进程或者线程,但是当连接太多的时候,会消耗大量的内存资源,同时进程/线程的切换让CPU不堪重负。比如Apache
- 复用IO方式:这是当前很多Web服务器采用的结构,比如通过epoll方式监听所有的连接,当连接的状态发生改变的时候(比如有数据可以进行处理)才为此连接分配一个的进程或线程,处理完以后继续监听。这种方法的好处是可以使用少量的进程或者线程应对大量的连接请求。
假设Web服务器是Nginx,一种非常流行的Web服务器,当读出来GET请求之后,需要判断此请求是静态的还是动态的?
- 静态请求(HTML文件,CSS文件):可以直接读取硬盘上相关文件,直接返回。
- 动态请求(修改数据库等):需要向后端服务器(Tomcat)转发,由Tomcat进行处理。那么如果后端的TomCat服务器有多个,那么需要负载均衡,有如下几种策略:
- 轮询:按照次序依次转发。
- 权重:给每个Tomcat服务器指定一个权重,权重高的优先发到。
- hash:根据IP地址进行HASH,确定转发的服务器。这样,同一个浏览器的IP总是会转发到同一个Tomcat中。
- fair:根据Tomcat服务器的响应时间来分配,响应时间短的优先分配。
这样Nginx将HTTP Request转给了Tomcat,由Tomcat返回HttpResponse,然后把回复的信息再转发给浏览器。在这种场景下,Nginx其实就是一个代理
应用服务器进行处理
刚刚讲到了HTTP Request被转发到Tomcat,这是由JAVA写的,可以处理Servlet/JSP的容器。Tomcat也可以为每个请求分配一个线程,即Blocing IO模式(BIO模式),也可以使用IO多路复用技术,即NIO模式。
所以HTTP Request会交给某个Servlet处理,然后这个Servlet会对Request请求做转换,变成框架所需的参数格式,分发给某个controller。然后就是执行增删查改等后端逻辑,与数据库、缓存打交道。最终返回HTTP Response给Nginx,比如一个HTML页面。Nginx作为一个代理,又转发给了浏览器
此时TCP连接能关闭呢?
- 如果是HTTP1.1,默认是keep-alive的,所以不能关闭
- 如果是HTTP1.0:如果Request Header里面有keep-alive的配置,也不能关。
浏览器收到了Response
现在浏览器收到了Response,从中读取HTML页面,开始准备显示。但是这个HTML页面可能引用了大量的资源,比如图片、视频等,这些资源位于服务器,还可能在另一个域名下。所以只能一个一个的下载,不过不会再有Tomcat等应用服务器参与了。
当服务器给浏览器返回图片等资源的时候,会告诉浏览器这个些文件什么时候过期(使用Cache-Control或者Expire),浏览器会把文件缓存到本地,当第二次请求的时候,如果没有过期,则直接从本地取即可。
如果过期了,则浏览器还可以询问服务器,文件是否修改过?(依据上一次服务器发的Last-Modified和ETag),如果没有修改过(304 Not Modified),还可以使用缓存,否则的话,服务器会发送最新的文件到浏览器。
现在浏览器得到了三个重要的东西。
- HTML,浏览器把它变为了DOM Tree
- CSS,浏览器把它变为了CSS Rule Tree
- JavaScript,它可以修改DOM Tree
浏览器通过DOM Tree和CSS Rule Tree生成了所谓Render Tree,计算每个元素的位置和大小,进行布局,然后调用操作系统的API进行绘制。
总结: