运维咖啡吧

享受技术带来的乐趣,体验生活给予的感动

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

介绍

当你在面试的时候经常会被问到一个问题:当你在浏览器中输入google.com并且按下回车之后发生了什么?

原文地址为:https://github.com/skyline75489/what-happens-when-zh_CN

按下"g"键

接下来的内容介绍了物理键盘和系统中断的工作原理,但是有一部分内容却没有涉及。当你按下“g”键,浏览器接收到这个消息之后,会触发自动完成机制。浏览器根据自己的算法,以及你是否处于隐私浏览模式,会在浏览器的地址框下方给出输入建议。大部分算法会优先考虑根据你的搜索历史和书签等内容给出建议。你打算输入"google.com",因此给出的建议并不匹配。但是输入过程中仍然有大量的代码在后台运行,你的每一次按键都会使得给出的建议更加准确。甚至有可能在你输入之前,浏览器就将"google.com" 建议给你。

回车键按下

为了从零开始,我们选择键盘上的回车键被按到最低处作为起点。在这个时刻,一个专用于回车键的电流回路被直接地或者通过电容器间接地闭合了,使得少量的电流进入了键盘的逻辑电路系统。这个系统会扫描每个键的状态,对于按键开关的电位弹跳变化进行噪音消除(debounce),并将其转化为键盘码值。在这里,回车的码值是13。键盘控制器在得到码值之后,将其编码,用于之后的传输。现在这个传输过程几乎都是通过通用串行总线(USB)或者蓝牙(Bluetooth)来进行的,以前是通过PS/2或者ADB连接进行。

USB键盘:

虚拟键盘(触屏设备):

产生中断[非USB键盘]

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

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

HID把键盘按下的事件传送给 KBDHID.sys驱动,把HID的信号转换成一个扫描码(Scancode),这里回车的扫描码是VK_RETURN(0x0d)KBDHID.sys 驱动和 KBDCLASS.sys(键盘类驱动,keyboard classdriver)进行交互,这个驱动负责安全地处理所有键盘和小键盘的输入事件。之后它又去调用Win32K.sys,在这之前有可能把消息传递给安装的第三方键盘过滤器。这些都是发生在内核模式。

Win32K.sys 通过 GetForegroundWindow()API函数找到当前哪个窗口是活跃的。这个API函数提供了当前浏览器的地址栏的句柄。Windows系统的"messagepump"机制调用 SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, lParam) 函数,lParam是一个用来指示这个按键的更多信息的掩码,这些信息包括按键重复次数(这里是0),实际扫描码(可能依赖于OEM厂商,不过通常不会是VK_RETURN ),功能键(alt, shift,ctrl)是否被按下(在这里没有),以及一些其他状态。

Windows的 SendMessage API直接将消息添加到特定窗口句柄 hWnd的消息队列中,之后赋给 hWnd 的主要消息处理函数 WindowProc将会被调用,用于处理队列中的消息。

当前活跃的句柄 hWnd 实际上是一个editcontrol控件,这种情况下,WindowProc 有一个用于处理 WM_KEYDOWN消息的处理器,这段代码会查看 SendMessage 传入的第三个参数 wParam,因为这个参数是 VK_RETURN ,于是它知道用户按下了回车键。

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

中断信号引发了I/O Kit Kext键盘驱动的中断处理事件,驱动把信号翻译成键码值,然后传给OS X的WindowServer 进程。然后, WindowServer将这个事件通过Mach端口分发给合适的(活跃的,或者正在监听的)应用程序,这个信号会被放到应用程序的消息队列里。队列中的消息可以被拥有足够高权限的线程使用mach_ipc_dispatch 函数读取到。这个过程通常是由 NSApplication主事件循环产生并且处理的,通过 NSEventTypeKeyDownNSEvent

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

当使用图形化的 X Server 时,X Server会按照特定的规则把键码值再一次映射,映射成扫描码。当这个映射过程完成之后,X Server 把这个按键字符发送给窗口管理器(DWM,metacity,i3等等),窗口管理器再把字符发送给当前窗口。当前窗口使用有关图形API把文字打印在输入框内。

解析URL

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

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

转换非 ASCII 的 Unicode 字符

检查 HSTS 列表

DNS 查询

ARP 过程

要想发送 ARP(地址解析协议)广播,我们需要有一个目标IP地址,同时还需要知道用于发送 ARP 广播的接口的 MAC 地址。

如果缓存没有命中:

ARP Request:

    Sender MAC: interface:mac:address:here
    Sender IP: interface.ip.goes.here
    Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
    Target IP: target.ip.goes.here

根据连接主机和路由器的硬件类型不同,可以分为以下几种情况:

直连:

集线器:

交换机:

ARP Reply:

    Sender MAC: target:mac:address:here
    Sender IP: target.ip.goes.here
    Target MAC: interface:mac:address:here
    Target IP: interface.ip.goes.here

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

使用套接字

当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数socket ,请求一个 TCP流套接字,对应的参数是 AF_INET/AF_INET6SOCK_STREAM

到了现在,TCP 封包已经准备好了,可以使用下面的方式进行传输:

对于大部分家庭网络和小型企业网络来说,封包会从本地计算机出发,经过本地网络,再通过调制解调器把数字信号转换成模拟信号,使其适于在电话线路,有线电视光缆和无线电话线路上传输。在传输线路的另一端,是另外一个调制解调器,它把模拟信号转换回数字信号,交由下一个网络节点处理。节点的目标地址和源地址将在后面讨论。

大型企业和比较新的住宅通常使用光纤或直接以太网连接,这种情况下信号一直是数字的,会被直接传到下一个网络节点进行处理。

最终封包会到达管理本地子网的路由器。在那里出发,它会继续经过自治区域(autonomoussystem, 缩写AS)的边界路由器,其他自治区域,最终到达目标服务器。一路上经过的这些路由器会从IP数据报头部里提取出目标地址,并将封包正确地路由到下一个目的地。IP数据报头部time to live (TTL)域的值每经过一个路由器就减1,如果封包的TTL变为0,或者路由器由于网络拥堵等原因封包队列满了,那么这个包会被路由器丢弃。

上面的发送和接受过程在 TCP 连接期间会发生很多次:

TLS 握手

HTTP 协议

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

如果浏览器使用 HTTP 协议而不支持 SPDY协议,它会向服务器发送这样的一个请求:

    GET / HTTP/1.1
    Host: google.com
    Connection: close
    [其他头部]

“其他头部”包含了一系列的由冒号分割开的键值对,它们的格式符合HTTP协议标准,它们之间由一个换行符分割开来。(这里我们假设浏览器没有违反HTTP协议标准的bug,同时假设浏览器使用HTTP/1.1 协议,不然的话头部可能不包含 Host 字段,同时 GET请求中的版本号会变成 HTTP/1.0 或者 HTTP/0.9 。)

HTTP/1.1 定义了“关闭连接”的选项"close",发送者使用这个选项指示这次连接在响应结束之后会断开。例如:

Connection:close

不支持持久连接的 HTTP/1.1 应用必须在每条消息中都包含 "close" 选项。

在发送完这些请求和头部之后,浏览器发送一个换行符,表示要发送的内容已经结束了。

服务器端返回一个响应码,指示这次请求的状态,响应的形式是这样的:

    200 OK
    [响应头部]

然后是一个换行,接下来有效载荷(payload),也就是 www.google.com的HTML内容。服务器下面可能会关闭连接,如果客户端请求保持连接的话,服务器端会保持连接打开,以供之后的请求重用。

如果浏览器发送的HTTP头部包含了足够多的信息(例如包含了 Etag头部),以至于服务器可以判断出,浏览器缓存的文件版本自从上次获取之后没有再更改过,服务器可能会返回这样的响应:

    304 Not Modified
    [响应头部]

这个响应没有有效载荷,浏览器会从自己的缓存中取出想要的内容。

在解析完 HTML之后,浏览器和客户端会重复上面的过程,直到HTML页面引入的所有资源(图片,CSS,favicon.ico等等)全部都获取完毕,区别只是头部的GET / HTTP/1.1 会变成 GET /$(相对www.google.com的URL) HTTP/1.1

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

HTTP 服务器请求处理

HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx,以及 Windows 上的 IIS。

浏览器背后的故事

当服务器提供了资源之后(HTML,CSS,JS,图片等),浏览器会执行下面的操作:

浏览器

浏览器的功能是从服务器上取回你想要的资源,然后展示在浏览器窗口当中。资源通常是HTML 文件,也可能是PDF,图片,或者其他类型的内容。资源的位置通过用户提供的 URI(UniformResource Identifier) 来确定。

浏览器解释和展示 HTML 文件的方法,在 HTML 和 CSS的标准中有详细介绍。这些标准由 Web 标准组织 W3C(World Wide WebConsortium) 维护。

不同浏览器的用户界面大都十分接近,有很多共同的 UI 元素:

浏览器高层架构

组成浏览器的组件有:

HTML 解析

浏览器渲染引擎从网络层取得请求的文档,一般情况下文档会分成8kB大小的分块传输。

HTML 解析器的主要工作是对 HTML 文档进行解析,生成解析树。

解析树是以 DOM 元素以及属性为节点的树。DOM是文档对象模型(Document ObjectModel)的缩写,它是 HTML 文档的对象表示,同时也是 HTML元素面向外部(如Javascript)的接口。树的根部是"Document"对象。整个 DOM 和HTML 文档几乎是一对一的关系。

解析算法

HTML不能使用常见的自顶向下或自底向上方法来进行分析。主要原因有以下几点:

由于不能使用常用的解析技术,浏览器创造了专门用于解析 HTML的解析器。解析算法在 HTML5标准规范中有详细介绍,算法主要包含了两个阶段:标记化(tokenization)和树的构建。

解析结束之后

浏览器开始加载网页的外部资源(CSS,图像,Javascript 文件等)。

此时浏览器把文档标记为可交互的(interactive),浏览器开始解析处于“推迟(deferred)”模式的脚本,也就是那些需要在文档解析完毕之后再执行的脚本。之后文档的状态会变为“完成(complete)”,浏览器会触发“加载(load)”事件。

注意解析 HTML 网页时永远不会出现“无效语法(InvalidSyntax)”错误,浏览器会修复所有错误内容,然后继续解析。

CSS 解析

页面渲染

GPU 渲染

Window Server

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

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