恩,如你所见,这是一道程序员经典面试题,涵盖了各个领域的知识,这个问题可以答的很简单,也可以答的很复杂。
首先,我们会在浏览器中输入类似 http(s)://xxx.xxx.com 这样的域名。
我们看到这个域名由 http 或 https 协议组成。
那么既然我们说到了协议,不得不先来了解一下计算机网络的知识了。
一台电脑要和另一台电脑通信,本质上就是通过电信号的高低频,在计算机中也就是0和1来传输,那么最底层一定是物理层,我们用的光纤,网线都是物理层的媒介和连接器,物理层也有很多协议和标准,这里就不展开了。往上一层包装的就是数据链路层,我们平时所说的ARP地址解析协议就在这一层,它是根据ip地址解析到电脑mac地址(硬件物理地址)的协议。再往上包装的就是网络层,著名的ip协议就在这一层,是用来寻址的重要协议。然后是传输层,寻址到了我们需要搭建一个传输的桥梁,著名的TCP三次握手就在这一层。通道已经建好了,那么把数据包用HTTP协议的格式封装好,准备传输,这一层就是应用层,还包括了常见的DNS域名解析系统,SMTP简单邮件传输协议等。
这样经过一层一层的包装和拆包,计算机和计算机之间才可以建立联系,互相发送彼此能看懂的数据包。
所以,在浏览器中输入URL后,第一步是通过应用层的DNS解析这个域名对应的真实ip地址,然后向这个ip地址所对应的服务器发送http(s)请求,然后层层包装数据,发送,最后目标服务器不断拆包,拿到请求体,返回客户端需要的数据,再包装发回,客户端再拆包。(当然,实际上建立连接的过程并不是这么简单一次发送就完成了,这边只是方便理解)
如果是http协议,那么就是这么简单了,但众所周知,http协议的数据包都是明文发送,很容易被截获或者被重放攻击,因此,基于http的更安全的https协议就出现了。https协议也就是在http的基础上加了一个安全认证的过程。讲到安全,那么不得不提对称加密和非对称加密了。
对称加密,顾名思义,加密和解密用的是同一个秘钥。我们可以使用秘钥加密消息后再发送出去,但对方得到的是你加密后的信息,所以,你需要把秘钥也一块发送过去,他才能解密,那么发送秘钥的过程也是可以被截获的,所以,这好像不符合我们的需求。
再来看非对称加密,非对称加密会有一组配套的公钥和私钥,私钥只在本机持有(千万不要泄露),而公钥所有人都可以持有,两者都可以用来加密或解密,特别的是,用公钥加密的消息,只能用对应的唯一私钥解密,用私钥加密的消息,只能用对应的公钥解密。
聪明的童鞋应该已经想到了,由于非对称加密的特性,私钥往往可以用来做签名,因为只有本机有私钥,防止第三方篡改信息,而公钥可以用来加密私密消息,因为只有拥有私钥的服务器可以解开。这样的话,我们只需要得到目标服务器的公钥,就可以安全的和服务器交互了。
但是问题又来了!传输公钥的过程依然是可以被截获的,假如第三方将目标服务器发出来的公钥截获,把自己的公钥发给客户端,客户端用这个公钥加密的消息,会被第三方用自己的私钥给解开,然后第三方再将解开的消息篡改,用之前保存的目标服务器的公钥加密后发给目标服务器,结果可想而知,所有信息第三方了如指掌。
所以现在最大的问题是,我们怎么才能确定这个公钥确实是我要访问的目标服务器的公钥,那么就要引出证书(CA)的概念了。证书一般是由第三方受信任机构颁发的,我们需要将自己服务器的公钥,域名等关键唯一信息上传到第三方证书颁发机构,申请证书,申请成功后,第三方机构会用自己的一套加密规则生成证书发放给你(用它自己的私钥加密签名)。
接下来要做的,就是将证书放在目标服务器上,当客户端发请求过来时,把证书发送到客户端,此时客户端会验证证书的有效性(颁发证书的机构是否合法与是否过期,证书中包含的网站地址是否与正在访问的地址一致等,通常浏览器都已经内置了各大信任机构的证书链和公钥了,具体不展开客户端是如何验证证书有效性的)。PS:证书颁发机构还有更上级的受信任机构,所以证书通常是链条式的,直至找到根证书。发送证书这个过程即使被第三方不法份子拦截了,也是然并卵,因为篡改证书内容后需要私钥签名,而私钥只有颁发机构有,这条路走不通,把自己的受信任证书发给客户端也没用,证书检测不能通过。
有了CA,想伪造公钥的办法基本是被堵死了,那么客户端就安全的拿到了目标服务器的公钥了,这下,客户端会生成伪随机串,作为对称加密的秘钥,并把自己支持的对称加密的算法列出来,和目标服务器协商使用哪一种。这些信息用公钥加密发给目标服务器,此时因为私钥只有目标服务器有,所以只有目标服务器可以解密,拿到具体的秘钥和使用的对称加密算法后,以后每次通讯,使用效率比较高的对称加密就可以了。
以上就是https建立ssl连接的全过程了,有的人可能会说,https的数据照样可以截取啊。确实可以截取,但那是需要满足一些条件的,那些抓包工具的原理,其实就是在客户端插入了抓包工具的根证书,使客户端受信抓包工具,从而获取到数据,而对于不法份子来说,他没有办法在你不知情的情况下给你的客户端安装一个根证书,所以https还是相对安全的。
另外,http2协议也已经逐渐在普及了。
相比 HTTP/1.x,HTTP/2 在底层传输做了很大的改动和优化:
- HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
- HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。
- 多路复用,直白的说就是所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然能利用一个连接完成多次请求,但是多个请求之间是有先后顺序的,后面发送的请求必须等待上一个请求返回才能发送响应。这会很容易导致后面的请求被阻塞,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。
- Server Push:服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
http2已经可以在nodejs中通过引入spdy模块来启动服务器了,不过是强制要求存放证书,必须over SSL/TLS,要检测某个网站是否启用了http2协议,可以安装chrome插件,HTTP/2 and SPDY indicator,当使用该协议时,蓝色闪电会亮。
那么我们是怎么发起这个请求的?当然是通过浏览器,浏览器会将我们输入的url封装成http数据包发送出去。
讲到浏览器,那么不得不简单说一下浏览器的工作原理。
浏览器的主要组件包括:
- 用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
- 浏览器引擎- 用来查询及操作渲染引擎的接口
- 渲染引擎- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
- 网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
- UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
- JS解释器- 用来解释执行JS代码
- 数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术
其中,渲染引擎是我们用来解析html的核心组件,需要注意的是,大部分浏览器为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。
假如服务器现在返回了html页面,响应完成后,浏览器渲染引擎中的解析器线程会根据返回的html标签来构建DOM树。
浏览器渲染引擎进程是多线程的,一个浏览器渲染引擎至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。
1) javascript引擎是基于事件驱动单线程执行的。JS引擎一直等待着任务队列中任务的到来,然后加以处理(只有当前函数执行栈执行完毕,才会去任务队列中取任务执行)。浏览器无论什么时候都只有一个JS线程在运行JS程序。
2) GUI渲染线程负责渲染浏览器界面,根据渲染树进行渲染,或当界面需要重绘(repaint)或由于某种操作引发回流(reflow)时,该线程也会执行。但是GUI渲染线程与JS引擎是互斥的,当JS引擎执行时,GUI线程会被挂起冻结,GUI在渲染时,JS线程也会被阻塞。
3) 事件触发线程,当一个事件被触发时该线程会把事件添加到任务队列的队尾,等待JS引擎的处理。这些事件可以来自javascript引擎当前执行的代码块调用setTimeout或ajax添加一个任务,也可以来自浏览器其他线程如鼠标点击添加的任务。但由于JS的单线程关系,所有这些事件都得排队等待JS引擎处理。
假如此时解析器碰到了一个样式文件的link标签<link rel="stylesheet" href="example.css">,解析完后要和它建立连接,发起一个下载css文件的请求(注意:浏览器有最大请求数量的限制,一般是4-8个,超过限制数目的请求会被阻塞,不过浏览器的并发请求数目限制是针对同一域名的,这就是为什么我们要把静态资源放到CDN上的其中一个原因,什么?其他的原因是啥,这里不展开了,就说几个请求优化的关键词,cookie free,domain hash,css sprites,js/css combine、minify、compress,Cache-Control max-age,scroll loading等)。
下载样式表的过程是耗时的,但是此时解析器线程并没有被阻塞,会继续往下解析(注意:href只是建立连接,并不会阻塞解析器,而src是请求一个必要资源替换当前元素,会阻塞解析器),但GUI渲染线程会被阻塞,直到样式表加载完成,生成CSSOM,才会和DOM共同构建渲染树,再交由GUI渲染线程渲染。值得注意的是,渲染过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局渲染树,它是解析完一部分内容就渲染一部分内容。
继续解析,遇到了script标签,<script src=""></script>,script文件的下载和执行会阻塞解析器线程,js线程的执行也会冻结GUI渲染线程。如果解析到script的时候,样式文件还没来得及下载完,那么部分浏览器会先阻塞全部脚本的下载和执行,必须先等待样式文件下载完成,而部分浏览器只会阻塞操作了dom结构或样式部分的js代码的执行。
在script执行期间,js可能修改了样式和dom结构,这种情况就会触发浏览器的重排或重绘。重排或重绘会大量消耗计算机性能,建议谨慎操作,或多采用合成的方式实现动画,比如transform和opacity,它们只会触发合成,合成意味着将网页中已经绘画好的部分结合在一起,且展示在屏幕上。
浏览器的渲染过程是由 布局 --> 绘制 --> 合成 这三部分构成,首先需要确定元素的大小和在窗口的位置,然后开始绘制,最后将不同层合并,显示到浏览器中。
等script标签加载并执行完后,解析器才继续往下解析。
由于以上这些因素,我们也可以知道为什么总是把css放在头部,而js放在尾部了,css放在头部可以尽快的加载样式构建渲染树,而js放在尾部是为了不阻塞解析器生成DOM。
当然,script的下载也可以变为非阻塞的,你可以给<script>标签加上async和defer,变为异步加载,他两的区别一张图就可以看懂。
为了减缓渲染被阻塞的情况,现代的浏览器都使用了猜测预加载。
当渲染被阻塞的时候,它会做以下几件事:
- 轻量级的HTML(或CSS)扫描器继续在文档中扫描
- 查找那些将来可能能够用到的资源文件的url
- 在渲染器使用它们之前将其下载下来
以上,就是从输入URL到页面加载发生的完整过程。
参考文献:http://blog.jobbole.com/12749/
http://www.cnblogs.com/yuezk/archive/2013/01/11/2855698.html
http://www.cnblogs.com/roverliang/p/5176456.html
http://www.tuicool.com/articles/QjMFBj3
https://www.zhihu.com/question/37370216
https://segmentfault.com/q/1010000000640869
https://www.zhihu.com/question/20474326
http://www.cnblogs.com/kevin2chen/p/6415630.html