从输入 URL 到页面加载完成的过程中都发生了什么事情?
2014-11-21 11:06
387 查看
不过写这篇文章并不是为了帮大家准备面试,而是想借这道题来介绍计算机和互联网的基础知识,让读者了解它们之间是如何关联起来的。
为了便于理解,我将整个过程分为了六个问题来展开。
触摸屏一种传感器,目前大多是基于电容(Capacitive)来实现的,以前都是直接覆盖在显示屏上的,不过最近出现了3种嵌入到显示屏中的技术,第一种是iPhone5的In-cell,它能减小了0.5毫米的厚度,第二种是三星使用的On-cell技术,第三种是国内厂商喜欢用的OGS全贴合技术,具体细节可以阅读这篇文章。
当手指在这个传感器上触摸时,有些电子会传递到手上,从而导致该区域的电压变化,触摸屏控制器芯片根据这个变化就能计算出所触摸的位置,然后通过总线接口将信号传到CPU的引脚上。
以Nexus5为例,它所使用的触屏控制器是SynapticsS3350B,总线接口为I²C,以下是Synaptics触摸屏和处理器连接的示例:
左边是处理器,右边是触摸屏控制器,中间的SDA和SCL连线就是I²C总线接口。
移动设备中的CPU并不是一个单独的芯片,而是和GPU等芯片集成在一起,被称为SoC(片上系统)。
为了便于理解,我将整个过程分为了六个问题来展开。
第一个问题:从输入URL到浏览器接收的过程中发生了什么事情?
从触屏到CPU
首先是「输入URL」,大部分人的第一反应会是键盘,不过为了与时俱进,这里将介绍触摸屏设备的交互。触摸屏一种传感器,目前大多是基于电容(Capacitive)来实现的,以前都是直接覆盖在显示屏上的,不过最近出现了3种嵌入到显示屏中的技术,第一种是iPhone5的In-cell,它能减小了0.5毫米的厚度,第二种是三星使用的On-cell技术,第三种是国内厂商喜欢用的OGS全贴合技术,具体细节可以阅读
当手指在这个传感器上触摸时,有些电子会传递到手上,从而导致该区域的电压变化,触摸屏控制器芯片根据这个变化就能计算出所触摸的位置,然后通过总线接口将信号传到CPU的引脚上。
以Nexus5为例,它所使用的触屏控制器是
左边是处理器,右边是触摸屏控制器,中间的SDA和SCL连线就是I²C总线接口。
CPU内部的处理
移动设备中的CPU并不是一个单独的芯片,而是和GPU等芯片集成在一起,被称为SoC(片上系统)。
前面提到了触屏和CPU的连接,这个连接和大部分计算机内部的连接一样,都是通过电气信号来进行通信的,也就是电压高低的变化,如下面的时序图:
在时钟的控制下,这些电流会经过
除了计算,在CPU中还需要存储单元来加载和存储数据,这个存储单元一般通过触发器(Flip-flop)来实现,称为寄存器。
以上这些概念都比较抽象,推荐阅读「
另外其实我也是刚开始学习CPU芯片的实现,所以就不在这误人子弟了,感兴趣的读者请阅读本节后面推荐的书籍。
从CPU到操作系统内核
前面说到触屏控制器将电气信号发送到CPU对应的引脚上,接着就会触发CPU的中断机制,以Linux为例,每个外部设备都有一标识符,称为中断请求(IRQ)号,可以通过/proc/interrupts文件来查看系统中所有设备的中断请求号,以下是Nexus7(2013)的部分结果:
shell@flo:/$cat/proc/interrupts
CPU0
17:0GICdg_timer
294:1973609msmgpioelan-ktf3k
314:679msmgpioKEY_POWER
因为Nexus7使用了ELAN的触屏控制器,所以结果中的elan-ktf3k就是触屏的中断请求信息,其中294是中断号,1973609是触发的次数(手指单击时会产生两次中断,但滑动时会产生上百次中断)。
为了简化这里不考虑优先级问题,以ARMv7架构的处理器为例,当中断发生时,CPU会停下当前运行的程序,保存当前执行状态(如PC值),进入IRQ状态),然后跳转到对应的中断处理程序执行,这个程序一般由第三方内核驱动来实现,比如前面提到的Nexus7的驱动源码在这里
这个驱动程序将读取I²C总线中传来的位置数据,然后通过内核的
/dev/input/event0这个设备文件中,比如下面展示了一次触摸事件所产生的信息:
130|shell@flo:/$getevent-lt/dev/input/event0
[414624.658986]EV_ABSABS_MT_TRACKING_ID0000835c
[414624.659017]EV_ABSABS_MT_TOUCH_MAJOR0000000b
[414624.659047]EV_ABSABS_MT_PRESSURE0000001d
[414624.659047]EV_ABSABS_MT_POSITION_X000003f0
[414624.659078]EV_ABSABS_MT_POSITION_Y00000588
[414624.659078]EV_SYNSYN_REPORT00000000
[414624.699239]EV_ABSABS_MT_TRACKING_IDffffffff
[414624.699270]EV_SYNSYN_REPORT00000000
从操作系统GUI到浏览器
前面提到Linux内核已经完成了对硬件的抽象,其它程序只需要通过监听/dev/input/event0文件的变化就能知道用户进行了哪些触摸操作,不过如果每个程序都这么做实在太麻烦了,所以在图像操作系统中都会包含GUI框架来方便应用程序开发,比如Linux下著名的
但Android并没有使用X,而是自己实现了一套GUI框架,其中有个
/dev/input/目录下的文件,然后将这些信息传递到Android的窗口管理服务(
就这样,我们解答了第一个问题,不过由于时间有限,这里省略了很多细节,想进一步学习的读者推荐阅读以下书籍。
扩展学习
《《
《
《
《
《
《
《
《
第二个问题:浏览器如何向网卡发送数据?
从浏览器到浏览器内核
前面提到操作系统GUI将输入事件传递到了浏览器中,在这过程中,浏览器可能会做一些预处理,比如Chrome会根据历史统计来预估所输入字符对应的网站,比如输入了「ba」,根据之前的历史发现90%的概率会访问「www.baidu.com」,因此就会在输入回车前就马上开始建立TCP链接甚至渲染了,这里面还有很多其它策略,感兴趣的读者推荐阅读接着是输入URL后的「回车」,这时浏览器会对URL进行检查,首先判断协议,如果是http就按照Web来处理,另外还会对这个URL进行安全检查,然后直接调用浏览器内核中的对应方法,比如
在浏览器内核中会先查看缓存,然后设置UA等HTTP信息,接着调用不同平台下网络请求的方法。
需要注意浏览器和浏览器内核是不同的概念,浏览器指的是Chrome、Firefox,而浏览器内核则是Blink、Gecko,浏览器内核只负责渲染,GUI及网络连接等跨平台工作则是浏览器实现的
HTTP请求的发送
因为网络的底层实现是和内核相关的,所以这一部分需要针对不同平台进行处理,从应用层角度看主要做两件事情:通过DNS查询IP、通过Socket发送数据,接下来就分别介绍这两方面的内容。DNS查询
应用程序可以直接调用Libc提供的DNS查询其实是基于UDP来实现的,这里我们通过一个具体例子来了解它的查找过程,以下是使用
dig+tracefex.baidu.com命令得到的结果(省略了一些):
;<<>>DiG9.8.3-P1<<>>+tracefex.baidu.com
;;globaloptions:+cmd
.11157INNSg.root-servers.net.
.11157INNSi.root-servers.net.
.11157INNSj.root-servers.net.
.11157INNSa.root-servers.net.
.11157INNSl.root-servers.net.
;;Received228bytesfrom8.8.8.8#53(8.8.8.8)in220ms
com.172800INNSa.gtld-servers.net.
com.172800INNSc.gtld-servers.net.
com.172800INNSm.gtld-servers.net.
com.172800INNSh.gtld-servers.net.
com.172800INNSe.gtld-servers.net.
;;Received503bytesfrom192.36.148.17#53(192.36.148.17)in185ms
baidu.com.172800INNSdns.baidu.com.
baidu.com.172800INNSns2.baidu.com.
baidu.com.172800INNSns3.baidu.com.
baidu.com.172800INNSns4.baidu.com.
baidu.com.172800INNSns7.baidu.com.
;;Received201bytesfrom192.48.79.30#53(192.48.79.30)in1237ms
fex.baidu.com.7200INCNAMEfexteam.duapp.com.
fexteam.duapp.com.300INCNAMEduapp.n.shifen.com.
n.shifen.com.86400INNSns1.n.shifen.com.
n.shifen.com.86400INNSns4.n.shifen.com.
n.shifen.com.86400INNSns2.n.shifen.com.
n.shifen.com.86400INNSns5.n.shifen.com.
n.shifen.com.86400INNSns3.n.shifen.com.
;;Received258bytesfrom61.135.165.235#53(61.135.165.235)in2ms
可以看到这是一个逐步缩小范围的查找过程,首先由本机所设置的DNS服务器(8.8.8.8)向DNS根节点查询负责.com区域的域务器,然后通过其中一个负责.com的服务器查询负责baidu.com的服务器,最后由其中一个baidu.com的域名服务器查询fex.baidu.com域名的地址。
可能你在查询某些域名的时会发现和上面不一样,最底将看到有个奇怪的服务器抢先返回结果。。。
这里为了方便描述,忽略了很多不同的情况,比如127.0.0.1其实走的是
通过Socket发送数据
有了IP地址,就可以通过SocketAPI来发送数据了,这时可以选择TCP或UDP协议,具体使用方法这里就不介绍了,推荐阅读HTTP常用的是TCP协议,由于TCP协议的具体细节到处都能看到,所以本文就不介绍了,这里谈一下TCP的Head-of-lineblocking问题:假设客户端的发送了3个TCP片段(segments),编号分别是1、2、3,如果编号为1的包传输时丢了,即便编号2和3已经到达也只能等待,因为TCP协议需要保证顺序,这个问题在HTTPpipelining下更严重,因为HTTPpipelining可以让多个HTTP请求通过一个TCP发送,比如发送两张图片,可能第二张图片的数据已经全收到了,但还得等第一张图片的数据传到。
为了解决TCP协议的性能问题,Chrome团队去年提出了
chrome://net-internals/#spdy页面来发现。
虽然目前除了Google还没人用QUIC,但我觉得挺有前景的,因为优化TCP需要升级系统内核(比如
浏览器对同一个域名有连接数限制,
另外,因为HTTP请求是纯文本格式的,所以在TCP的数据段中可以直接分析HTTP的文本,如果发现。。。
Socket在内核中的实现
前面说到浏览器的跨平台库通过调用SocketAPI来发送数据,那么SocketAPI是如何实现的呢?以Linux为例,它的实现在这里
底层网络协议的具体例子
接下来如果继续介绍IP协议和MAC协议可能很多读者会晕,所以本节将使用最底下是实际的二进制数据,中间是解析出来的各个字段值,可以看到其中最底部为HTTP协议(HypertextTransferProtocol),在HTTP之前有54字节(0x36),这就是底层网络协议所带来的开销,我们接下来对这些协议进行分析。
在HTTP之上是TCP协议(TransmissionControlProtocol),它的具体内容如下图所示:
通过底部的二进制数据,可以看到TCP协议是加在HTTP文本前面的,它有20个字节,其中定义了本地端口(Sourceport)和目标端口(Destinationport)、顺序序号(SequenceNumber)、窗口长度等信息,以下是TCP协议各个部分数据的完整介绍:
0123
01234567890123456789012345678901
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|SourcePort|DestinationPort|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|SequenceNumber|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|AcknowledgmentNumber|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Data||U|A|E|R|S|F||
|Offset|Reserved|R|C|O|S|Y|I|Window|
|||G|K|L|T|N|N||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Checksum|UrgentPointer|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Options|Padding|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|data|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
具体各个字段的作用这里就不介绍了,感兴趣的读者可以阅读
需要注意的是,在TCP协议中并没有IP地址信息,因为这是在上一层的IP协议中定义的,如下图所示:
IP协议同样是在TCP前面的,它也有20字节,在这里指明了版本号(Version)为4,源(Source)IP为
192.168.1.106,目标(Destination)IP为
119.75.217.56,因此IP协议最重要的作用就是确定IP地址。
因为IP协议中可以查看到目标IP地址,所以如果发现某些特定的IP地址,某些路由器就会。。。
但是,光靠IP地址是无法进行通信的,因为IP地址并不和某台设备绑定,比如你的笔记本的IP在家中是
192.168.1.1,但到公司就变成
172.22.22.22了,所以在底层通信时需要使用一个固定的地址,这就是MAC(mediaaccesscontrol)地址,每个网卡出厂时的MAC地址都是固定且唯一的。
因此再往上就是MAC协议,它有14字节,如下所示:
当一台电脑加入网络时,需要通过
最顶上的Frame是代表Wireshark的抓包序号,并不是网络协议
就这样,我们解答了第二个问题,不过其实这里面还有很多很多细节没介绍,建议大家通过下面的书籍进一步学习。
扩展学习
《《
《
第三个问题:数据如何从本机网卡发送到服务器?
从内核到网络适配器(NetworkInterfaceCard)
前面说到调用SocketAPI后内核会对数据进行底层协议栈的封装,接下来启动以Nexus5为例,它使用的是博通
连接Wi-Fi路由
Wi-Fi网卡需要通过Wi-Fi路由来与外部通信,原理是基于无线电,通过电流变化来产生无线电,这个过程也叫「调制」,而反过来无线电可以引起电磁场变化,从而产生电流变化,利用这个原理就能将无线电中的信息解读出来就叫「解调」,其中单位时间内变化的次数就称为频率,目前在Wi-Fi中所采用的频率分为2.4GHz和5GHz两种。在同一个Wi-Fi路由下,因为采用的频率相同,同时使用时会发生冲突,为了解决这个问题,Wi-Fi采用了被称为
而同样基于无线电原理的2G/3G/LTE也会遇到类似的问题,但它并没有采用Wi-Fi那样的独占方案,而是通过频分(FDMA)、时分(TDMA)和码分(CDMA)来进行复用,具体细节这里就不展开了。
以小米路由为例,它使用的芯片是
路由器中的操作系统可以基于
因为内网设备的IP都是类似
192.168.1.x这样的内网地址,外网无法直接向这个地址发送数据,所以网络数据在经过路由时,路由会修改相关地址和端口,这个操作称为
最后家庭路由一般会通过
运营商网络内的路由
数据过双绞线发送到运营商网络后,还会经过很多个中间路由转发,读者可以通过traceroute命令或者当数据传递到这些路由器后,路由器会取出包中目的地址的前缀,通过内部的转发表查找对应的输出链路,而这个转发表是如何得到的呢?这就是路由器中最重要的选路算法了,可选的有很多,我对这方面并不太了解,看起来
主干网间的传输
对于长线的数据传输,通常使用光纤作为介质,光纤是基于光的全反射来实现的,使用光纤需要专门的发射器通过既然是基于光来传输数据,数据传输速度也就取决于光的速度,在真空中的光速接近于30万千米/秒,由于光纤包层(cladding)中的折射率(refractiveindex)为1.52,所以实际光速是20万千米/秒左右,从首都机场飞往广州白云机场的距离是1967千米,按照这个距离来算需要花费10毫秒才能抵达。这意味着如果你在北京,服务器在广州,等你发出数据到服务器返回数据至少得等20毫秒,实际情况预计是2-3倍,因为这其中还有各个节点路由处理的耗时,比如我测试了一个广州的IP发现平均延迟为60毫秒。
这个延迟是现有科技无法解决的(除非找到超过光速的方法),只能通过CDN来让传输距离变短,或尽量减少串行的来回请求(比如TCP建立连接所需的3次握手)。
IDC内网
数据通过光纤最终会来到服务器所在的IDC机房,进入IDC内网,这时可以先通过这里的带宽成本很高,是按照峰值来结算的,以每月每Gbps(注意这里指的是bit,而不是Byte)为单位,北京这边价格在十万人民币以上,一般网站使用1G到10G不等。