`

Chrome的进程间通信(四)

阅读更多

Chrome的进程间通信(四)

1. Chrome的窗口控件

Chrome提供了自己的一个UI控件库,相关文档可以参见这里。用Chrome自己的话来说,我觉得市面上的七荤八素的图形控件库都不好用,于是自己倒腾倒腾实现了一套。。。
广告虽如此说,不过,Chrome的图形控件结构,我还未发现有啥非常非常特别的地方。Chrome的窗口、按钮、菜单之类的控件,都直接或间接派生自View,这个是控件基类。Chrome的View具有树形结构,其内部有一个子View数组,由此构成一个控件常用的组合模式。。。
有一个比较特殊的View子类,叫做RootView,顾名思义,它是整个View控件树的根,在Chrome中,一个正确的树形的控件结构,必须由RootView作为根。之所以要这样设计,是因为RootView有一个比较特殊的功能,那就是分发消息。。。
我们知道,一般的Windows控件,都有一个HWND,用与占据一块屏幕,捕获系统消息。Chrome中的View只是保存控件相关信息和绘制控件,里面没有HWND句柄,因此不能够捕获系统消息。在Chrome中,完整的控件架构是这样的,首先需要有一个ViewContainer,它里面包含一个RootView。ViewContainer是一个抽象类,在Window中的一个子类是HWNDViewContainer,同时,HWNDViewContainer还是MessageLoopForUI::Observer的子类。如果你看过本文第一部分描述的线程通信的内容的话,你就应该还记得,Observer是用于监听本线程内系统消息的东东。。。
当有系统消息进入此线程消息循环后,HWNDViewContainer会监听到这个情况,如果和View相关的消息,它就会调用RootView的相关方法,传递给控件。在RootView的内部,会遍历整个控件树上的控件,将消息传递给各个控件。当然,有的消息是可以独占的,比如鼠标移动发送在某个View所管辖的范围内,它会告知RootView(通过方法的返回值...),这个消息我要了,那么RootView会停止遍历。。。
在设计的时候,View对消息的处理,采取的是大而全的接口模式。就是说在View内部,提供了所有可能的消息处理接口,并提供了默认实现,所有子类只需要覆盖自己需要的消息处理函数即可。如果对MFC的消息映射有了解的话,可以知道两者的区别。MFC在设计的时候,觉得无法提供大而全的接口,因为消息总类实在太多,而且还是可扩展的,于是就有了消息映射着一套繁琐的宏。但Chrome的图形框架,显然没有做一个通用的Framework的打算,因此,可以采用这样的策略,使得子类的派生变得简单而自然。。。
每一个View的子类控件,比如Button之类的,会存储一些数据,根据消息做一些行为,并且绘制出自己。在Chrome中,画图的东西是ChromeCanvas这个类,在其内部,通过Skia和GDI实现绘制。Skia是Android团队开发的一个跨平台的图形引擎,在Chrome中负责除了文字之外,所有内容的绘制;而文字绘制的重担,在Windows中交到了GDI的手上。这样的设计会给跨平台带来一些困难,估计是由Skia实现文本绘制会比较繁琐,才会带出如此一个设计的模式。。。
另外一个历史遗留产物,就是在Windows下的图形控件,还有一些是原生的,就是说带有HWND那种传统的控件,这是Chrome身上不多的赶工期的痕迹,随着时间的宽裕,这样的原生控件会被淘汰进历史的垃圾箱,而全部变为从View派生的控件。。。
其实,对于Chrome这套控件架构我还没算摸得很熟悉,估计等到做一次插件之后会了解的更透彻,因此,只说了点皮毛,聊表心意。。。

2. Chrome的页面加载和绘制

上面这些UI控件,都是用在窗口上的(比如浏览器的外框,菜单,对话框之类的...)。我们在浏览器中看到的大部分内容,是网页页面。页面的绘制(绘制,就是把一个HTML文件变成一个活灵活现的页面展示的过程...),只有一半轮子是Chrome自己做的,还有一部分来自于WebKit,这个Apple打造的Web渲染器。。。
之所以说是一半轮子来源于WebKit,是因为WebKit本身包含两部分主要内容,一部分是做Html渲染的,另一部分是做JavaScript解析的。在Chrome中,只有Html的渲染采用了WebKit的代码,而在JavaScript上,重新搭建了一个NB哄哄的V8引擎。目标是,用WebKit + V8的强强联手,打造一款上网冲浪的法拉利,从效果来看,还着实做的不错。。。
不过,虽说Chrome和WebKit都是开源的,并联手工作。但是,Chrome还是刻意的和WebKit保持了距离,为其始乱终弃埋下了伏笔。Chrome在WebKit上封装了一层,称为WebKit Glue。Glue层中,大部分类型的结构和接口都和WebKit类似,Chrome中依托WebKit的组件,都只是调用WebKit Glue层的接口,而不是直接调用WebKit中的类型。按照Chrome自己文档中的话来说,就是,虽然我们再用WebKit实现页面的渲染,但通过这个设计(加一个间接层...)已经从某种程度大大降低了与WebKit的耦合,使得可以很容易将WebKit换成某个未来可能出现的更好的渲染引擎。。。

重用
在《梦断代码》中,有一坨调侃重用的文字。他觉着软件重用的困难一方面来自于场景本身很多变,很难设计出一套包罗万象的东西;另一方面来自于人,程序员总是瞅着别人写的代码不顺眼,总喜欢自己写一套。。。
于是,解决重用这个问题也就只有两种,写最NB人见人服无所不能的代码,或者是有很多很多NB代码共君任选。Google无疑在这两个方面做得都不错,Map/Reduce,Big Table之类的一套东西,强大到可以适合太多的场景,大大简化了N多上层应用的开发。而对开源的
利用使用,使得其可以随意挑一个巨人站到他肩膀上跳舞,每看到这种场景,MS估计都会气得拍着胸口吐血。。。
Google本身在服务端的基础底层,有很深积累,随着Chrome,Android等等客户端应用的开发,客户端的积累也逐步提升,也许,拥抱开源才是MS的正道?。。。

当你键入一个Url并敲下回车后,Chrome会在Browser进程中下载Url对应的页面资源(包括Web页面和Cookie),而不是直接将Url发送给Render进程让它们自行下载(你会越来越发现,Render进程绝对是100%的名符其实,除了绘制,几乎啥多余的事情都不会干的...)。与各个Render进程各自为站,各自管好自己所需的资源相比,这种策略仿佛会增加大量的进程间通信。之所以采用,按照这篇文档的解释,主要有三个优点,一个是避免子进程与网络通信,从而将网络通信的权限牢牢握在主进程手中,Render进程能力弱了,想造反干坏事的可能性就降低了(可以更好控制各个Render进程的权限...);另一个是有利于Cookie等持久化资源在不同页面中的共享,否则在不同Render进程中传递Cookie这样的事情,做起来更麻烦;还有一点很重要的,是可以控制与网络建立HTTP连接的数量,以Browser为代表与网络各方进行通信,各种优化策略都比较好开展(比如池化)。。。
当然,在Browser进程中进行统一的资源管理,也就意味着不再方便用WebKit进行资源下载(WebKit当然有此能力,不过再次被Chrome抛弃了...),而是依托WinHTTP来做的。WinHTTP在接受数据的过程中,会不停的把数据和相关的消息通过IPC,发送给负责绘制此页面的Render进程中对应的RenderView。在这里,路由消息中的那个ID值起了关键的作用,系统依照此ID,能够准确的将相关的消息发送到相关的View头上,这玩意发错了地方还真不是和有人把钱错到你账户上一样,因为错收的进程基本上无福消受这个意外来客,轻者页面显示混乱,重者消化不良直接噎死。。。
RenderView接收到页面信息,会一边绘制一边等待更多的资源到来,在用户看来,所请求的页面正在一点一点显示出来。当然,如果是一个通知传输开始、传输结束这样的消息,通过序列化到消息参数里面,经由IPC发过来,代价还是可以承受的,但是,想资源内容这样大段大段的字节流,如果通过消息发过来,浪费两边进程大量空间和时间,就不合适了。于是这里用到了共享内存。Browser进程将下载到的资源写到共享内存中,并将共享内存的句柄和共享区域的大小序列化在消息中发送给Render进程。Render进程拿到这个句柄,就可以通过它访问到共享内存相关的区域,读取信息并进行绘制。通过这样的方式,即享用到了统一资源管理的优点,由避免了很高的进程通信开销,左右逢源,好不快活。。。

3. Chrome页面的消息响应

Render进程是一个娇生惯养的进程,这一点从上面一段已经可以看出来了。它自己的资源它自己都不下载,而是由Browser进程来帮忙。不过Render进程也许比你想象的还要懒惰一些,它不但不自己下载资源,甚至,连自己的系统消息都不接收。。。
Render进程中不包含HWND,当你鼠标在页面上划来划去,点上点下,这些消息其实都发到了Browser进程,它们拥有页面呈现部分的HWND。Browser会将这些消息转手通过IPC发送给对应的Render进程中的RenderView,很多时候WebKit会处理此类消息,当它发现出现了某种值得告诉Browser进程的事情,它会组个报回赠给Browser进程。举个例子,你打开一个页面,然后拿鼠标在页面上乱晃。Browser这时候就像一个碎嘴大婶,不厌其烦的告诉Render进程,“鼠标动了,鼠标动了”。如果Render对这个信息无所谓,就会很无聊的应答着:“哦,哦”(发送一个回包...)。但是,当鼠标划过链接的时候,矜持的Render进程坐不住了,会大声告诉Browser进程:“换鼠标,换鼠标~~”,Browser听到后,会将鼠标从箭头状换成手指状,然后继续以上过程。。。
比较麻烦的是Paint消息,重新绘制页面是一个太频繁发生的事情,不可能重绘一次就序列化一坨字节流过去。于是策略也很清楚了,就是依然用共享内存读写,用消息发句柄。在Render进程中,会有一个共享内存池(默认值为2...),以size为key,以共享内存为值,简单的先入先出淘汰算法,利用局部性的特征,避免反复的创建和销毁共享内存(这和资源传递不一样,因为资源传递可以开一块固定大小的共享内存...)。Render进程从共享内存池中拿起一块(二维字节数组...),就好像拿着一块屏幕似的,拼了命往上绘制,为了让Render安心觉着有成就感,Browser会偷偷帮Render把这些内容绘制到屏幕上,造成Render进程直接绘制屏幕的假象。这可就苦了屏幕取词的工具们,因为在HWND上压根就没啥字符信息,全部就是一坨图像而已,啥也取不着。于是Google金山词霸,网易有道词霸各自发挥智慧,另辟蹊径,也算是都利用Chrome做了一把广告。。。
为什么不让Render进程自己拥有HWND,自己管理自己的消息,既快捷又便利。在Chrome的官方Blog上,有一篇解释的文章,基本上是这个意思,速度是必须快的发指的,但是为了用户响应,放弃一些速度是必要的,毕竟,没有人喜欢总假死的浏览器。在Browser进程中,基本上是杜绝任何同步Render进程的工作,所有操作都是异步完成。因为Render进程是不靠谱的,随时可能牺牲掉,同步它们往往导致主进程停止响应,从而导致整个浏览器停下来甚至挂掉,这个代价是不可以容忍的。但是,Windows有一个恶习,喜欢往整个HWND继承体系中发送同步消息(我不是很清楚这个状况,有人能解释么?...),这时候,如果HWND在Render进程中,就务必会导致主进程与Render进程的同步,Chrome无法控制Windows,于是,它们只能够控制Render,把它们的HWND搬到主进程中,避免同步操作,换取用户响应的速度。。。

4. 结论

整个Chrome的UI架构,就是一个权责分配的问题。可以把Browser进程看成是一个类似于朱元璋般的勤劳皇帝(详见《明朝那些事 一》...),把大多数的权利都牢牢把握在手中,这样,虽然Browser很操劳,但是整体上的协调和同步,都进行的非常顺畅。Render进程就是皇帝手下的傀儡宰相们,只负责自己的一亩三分地,听从皇帝的调配即可。这这样的环境下,Render进程的生死变得无足轻重,Render的死亡,只是少了一个绘制页面的工具而已,其他一切如故。通过控制权力,换取天下太平,这招在coding界,同样是一个不错的策略,但是,唯一的意外来自于Plugin。按照规范,Chrome的Plugin是可以创立窗口的(HWND),这必然导致同步问题,Chrome没有办法通过控制权力的方式解决这个问题,只能想些别的亡羊补牢的招来搞定。。。

分享到:
评论

相关推荐

    CEF:进程间通信 Demo(VS2013)

    在这个“CEF:进程间通信 Demo(VS2013)”中,我们将探讨如何在Visual Studio 2013环境下利用CEF实现跨进程通信。 首先,让我们了解什么是进程间通信(IPC,Inter-Process Communication)。在多线程或多进程的...

    C#多进程浏览器-仿Chrome浏览器

    为了实现进程间的通信,可能需要用到命名管道、内存映射文件或者.NET的Remoting技术。 此外,为了模拟Chrome浏览器的功能,还需要实现以下关键技术点: 1. **渲染引擎**:负责解析HTML、CSS和JavaScript,生成渲染...

    剥析chrome源码

    ### 剥析Chrome源码:深入理解Chrome的进程间通信机制 #### 一、引言 在现代浏览器设计中,Chrome以其独特的架构和技术脱颖而出,成为业界标杆。Chrome的源码不仅庞大而且复杂,其中涉及的技术细节对于软件开发...

    Chrome源码剖析

    在探索 Chrome 源码时,需要了解它的多进程模型、线程模型、进程间通信机制、插件模型、整体框架设计、跨平台 UI 控件系统、V8 引擎等技术要点。 Chrome 的多进程模型是它的一大特色,包括一个主进程(Browser ...

    google Chrome源码剖析

    这种设计也带来了进程间通信(IPC)的挑战。Chrome 使用 IPC 实现进程间的协作,如数据传输和命令控制。同时,多进程架构增加了内存开销,但可以通过智能的进程管理和资源调度来优化性能。 2. **扩展能力和插件模型...

    chrome核心部分源码

    在源码中,你可以找到关于进程间通信(IPC)和沙盒环境的实现。 5. **网络堆栈**:Chrome的网络堆栈负责处理HTTP/HTTPS请求和响应,缓存机制,以及数据传输优化。这部分源码涉及到Quic协议(Google的快速UDP互联网...

    chrome源码学习文档

    学习源码可以了解如何实现进程间通信(IPC)和渲染进程与主进程之间的协作。 2. **Blink渲染引擎**:Blink负责解析HTML、CSS,布局页面并绘制到屏幕上。深入Blink源码有助于理解网页渲染流程,包括解析规则、布局...

    chrome 源代码

    源代码展示了如何有效地在不同进程间通信,如使用IPC(Inter-Process Communication)机制。 JavaScript引擎V8也是Chrome源代码中的重要部分,它负责解释和编译JavaScript代码,提供了高效的执行环境。V8的源码可以...

    chrome.tar

    此外,还有各种库和框架,如WebKit(以前的渲染引擎)、 Mojo(进程间通信机制)和PPAPI(插件接口)等。 为了运行和测试你的修改,你可能需要设置一个本地开发服务器,安装必要的测试框架,并学习如何使用Chrome的...

    chrome实现窗口的源码

    - **切换**:用户在多个标签间切换,源码中应有处理这种事件的逻辑,切换时会激活相应的渲染进程。 - **拖放**:Chrome允许用户通过拖放来重新排列标签,这需要处理鼠标事件和UI更新。 - **多标签策略**:Chrome...

    python selenium chrome 多开 多线程

    3. **Chromedriver**: Chromedriver是Chrome浏览器的驱动程序,它实现了WebDriver协议,使Selenium能与Chrome通信。为了运行多个浏览器实例,我们需要多个独立的Chromedriver实例。 4. **PhantomJS**: PhantomJS是...

    01-Chrome架构:仅仅打开了1个页面,为什么有4个进程?_For_vip_user_0011

    同时,通过进程间通信(IPC)机制,它们协同工作,提供高效的网络请求、页面渲染和JavaScript执行。 对于开发者而言,理解Chrome的多进程架构有助于优化Web应用性能,排查问题,以及实现更安全的编程实践。例如,...

    chrome app

    4. APIs:Chrome App提供了许多原生API,如Chrome Storage API用于持久化数据存储,Chrome Messaging API用于应用间的通信,以及System API获取设备信息等。这些API让Chrome App能更好地利用硬件资源和系统功能。 5...

    nodejs 内部资料 进程管理

    本篇内部资料主要围绕Node.js的进程管理,特别是Master-Worker模式和进程间通信(IPC)进行详细讲解。 首先,进程管理服务模型经历了多个阶段。从同步到复制进程,再到多线程,直至现在的事件驱动多进程架构。Node....

    Google Chrome 浏览器架构解析及相关特性分析

    #### 进程间通信 - **通信方式**: - 使用 named pipes/socket pair 作为 IPC (Inter-Process Communication) 通信通道。 - 主要采用异步通信方式,但也会在必要时使用少量的同步通信。 - 一些数据通过 Shared ...

Global site tag (gtag.js) - Google Analytics