草 稿

当你在浏览器中输入google.com并且按下回车之后发生了什么?

这是一个古老的面试问题。这不是一个“标准”答案,而是一个协作的过程,因为大家希望深入挖掘一起完善它。欢迎到这里( https://github.com/skyline75489/what-happens-when-zh_CN )加入这个队伍。

  1. 回车键按下

  2. USB键盘

    串行信号在计算机的USB控制器处被解码,然后被驱动进行进一步解释。之后按键的码值被传输到操作系统的硬件抽象层。

  3. 虚拟键盘(触屏设备)

    虚拟键盘引发一个软中断,返回给OS一个“按键按下”消息。这个消息又返回来向当前活跃的应用通知一个“按键按下”事件。

  4. 产生中断[非USB键盘]

    键盘在它的中断请求线(IRQ)上发送信号,信号会被中断控制器映射到一个中断向量,实际上就是一个整型数 。CPU使用中断描述符表(IDT)把中断向量映射到对应函数,这些函数被称为中断处理器,它们由操作系统内核提供。当一个中断到达时,CPU根据IDT和中断向量索引到对应的中端处理器,然后操作系统内核出场了。

  5. (Windows)一个 WM_KEYDOWN 消息被发往应用程序

  6. (Mac OS X)一个 KeyDown NSEvent被发往应用程序

  7. (GNU/Linux)Xorg 服务器监听键码值

  8. 解析URL

    浏览器通过URL能够知道下面的信息:

  9. Protocol

    使用HTTP协议

  10. Resource

    请求的资源是主页(index)

  11. 输入的是URL还是搜索的关键字?

    当协议或主机名不合法时,浏览器会将地址栏中输入的文字传给默认的搜索引擎。大部分情况下,在把文字传递给搜索引擎的时候,URL会带有特定的一串字符,用来告诉搜索引擎这次搜索来自这个特定浏览器。

  12. 检查HSTS列表……

  13. 浏览器检查自带的“预加载HSTS(HTTP严格传输安全)”列表

    这个列表里包含了那些请求浏览器只使用HTTPS进行连接的网站

  14. 如果网站在这个列表里

    浏览器会使用HTTPS而不是HTTP协议,否则,最初的请求会使用HTTP协议发送

  15. 注意,一个网站哪怕不在HSTS列表里,也可以要求浏览器对自己使用HSTS政策进行访问

  16. 转换非ASCII的Unicode字符

  17. 浏览器检查输入是否含有不是 a-z, A-Z,0-9, - 或者 . 的字符

  18. 如果有非ASCII的字符的话

    浏览器会对主机名部分使用 Punycode 编码

  19. DNS查询……

  20. 浏览器检查域名是否在缓存当中

  21. 如果缓存中没有

    就去调用 gethostbynme 库函数(操作系统不同函数也不同)进行查询

  22. gethostbyname 函数在试图进行DNS解析之前首先检查域名是否在本地Hosts里

    Hosts的位置 不同的操作系统有所不同

  23. 如果 gethostbyname 没有这个域名的缓存记录,也没有在 hosts 里找到

    它将会向DNS 服务器发送一条DNS查询请求。DNS服务器是由网络通信栈提供的,通常是本地路由器或者ISP的缓存DNS服务器。

  24. 查询本地DNS服务器

  25. 如果DNS服务器和我们的主机在同一个子网内

    系统将会查询ARP缓存,以得到DNS服务器的ARP入口。缓存没有命中的话就要进行ARP广播(见下面),缓存命中的话,我们就得到了DNS.server.ip.address = dns:mac:address

  26. 如果DNS服务器和我们的主机在不同的子网

    与上面类似,不过我们的目标变成了默认网关,得到的信息是default.gateway.ip.address = gateway:mac:address

  27. ARP

    要想发送ARP广播,我们需要有一个目标IP地址,同时还需要知道用于发送ARP广播的接口的Mac地址。

  28. 首先查询ARP缓存,如果缓存命中,我们返回结果:目标IP = MAC

  29. 如果缓存没有命中

    查看路由表,看看目标IP地址是不是在本地路由表中的某个子网内。是的话,使用跟那个子网相连的接口,否则使用与默认网关相连的接口。

  30. 现在我们有了DNS服务器或者默认网关的IP地址,我们可以继续DNS请求了

    使用53端口向DNS服务器发送UDP请求包,如果响应包太大,会使用TCP

     

    如果本地/ISP DNS服务器没有找到结果,它会发送一个递归查询请求,一层一层向高层DNS服务器做查询,直到查询到起始授权机构,如果找到会把结果返回

  31. 使用套接字

    TCP segment被送往网络层,网络层会在其中再加入一个IP头部,里面包含了目标服务器的IP地址以及本机的IP地址,把它封装成一个TCP packet。

     

    这个TCP packet接下来会进入链路层,链路层会在封包中加入frame头部,里面包含了本地内置网卡的MAC地址以及网关(本地路由器)的MAC地址。像前面说的一样,如果内核不知道网关的MAC地址,它必须进行ARP广播来查询其地址。

  32. 客户端选择一个初始序列号(ISN)

    将设置了SYN位的封包发送给服务器端,表明自己要建立连接并设置了初始序列号

  33. 服务器端接受到SYN包,如果它可以建立连接:

    服务器端选择它自己的初始序列号

     

    服务器端设置SYN位,表明自己选择了一个初始序列号

     

    服务器端把 (客户端ISN + 1) 复制到ACK域,并且设置ACK位,表明自己接收到了客户端的第一个封包

  34. 客户端通过发送下面一个封包来确认这次连接:

    自己的序列号+1

     

    接收端ACK+1

     

    设置ACK位

  35. 数据通过下面的方式传输:

    当一方发送了N个Bytes的数据之后,将自己的SEQ序列号也增加N

     

    另一方确认接收到这个数据包(或者一系列数据包)之后,它发送一个ACK包,ACK的值设置为接收到的数据包的最后一个序列号

  36. 关闭连接时:

    要关闭连接的一方发送一个FIN包

     

    另一方确认这个FIN包,并且发送自己的FIN包

     

    要关闭的一方使用ACK包来确认接收到了FIN

  37. UDP 数据包

  38. TLS 握手

  39. 客户端发送一个 Client hello 消息到服务器端

    消息中同时包含了它的TLS版本,可用的加密算法和压缩算法。

  40. 服务器端向客户端返回一个 Server hello 消息

    消息中包含了服务器端的TLS版本,服务器选择了哪个加密和压缩算法,以及服务器的公开证书,证书中包含了公钥。

  41. 客户端验证服务器端的证书是否有效

    使用非对称加密算法加密一个对称密钥,客户端将这个加密后的秘钥发送给服务器端,同时附带了服务器的公钥和一个加密信息用于验证身份。

  42. 服务器端使用自己的私钥解密上面提到的对称秘钥

    然后向客户端返回一个使用自己私钥加密的验证消息

  43. 客户端确认服务器端身份,生成一个会话秘钥

    将这个秘钥连同一个 finished 消息给服务器端

  44. 服务器端也向客户端发送一个使用协商好的会话秘钥加密的 finished 消息

  45. 从现在开始,接下来整个会话都使用会话秘钥进行加密

  46. TCP 数据包

  47. HTTP 协议……

    如果浏览器是Google出品的,它不会使用HTTP协议来获取页面信息,而是会与服务器端发送请求,商讨使用SPDY协议。

     

    如果浏览器使用HTTP协议,它会向服务器发送请求。

     

    如果HTML引入了 www.google.com 域名之外的资源,浏览器会回到上面解析域名那一步,按照下面的步骤往下一步一步执行,请求中的 Host 头部会变成另外的域名。

  48. HTTP服务器请求处理

  49. 服务器验证其上已经配置了google.com的虚拟主机

  50. 服务器验证google.com接受GET方法

  51. 服务器验证该用户可以使用GET方法(根据IP地址,身份信息等)

  52. 服务器根据请求信息获取相应的响应内容

    这种情况下由于访问路径是 "/" ,会访问index这个文件。(你可以重写这个规则,但是这个是最常用的)

  53. 服务器会使用指定的处理程序分析处理这个文件,比如假设Google使用PHP

  54. 服务器会使用PHP解析index文件,并捕获输出

  55. 服务器会返回PHP的输出结果给请求者

  56. HTML 解析

    浏览器渲染引擎从网络层取得请求的文档,一般情况下文档会分成8kB大小的分块传输。HTML解析器的主要工作是对HTML文档进行解析,生成解析树。

  57. 解析算法

    HTML不能使用常见的自顶向下或自底向上方法来进行分析。

  58. 解析结束之后

    文档的状态会变为“完成”,浏览器会进行“加载”事件。

  59. 浏览器容错

    在解析HTML网页是永远不会出现“语法错误”,浏览器会修复所以错误,然后继续解析。加载/预加载网页的外部资源(CSS,图像,Javascript 文件等),执行同步 Javascript 代码。

  60. CSS 解析

  61. 根据 CSS词法和句法 分析CSS文件和 <style> 标签

  62. 每个CSS文件都被解析成一个样式表对象

    这个对象里包含了带有选择器的CSS规则,和对应CSS语法的对象

  63. CSS解析器可能是自顶向下的,也可能是使用解析器生成器生成的自底向上的解析器

  64. 页面渲染

  65. 通过遍历DOM节点树创建一个“Frame 树”或“渲染树”,并计算每个节点的各个CSS样式值

  66. 通过累加子节点的宽度,该节点的水平内边距(padding)、边框(border)和外边距(margin),自底向上的计算

  67. 通过自顶向下的给每个节点的子节点分配可行宽度,计算每个节点的实际宽度

  68. 通过应用文字折行、累加子节点的高度和此节点的内边距(padding)、边框(border)和外边距(margin),自底向上的计算每个节点的高度

  69. 使用上面的计算结果构建每个节点的坐标

  70. 当存在元素使用 floated,位置有 absolutely 或 relatively 属性的时候,会有更多复杂的计算

    详见http://dev.w3.org/csswg/css2/ 和 http://www.w3.org/Style/CSS/current-work

  71. 创建layer(层)来表示页面中的哪些部分可以成组的被绘制,而不用被重新栅格化处理。每个帧对象都被分配给一个层

  72. 页面上的每个层都被分配了纹理(?)

  73. 每个层的帧对象都会被遍历,计算机执行绘图命令绘制各个层,此过程可能由CPU执行栅格化处理,或者直接通过D2D/SkiaGL在GPU上绘制

  74. 上面所有步骤都可能利用到最近一次页面渲染时计算出来的各个值,这样可以减少不少计算量

  75. 计算出各个层的最终位置,一组命令由 Direct3D/OpenGL发出,GPU命令缓冲区清空,命令传至GPU并异步渲染,帧被送到Window Server。

  76. GPU 渲染

  77. 在渲染过程中,图形处理层可能使用通用用途的CPU,也可能使用图形处理器GPU

  78. 当使用GPU用于图形渲染时,图形驱动软件会把任务分成多个部分,这样可以充分利用GPU强大的并行计算能力,用于在渲染过程中进行大量的浮点计算。

  79. Window Server

  80. 后期渲染与用户引发的处理

    渲染结束后,浏览器根据某些时间机制运行JavaScript代码(比如Google Doodle动画)或与用户交互(在搜索栏输入关键字获得搜索建议)。类似Flash和Java的插件也会运行,尽管Google主页里没有。这些脚本可以触发网络请求,也可能改变网页的内容和布局,产生又一轮渲染与绘制。

评论(6

发生太多事情了,让我静一静……
我也需要静一静…
我也想静一静
如果在中国的话,GFW要不要考虑进去
作者
可以啊,欢迎 Fork
感觉把墙考虑进去是挺有意思的样子,mark一下
取消