基于.net开发chrome核心浏览器3

基于.net开发chrome核心浏览器【三】

本篇我们讲解怎么用CefGlue开发一个最简单的浏览器

一:

CefGlue是建立在Cef项目之上的,
Cef项目是C/C++的项目;
CefGlue只不过是通过PInvoke来访问Cef项目生成的一些dll
下面我们来看看Cef项目生成的一些dll和资源都是做什么用的
打开这个目录\cef_binary_3.1453.1236_windows_xilium\Release
libcef.dll-------------------------->Cef的核心类库
icudt.dll-------------------------->支持unicode的类库
ffmpegsumo.dll------------------>支持音频和视频的类库
d3dcompiler_43.dll--------------->WinXP下支持3D的类库
d3dcompiler_46.dll--------------->Win7和之后的Win支持3D的类库
libEGL.dll------------------------->用于支持3D
libGLESv2.dll--------------------->用于支持3D

打开目录:\cef_binary_3.1453.1236_windows_xilium\Resources
locales--------------------------->此文件夹存放了各种国家的语言资源
cef.pak-------------------------->为WebKit相关的资源(谷歌浏览器的核心是webkit)
devtools_resources.pak--------->调试器的相关资源(我们做的项目是可以使用谷歌浏览器的调试器的)

二:

建立一个winform工程,取名加CefDemo
在程序集中创建一个文件夹取名dll
在程序集的属性里设置此程序集的预先生成事件的命令

xcopy $(ProjectDir)dll $(TargetDir) /e /i /y

这个命令的目的是:每次编译的时候把dll文件夹中的文件拷贝的输出目录中

把\cef_binary_3.1453.1236_windows_xilium\Release此目录下的所有文件都拷贝到CefDemo的dll目录中去
把\cef_binary_3.1453.1236_windows_xilium\Resources此目录下的所有文件和文件夹拷贝到dll目录中去
注意:locales子目录下的文件大部分都没有用,你可以把所有的文件都删掉,只留下zh-CN.pak文件。
打开Xilium.CefGlue工程,release编译CefGlue程序集,把生成的Xilium.CefGlue.dll也拷贝到CefDemo的dll目录中去
在CefDemo项目中添加Xilium.CefGlue.dll的引用

三:

修改Program.cs的代码:

复制代码
        static void Main()
        {
            CefRuntime.Load();
            var mainArgs = new CefMainArgs(new string[] { });
            var exitCode = CefRuntime.ExecuteProcess(mainArgs, null);
            if (exitCode != -1)
                return;
            var settings = new CefSettings
            {
                SingleProcess = false,
                MultiThreadedMessageLoop = true,
                LogSeverity = CefLogSeverity.Disable,
                Locale = "zh-CN"
            };
            CefRuntime.Initialize(mainArgs, settings, null);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            if (!settings.MultiThreadedMessageLoop)
            {
                Application.Idle += (sender, e) => { CefRuntime.DoMessageLoopWork(); };
            }
            Application.Run(new CefBrowser());
            CefRuntime.Shutdown();
        }
复制代码

我们来一点一点解释这些代码:

CefRuntime.Load();
此行代码用于加载CEF的运行时
————————————————————————
var mainArgs = new CefMainArgs(new string[] { });
此行代码可以收集命令行参数,用于传递给CEF浏览器

————————————————————————
var exitCode = CefRuntime.ExecuteProcess(mainArgs, null);
if (exitCode != -1)
return;
以上代码用于启动第二个进程,至于用第二个进程做什么,我没有深入研究过(可以是浏览器的第二个进程、也可以是一个可执行文件的)
注意:CefRuntime.ExecuteProcess方法必须在程序的入口处调用;
——————————————————————————
var settings = new CefSettings
{
SingleProcess = false,
MultiThreadedMessageLoop = true,
LogSeverity = CefLogSeverity.Disable,
Locale = "zh-CN"
};
CEF的配置参数,有很多参数,我们这里挑几个解释一下:
SingleProcess = false:此处目的是使用多进程。
注意:强烈不建议使用单进程,单进程不稳定,而且Chromium内核不支持
MultiThreadedMessageLoop = true:此处的目的是让浏览器的消息循环在一个单独的线程中执行
注意:强烈建议设置成true,要不然你得在你的程序中自己处理消息循环;自己调用CefDoMessageLoopWork()
Locale = "zh-CN":webkit用到的语言资源,如果不设置,默认将为en-US
注意:可执行文件所在的目录一定要有locals目录,而且这个目录下要有相应的资源文件
——————————————————————————————
CefRuntime.Initialize(mainArgs, settings, null);
这句代码把创建的配置信息和命令行信息传递个cef的运行时
此函数必须在应用程序的主线程中调用
——————————————————————————————
if (!settings.MultiThreadedMessageLoop)
{
Application.Idle += (sender, e) => { CefRuntime.DoMessageLoopWork(); };
}
如果你在前面设置的MultiThreadedMessageLoop为false,
那么你可以加入如上代码,自行调用CefRuntime.DoMessageLoopWork();
——————————————————————————————
CefRuntime.Shutdown();
主进程结束时,要释放CEF的资源,并结束浏览器的进程。

四:

在工程中创建一个窗体,
在设计视图中,把窗口调整到合适的大小
(你想让浏览器变成多大,就调整到多大)
然后我们调整一下这个窗体的一些属性

            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "CefBrowser";
            this.Text = "最简单的实现";

在这篇文章提供的例子,还没有实现浏览器随着容器窗体的大小变化而变化
所以:我们在这里禁用了窗口的最大化功能,也禁用了拖动改变窗口大小的功能。

五:

在窗口的构造函数中加入如下代码:

var cwi = CefWindowInfo.Create();
cwi.SetAsChild(this.Handle, new CefRectangle(0, 0, this.Width, this.Height));
var bc = new BrowserClient();
var bs = new CefBrowserSettings() { };
CefBrowserHost.CreateBrowser(cwi,bc, bs,"http://www.cnblogs.com/liulun");

然后运行程序,你就看到了一个浏览器,如下图:

虽然没有滚动条,窗口也不能拖动改变大小
但是当你把鼠标移动到网页上之后,滚动鼠标滚轮,网页还是会跟着滚动的。

六:

下面我们来详细解释一下上面几句代码的意义

CefWindowInfo是CEF浏览器窗口实现的类,其中包含了在windows、linux、MAC下的具体实现
此类中的Create静态方法负责创建这个类的实例,
我在windows下执行这一句,将得到windows下CEF浏览器的实现方式
------------------
cwi.SetAsChild(this.Handle, new CefRectangle(0, 0, this.Width, this.Height));
此行代码负责把创建的CEF浏览器窗口与我们创建的winform窗口结合起来
this.Handle就是我们创建的winform窗口的句柄
SetAsChild函数使CEF浏览器窗口作为winform窗口的子窗口呈现
CefRectangle标志着CEF浏览器窗口将出现在父窗口中的位置和大小
-------------------
var bc = new BrowserClient();
BrowserClient是我在工程中新建的一个类
这个类没有任何逻辑和属性,只是继承了CefClient类
CefClient类有很多虚方法以供重写,
比如GetDisplayHandler、GetDownloadHandler、GetJSDialogHandler等等
注意:此类很重要,我们将在接下来的章节中为这个类添加很多内容
---------------------
var bs = new CefBrowserSettings() { };
之前我们在Program中设置的是CefSettings
那是针对CEF环境的一些全局设置
这里是CefBrowserSettings
这是针对CEF浏览器环境的一些全局设置
可以在这里配置的参数有很多
比如:
DefaultEncoding(用于所有网页内容的编码方式,默认为ISO-8859-1)
UserStyleSheetLocation(用于所有网页的样式,应该按照这样的格式设置这个字段:data:text/css;charset=utf-8;base64,[csscontent])
RemoteFonts(用于所有网页的字体)
JavaScript(用于所有网页是否可以执行JS脚本)
JavaScriptOpenWindows(用于所有网页是否可以通过JS来打开窗口)
(还有很多类似的设置,读者可以自己去研究)
----------------------------
CefBrowserHost.CreateBrowser(cwi,bc, bs,"http://www.cnblogs.com/liulun");
代码执行到这一行即开始创建浏览器子窗口
CreateBrowser前面三个参数不用多说了
最后一个参数就是你想让浏览器访问的页面
注意:这个方法是异步执行的(非阻塞的),也就是说你无法知道什么时候窗口被创建出来,(通过其他方式可以注册窗口创建成功的事件,以后再讲。)

源码下载:

https://files.cnblogs.com/liulun/CefDemo.zip
注意:为了下载方便,我已经去掉了dll文件夹中的资源和需要引用的类库

修改记录:
2013-4-22:创建文章,并完成了一部分内容
2013-4-29:添加了文章的一部分内容,碰到问题停滞不前。
2013-5-02:解决掉问题,更新并添加了大部分内容,修改了文章的排版
2013-5-11:增加了最后一部分内容,修改了排版,通读文章,纠正错别字

基于任务本身的并行处理模型设计

 

知 鸣  

文章为笔者一家之言,由于笔者刚入职场学识有限,观点可能有些偏颇,望博客园各位前辈指教,在此衷心感谢!


摘要:面临大数据量的任务处理时,串行任务的处理方式就会拉长任务的处理时间,而采用多线程并发处理或者设计多核的并行程序工程实施比较复杂,维护成本较大。本文着重探讨几种并发处理模型,这些模型使得并行任务的处理简单的变成了多个串行任务的同时处理。 


 1.串行任务处理

定义1:如果一系列的任务由单台处理机按照先后或者批次的顺序处理,我们把这种任务处理的方式称为串行任务处理

如图:

1.1串行任务处理的优点

(1)任务处理机部署方便,软件开发及工程实施维护成本较低

(2)比较直观,运行稳定

1.2缺点 

(1)当任务数增加时,整体处理的速度变慢,很难适应快速的任务量增长。

例如在电商订单生产处理,M万条订单数据如果采用单台处理机需要T时间处理完毕,如果采用N台处理机并行处理,理论上需要T/N的时间就可以处理完毕,这样顾客在很短的时间内就可以收货而获得好的顾客体验,如果采用串行处理方式,随着业务订单量的增长处理时间就会拉长。 

(2)同一个任务队列很难同时分配给多个处理机处理

串行任务处理时常在任务中引入一个标示状态,标识该任务是否已经处理,如果同时启用多台处理机处理很有可能造成任务处理混乱,如下图

处理机1首先获取标识"是否处理"为"否"(未处理)的任务2,然后"处理任务2",接着回写任务2标识为"是",但是由于处理机2和处理机1节拍并不相同,当任务2被处理机1处理而未来得及回写任务处理标识时,任务处理机2也取得任务2的处理权,这样任务2就被处理了2次。 


2并行任务处理

通过上面的简单分析,对于大数据量的任务处理引入并行任务处理方案显得十分必要。

定义2:如果一系列的任务能够并行的无冲突的分配给若干任务处理机处理,我们把这种任务的处理方式成为并行任务处理。

并行任务的处理关键在过各个处理机处理的任务处理互相独立,也就是构建一个无冲突的任务环境。最简单的无冲突模型,就是能够明确标识某任务分给了具体的某个任务处理机,该处理机独享该任务的处理权限。

我们需要谈谈常见的几种并行方式,本质上这些方式只是针对单台处理机而言并不是本文要讲的多台并行(或者分布)处理环境。

2.1基于多线程的并发任务设计

首选想到的是基于多线程的并发任务设计,把不同的任务分配给操作系统某个进程的多个线程去处理,这样,各个线程只负责处理已分配的独享权限的任务,从而实现在单台处理机上的任务并发。

然而多线程的这种并发只是一种用户态的并发,也就是说这种并发只是让用户看起来是并发的。因为多个线程共享一个进程的cpu处理资源,多个线程通过轮流切换暂用cpu空闲时间片的从而获得类似于并行的处理。实际上对一个进程而言在cpu上仍然是串行处理的。如果每个线程处理的任务时本身耗费的cpu资源是巨大的,那么导致cpu空闲时间片减少,很可能处理的效率尚不如一个串行处理机处理所有的任务,因为线程的轮流切换本身就是一个很耗费资源的操作。

2.2基于cpu多核的并行任务设计

构建并行程序是cpu多核时代的福音,他能够充分利用多核cpu的的每一个核去构建并行程序,而非像多线程那样去共享一个cpu核的进程资源,这种并行处理是高效的,然而基于这种方案的并行设计很可能比较复杂,工程实施和维护的代价也比较高。

2.3基于并行任务本身的并发设计

这是一个更高层面的并发设计,他脱离了线程和进程层面,他把某个具体的任务和具体的处理机提前建立一个对应的map关系,任务处理机仅仅负责处理和他建立对应关系的任务,而对单个处理机而已仅仅是一个串行的任务处理机,这样整个并发模型的构建具有很强的灵活性和稳定性,尤其适应企业分布的环境的任务处理,更为直观的表示如下。

如上图,任务分发程序负责建立具体某个任务和处理机的map对应关系,既任务分发程序实现任务的无冲突分配。实际上我们可以大胆假设,如果任务分发程序的分发速度比N太处理机的处理速度慢时,它本身就会变成一个任务处理速度的障碍。 


3构建基于任务的并发任务处理模型

3.1均等任务分配模型

定义3:如果一批任务不考虑任务处理机已有的配置环境和实时的处理环境,把任务均等的分配给多台并行的任务处理机中的每一台,既对任务数为M处理机数为N的场景,每台处理机获得M/N的任务处理任务量。

显然这种处理模型对于机器配置相同,负载相同的处理机分配和处理任务十分有效。顺便我们讨论几种任务均等的分配的划分方法,读者可以考虑其优劣。

(1)数字均等划分方法

这种划分仅仅是用了数学计算,通过以下举例可能理解更为透彻

例如在10000条订单任务处理的场景下:

我们可以按照订单单号奇偶数分给两台不同的机器处理;

或者指定尾号为12,34,56,78,90的分别分给编号为1,2,3,4,5的机器去处理;

或者我们把订单尾号为0,1,2,…,9的订单分别分配给10台编号以此为0,1,2,…,10不同的处理机去处理,那么只有9台处理机又该怎么分配呢?

也许更为通用的方法是这样

订单号对机器数求模,设订单号为M,机器数为N,那么M%N就表示该任务分配在第M%N台机器上处理(如订单号3456777,处理机为6是,3456777%6=3则分配给编号为3的机器处理),这样能够很快的适应机器数N变化场景,既当机器数增加或减少时调整N的大小即可。

(2)区间端划分法

这种方法让每台机器处理一个等长的任务区间段,假设订单号连续增长,我们需要用10台机器处理订单号为500----600之间的100条订单任务,那么第x台处理机应该分配的任务区间段为

Mx= [500+x*[(600-500)/10],500+x*[(600-500)/10]+(600-500)/10) 

(3)随机数概率均等划分法

如果每个任务本身就有一个标识表明该任务由那台机器处理,我们可以理解为这个标识就是该任务的一个属性,设处理机数为N,每个任务生成的时随机生成一个[0,N)区间内的随机数n=Random(0,N)作为该任务的标识,任务处理时,该任务直接由编号为n的机器处理即可。

按照概率均等的原则,对于数量为M的任务每个处理机理论上均等的的分得M/N个任务数,这种方式有的优点在于,任务的分配不需要通过一个分发程序去计算每个机器应该分配那些任务,而是任务本身生成时随机标识数就标明了他该有那台机器去处理,而这种标识的生成天生就是概率均等的。

如图

3.2构建自适应分配模型

以上所有讨论的情况是站在均等分配的角度,我们把任务按照均等的原则分配给各个处理机,实际上这是一种理想的境界,如果不是特别计较负载均衡,上述方案已经足够应付很多的并行任务处理场景,但是现实的生产环境中,不同处理机的配置,网络环境存在优劣,实时处理环境不断变化,大马拉小车,小马拉大车出现频率就会增大,此时采用自适应分配就显得必要了。

定义4:所谓的自适应分配模型就是希望任务在分配的时能够根据不同机器目前所处环境,(如负载能力等)给予分配合适的任务量,而非每台机器分配均等的任务量。

实际上如果总任务数M远远小于N台处理机的吞吐量 ,基于自适应分配模型还是采用均等分配的模型并没有多少在处理速度上的差别,但是当任务数M接近或者超过N台机器的吞吐量时自适应分配模型的优势就体现出来了。

构建自适应的分配模型的好处一言以蔽之----"能者多劳,适量分配,求总体高效"。

自适应任务分配的理念在于按照任务处理机的"实时负载能力为其分配合适的任务量",实际上讨论了2个方面的核心问题:

第一个问题:如果做到实时衡量(采集)机器的负载情况;

第二个问题:如果合适的分配任务,我们将会讨论几种自适应分配模型;

3.2.1如何做到实时

我们很难预测把一批量任务分配给某台机器后,该机器的负荷情况变化。换句话来说某台机器未来的时间的负荷情况变化因素很多很难实时性的准确性的预测。另一方面我们却很容易统计某台机器在过去的很短一段时间内的运行性能和负荷情况。

如果我们用该机器在过去很短一段时间内的运行情况去衡量它在下一个很短时间内运行情况,问题就变得可行了。我们完全有理由相信机器在上一个极短的时间内运行情况和他在下一个极短的时间内的运行情况具有很强的相似性。(姑且称之为"短时间相似性")

那么如何去理解"过去很短的一段内"这句话,不妨揣摩以下下几个排比句:

(1)该机器在过去一年内运行良好,我们可以断定在下一年运行良好

(2)该机器在过去一个月内运行良好,我们可以断定在下一个月运行良好

(3)该机器在过去一天内运行良好,我们可以断定在下一天运行良好

(4)该机器在过去一个小时内运行良好,我们可以断定在下一个小时运行良好

(5)该机器在过去十分钟内运行良好,我们可以断定在下一个十分钟运行良好

(6)该机器在过去一分钟运行良好,我们可以断定在下一个一分钟运行良好

  ……

希望这种越来越接近真相的排比句,让我们对"过去很短的一段内"的理解更为深刻。

3.2.2几种自适应分配模型的探讨

(1)数学计算自适应模型

设,机器i第k次分配任务量为Tik,那么,机器i极短时间内的上一次分配的任务量表示为Ti(k-1),

设,机器i,k-1次实际完成任务量为Wi(k-1),其中有Wi(k-1)<= Ti(k-1)

当机器i,第k-1次圆满完成任务时,有Ti(k-1)-Wi(k-1)=0,此时

第k次应该分得任务数应该增加,为

Tik=Ti(k-1)(1+u%),其中u%为当机器k-1次完任务时第k是分得任务数的增长量,该增长量可由人为计算指定。

当机器i,第k-1次未处理完分配的任务数时,有Ti(k-1)-Wi(k-1)<0此时

第k次应该分得任务数应该减少,为

Tik=Ti(k-1)(1-q%),其中q%=[Ti(k-1)-Wi(k-1)]/ Ti(k-1)

(2)随机数概率不均等模型

在均等分配模型中讨论中我们提出了随机数概率均等划分法把任务均等的分配给每个处理机。其原理在于随机数生成的概率均等原则,使得每个机器均等的获得同等数量的任务量,设想如果我们如果能够人为的改变这种概率均等原则,以达性能较好的机器获得更多的任务呢?先看一个超市做活动的案例:

客在超市购物,付款之后可以参与抽奖,抽奖时顾客旋转如上的一个圆盘,圆盘停止转动后指针指向的位置为顾客所获得的奖品;

通过观察我们发现,贵重的奖品占据的圆盘面积较少,相应抽到该奖的几率就会变小,而廉价的奖品占据圆盘面积较大,也就意味着这大多数人会抽到廉价的奖品。

当圆盘转动停止时,指针指向圆盘任何一个区域的概率是相等的,但是我们通过设置区间段大小的方式,让不同的商品获得了不均等概率。

对于任务处理机而言我们也能够根据其在过去极短一个时间段内的执行情况构建一个类似的圆盘模型,那么不同机器将会根据性能占据不同的比例,设定处理速度快的机器获得更多的区间长度,在生成随机数标识时,占用区间段长的机器的就会自然分得更多的任务数量,从而达到自适应分配的结果。

(3)污水处理模型

假设有n台污水净化处理机(T1,T2,...,Tn),每个净化机通过水管和对应污水桶(S1,S2,...,Sn)相连接,每个水桶容量为C,每个水桶的污水由一个大水杯G不断的注入。

污水净化处理时,净化处理机Ti不断的通过水管从对应的污水桶Si里面获取污水,由于每个污水净化处理机Ti的处理效率不一样,在同一个时间范围内其对应的污水桶Si的污水剩余高度Hi就会有所差异(如图),而大水杯G会不停的向各个污水桶Si注水,注满为止,既每次注入C-Hi容量的水使得水桶Si注满。

于是,

  1. 对于处理较快的污水净化处理机Ti,它的污水桶污水剩余高度Hi就会大幅度降低,这样水杯G向其注满水所注入的污水容量C-Hi就会增大,也就是说处理能力强的处理机将会自然的根据其处理能力分配更多的任务。
  2. 反之对于处理速度较慢的污水净化机,器注入污水量C-Hi就会减小;
  3. 极端情况,某个污水处理机Ti down掉,那么其对应的污水桶Si里面的污水高度Hi就不会发生变化(此时Hi等于C),那么大水杯G应该向污水处理机注入的污水容量C-Hi就为0;

    显然,我们无法预测"污水处理机"未来时刻处理净化污水的能力和速度,但是我们通过衡量在过去一个短时间内污水桶Si剩余的污水容量Hi,为其注入C-Hi容量的污水。

    其实这仍然是一种根据"处理机在过去极短的一段时间内的运行情况去衡量机机在未来很短的一段时间内的负载能力"的一种应用。

(4)请求分发模型

在前面的第2.3节我们讲到任务的分发需要一个类似于"任务分发程序"的东西,它会根据各个机器的实际负载运行情况主动的有预测性的给各个处理机分发任务,这种想法和实施也许是直观的简单的易于理解的。

但是作为一个分发程序需要主动的去采集各个处理机的数据,衡量每一个机器的执行情况,然后几乎在同一个时间分配每个机器不均等的任务量,显然是比较吃力的,对于处理机而言这种处理方式也是被动的。

假设,每个处理机衡量自己的处理情况,然后在一定的设定条件下主动触发向分发处理程序请求一定量的处理任务,然后任务处理机接受到请求信号后响应一个的任务队列给处理机。(如上图,处理机在当前任务处理剩下15%时出发任务请求信号请求分发程序分发任务)我们把这种处理机主动发起任务请求,分发程序响应一个任务队列的方式,称之为请求分发模型。

显然,处理速度快的处理机向任务分发程序发送任务请求的频次就会增加,从而达到"能者多劳,适量分配,求总体高效"的目的。

反之,亦然;

极端情况,某台处理机down掉,就不会向任务分发程序发送请求任务的信号。

 
 
分类: WIN FORM(c#)
原文地址:https://www.cnblogs.com/Leo_wl/p/3074838.html