本文试图回答一个古老的面试问题:当你在浏览器中输入Google.com
并且按下回车之后发生了什么?
不过我们不再局限于平常的回答,而是想办法回答地尽可能具体,不遗漏任何细节。
这将是一个协作的过程,所以深入挖掘吧,并且帮助我们一起完善它。仍然有大量的细节等待着你来添加,欢迎向我们发送Pull Requset
!
我们从回车键按下开始。
回车键按下
为了从头开始,我们选择键盘上的回车键被按到最低处作为起点。在这个时刻,一个专用于回车键的电流回路被直接或者通过电容器闭合了,使得少量的电流进入了键盘的逻辑电路系统。这个系统会扫描每个键的状态,对于按键开关的电位弹跳变化进行噪音消除(debounce
),并将其转化为键盘码值。在这里,回车的码值是13。键盘控制器在得到码值之后,将其编码,用于之后的传输。现在这个传输过程几乎都是通过通用串行总线(USB
)或者蓝牙(Bluetooth
)来进行的,以前是通过PS/2
或者ADB
连接进行。
USB键盘:
- 键盘的
USB
元件通过计算机上的USB
接口与USB
控制器相连接,USB
接口中的第一号针为它提供了5V
的电压 - 键码值存储在键盘内部电路一个叫做
endpoint
的寄存器内 USB
控制器大概每隔10ms
便查询一次endpoint
以得到存储的键码值数据,这个最短时间间隔由键盘提供- 键值码值通过
USB
串行接口引擎被转换成一个或者多个遵循低层USB
协议的USB
数据包 - 这些数据包通过
D+
针或者D-
针(中间的两个针),以最高1.5Mb/s
的速度从键盘传输至计算机。速度限制是因为人机交互设备总是被声明成”低速设备”(USB 2.0 compliance
) - 这个串行信号在计算机的
USB
控制器处被解码,然后被人机交互设备通用键盘驱动进行进一步解释。之后按键的码值被传输到操作系统的硬件抽象层
虚拟键盘(触屏设备):
- 在现代电容屏上,当用户把手指放在屏幕上时,一小部分电流从传导层的静电域经过手指传导,形成了一个回路,使得屏幕上触控的那一点电压下降,屏幕控制器产生一个中断,报告这次“点击”的坐标
- 然后移动操作系统通知当前活跃的应用,有一个点击事件发生在它的某个
GUI
部件上了,现在这个部件是虚拟键盘的按钮 - 虚拟键盘引发一个软中断,返回给
OS
一个“按键按下”消息 - 这个消息又返回来向当前活跃的应用通知一个“按键按下”事件
产生中断[非USB键盘]
键盘在它的中断请求线(IRQ
)上发送信号,信号会被中断控制器映射到一个中断向量,实际上就是一个整型数。CPU
使用中断描述符表(IDT
)把中断向量映射到对应函数,这些函数被称为中断处理器,它们由操作系统内核提供。当一个中断到达时,CPU
根据IDT
和中断向量索引到对应的中端处理器,然后操作系统内核出场了。
(Windows)一个 WM_KEYDOWN 消息被发往应用程序
HID
把键盘按下的事件传送给 KBDHID.sys
驱动,把HID
的信号转换成一个扫描码(Scancode
),这里回车的扫描码是VK_RETURN(0x0d)
。 KBDHID.sys
驱动和 KBDCLASS.sys
(键盘类驱动,keyboard class driver
)进行交互,这个驱动负责安全地处理所有键盘和小键盘的输入事件。之后它又去调用 Win32K.sys
,在这之前有可能把消息传递给安装的第三方键盘过滤器。这些都是发生在内核模式。
Win32K.sys
通过 GetForegroundWindow() API
函数找到当前哪个窗口是活跃的。这个API
函数提供了当前浏览器的地址栏的句柄。Windows
系统的message pump
机制调用 SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, lParam)
函数, lParam
是一个用来指示这个按键的更多信息的掩码,这些信息包括按键重复次数(这里是0),实际扫描码(可能依赖于OEM
厂商,不过通常不会是 VK_RETURN
),功能键(alt
, shift
, ctrl
)是否被按下(在这里没有),以及一些其他状态。
Windows
的 SendMessage API
直接将消息添加到特定窗口句柄 hWnd
的消息队列中,之后赋给 hWnd
的主要消息处理函数 WindowProc
将会被调用,用于处理队列中的消息。
当前活跃的句柄 hWnd
实际上是一个edit control
控件,这种情况下,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
主事件循环产生并且处理的,通过 NSEventType
为 KeyDown
的 NSEvent
。
(GNU/Linux)Xorg 服务器监听键码值
当使用图形化的 X Server
时,X Server
会按照特定的规则把键码值再一次映射,映射成扫描码。当这个映射过程完成之后, X Server
把这个按键字符发送给窗口管理器(DWM
,metacity
, i3
等等),窗口管理器再把字符发送给当前窗口。当前窗口使用有关图形API
把文字打印在输入框内。
解析URL
浏览器通过URL
能够知道下面的信息:
Protocol ”http”
使用HTTP
协议
Resource ”/”
请求的资源是主页(index
)
输入的是URL还是搜索的关键字?
当协议或主机名不合法时,浏览器会将地址栏中输入的文字传给默认的搜索引擎。大部分情况下,在把文字传递给搜索引擎的时候,URL
会带有特定的一串字符,用来告诉搜索引擎这次搜索来自这个特定浏览器。
检查HSTS列表···
- 浏览器检查自带的“预加载
HSTS
(HTTP
严格传输安全)”列表,这个列表里包含了那些请求浏览器只使用HTTPS
进行连接的网站 - 如果网站在这个列表里,浏览器会使用
HTTPS
而不是HTTP
协议,否则,最初的请求会使用HTTP
协议发送 - 注意,一个网站哪怕不在
HSTS
列表里,也可以要求浏览器对自己使用HSTS
政策进行访问。浏览器向网站发出第一个HTTP
请求之后,网站会返回浏览器一个响应,请求浏览器只使用HTTPS
发送请求。然而,就是这第一个HTTP
请求,却可能会使用户收到downgrade attack
的威胁,这也是为什么现代浏览器都预置了HSTS
列表。
转换非ASCII的Unicode字符
- 浏览器检查输入是否含有不是
a-z
,A-Z
,0-9
,-
或者.
的字符 - 这里主机名是
google.com
,所以没有非ASCII
的字符,如果有的话,浏览器会对主机名部分使用Punycode
编码
首先,对于http
肯定是有客户端和服务器的,在这个语境中,客户端和服务器本质上也都是一个软件,实现了http
协议相关标准的软件。客户端一般由都是由浏览器充当,也就是说,在浏览器中实现了http
客户端的相关功能。而服务器的实现就多种多样啦,我们可以用java
写servlet
,c#
写ASP.net
,还有php
,ruby
,Python
,nodejs
等。实际上我想,http
服务在操作系统底层应该有实现,而这些语言只不过是利用操作系统的http
服务封装成自己的接口供开发人员编写web
服务器程序。而我们熟悉的IIS
,Tomcat
,Apache
,Web logic
,都是能够作为某些web
服务器容器的大型服务器平台,它们都会包括很多更为强大的功能。一般来说,我们这里所说的服务器指的是自己用特定语言写的web
应用服务器程序。nodejs
不需要web
容器,本身就有对http
的直接应用模块,所以用nodejs
创建一个web
服务器是很方便的。
整体通信
有了客户端和服务器,就可以开始通信了,整体上分为3个步骤:
- 因为
http
是构建在TCP
之上,那么自然是要经过3次握手创建连接。 - 创建连接后,服务器会根据
url
请求中的信息进行处理,作出响应,一般来说是找到一个html
文件返回给客户端。 - 客户端即浏览器得到
html
,进行渲染。
下面详细说下这3个步骤
创建连接
这个跟网络关联多一些,只能大体说一下。对于http
的客户端,它的输入就是一个url
,而对于创建连接,它需要的只是url
的host
(主机)部分,而