部分内容来源于本文:http://blog.csdn.net/qq295445028/article/details/7993051

一、消息格式

如果说,多线程模式下,对数据的访问开销来自于锁,那么在多进程模式下,大部分的额外开销都来自于进程间的消息拆装和传递。不论怎么样的模式,只要进程不同,消息的打包,序列化,反序列化,组包,都是不可避免的工作。

在Chrome中,IPC之间的通信消息,都是派生自IPC::Message类的。对于消息而言,序列化和反序列化是必须要支持的,Message的基类Pickle,就是干这个活的。Pickle提供了一组的接口,可以接受int,char,等等各种数据的输入,但是在Pickle内部,所有的一切都没有区别,都转化成了一坨二进制流这个二进制流是32位齐位的,比如你只传了一个bool,也是最少占32位的,同时,Pickle的流是有自增逻辑的(就是说它会先开一个Buffer,如果满了的话,会加倍这个Buffer...),使其可以无限扩展。Pickle本身不维护任何二进制流逻辑上的信息,这个任务交到了上级处理(后面会有说到...),但Pickle会为二进制流添加一个头信息,这个里面会存放流的长度,Message在继承Pickle的时候,扩展了这个头的定义,完整的消息格式如下:

其中,黄色部分是包头,定长96个bit,绿色部分是包体,二进制流,由payload_size指明长度。从大小上看这个包是很精简的了,除了routing位在消息不为路由消息的时候会有所浪费。消息本身在有名管道中是按照二进制流进行传输的(有名管道可以传输两种类型的字符流,分别是二进制流和消息流...),因此由payload_size + 96bits,就可以确定是否收了一个完整的包。。。

从逻辑上来看,IPC消息分成两类,一类是路由消息(routed message),还有一类是控制消息(control message)。路由消息是私密的有目的地的,系统会依照路由信息将消息安全的传递到目的地,不容它人窥视;控制消息就是一个广播消息,谁想听等能够听得到。

control消息与具体View无关,它将被RenderProcess 或者RenderProcessHost处理,比如请求资源和修改剪切板是与具体view无关的,因此属于control消息。Routed消息的一个例子是 通知某个view以绘制区域。

二、消息声明

一个标准的IPC消息定义应该是类似于这样的:

classSomeMessage

: public IPC::Message

{

public:

enum { ID = ...; }

SomeMessage(SomeType & data)

: IPC::Message(MSG_ROUTING_CONTROL, ID, ToString(data))

{...}

...

};

大概意思是这样的,你需要从Message(或者其他子类)派生出一个子类,该子类有一个独一无二的ID值,该子类接受一个参数,你需要对这个参数进行序列化。两个麻烦的地方看的很清楚,如果生成独一无二的ID值?如何更方便的对任何参数可以自动的序列化?

在Chrome中,解决这两个问题的答案,就是宏 + 模板。Chrome为每个消息安排了一种ID规格,用一个16bits的值来表示,高4位标识一个Channel,低12位标识一个消息的子id,也就是说,最多可以有16种Channel存在不同的进程之间,每一种Channel上可以定义4k的消息。目前,Chrome已经用掉了8种Channel(如果A、B进程需要双向通信,在Chrome中,这是两种不同的Channel,需要定义不同的消息,也就是说,一种双向的进程通信关系,需要耗费两个Channel种类...),他们已经觉得,16bits的ID格式不够用了,在将来的某一天,估计就被扩展成了32bits的。书归正传,Chrome是这么来定义消息ID的,用一个枚举类,让它从高到低往下走,就像这样:

enumSomeChannel_MsgType

{

SomeChannelStart = 5 << 12,

SomeChannelPreStart = (5 << 12) - 1,

Msg1,

Msg2,

...

MsgN,

SomeChannelEnd

};

这是一个类型为5的Channel的消息ID声明,由于指明了最开始的两个值,所以后续枚举的值会依次递减,如此,只要维护Channel类型的唯一性,就可以维护所有消息ID的唯一性了(当然,前提是不能超过消息上限...)。但是,定义一个ID还不够,你还需要定义一个使用该消息ID的Message子类。这个步骤不但繁琐,最重要的,是违反了DIY原则,为了添加一个消息,你需要在两个地方开工干活,是可忍孰不可忍,于是Google祭出了宏这颗原子弹,需要定义消息,格式如下:

IPC_BEGIN_MESSAGES(PluginProcess,3)

IPC_MESSAGE_CONTROL2(PluginProcessMsg_CreateChannel,

int /*process_id */,

HANDLE /*renderer handle */)

IPC_MESSAGE_CONTROL1(PluginProcessMsg_ShutdownResponse,

bool /*ok to shutdown */)

IPC_MESSAGE_CONTROL1(PluginProcessMsg_PluginMessage,

std::vector<uint8> /* opaque data */)

IPC_MESSAGE_CONTROL0(PluginProcessMsg_BrowserShutdown)

IPC_END_MESSAGES(PluginProcess)

这是Chrome中,定义PluginProcess消息的宏,我挖过来放在这了,如果你想添加一条消息,只需要添加一条类似与IPC_MESSAGE_CONTROL0东东即可,这说明它是一个控制消息,参数为0个。你基本上可以这样理解,IPC_BEGIN_MESSAGES就相当于完成了一个枚举开始的声明,然后中间的每一条,都会在枚举里面增加一个ID,并声明一个子类。

Render和Browser之间的消息都声明在render_messages.h有2个部分——View和ViewHost。

若要声明一个ViewHost、与view相关的(routed)消息,并包含URL和整型2个参数:

IPC_MESSAGE_ROUTED2(ViewHostMsg_MyMessage,GURL, int)

若要声明一个View、control消息:

IPC_MESSAGE_CONTROL0(ViewMsg_MyMessage)

通过宏和模板,你可以很简单的声明一个消息,这个消息可以传入各式各样的参数(这里用到了夸张的修辞手法,其实,只要是模板实现的自动化,永远都是有限制的,在Chrome的模板实现中,参数数量不要超过5个,类型需要是基本类型、STL容器等,在不BT的场合,应该够用了...)

有时候消息中包含太多值,我们需要定义一个专门的结构来封装这些值。例如ViewMsg_Navigate消息,render_messages.h文件中定义了ViewMsg_Navigate_Params结构,同时也定义了ParamTraits对该结构的特化版本

同步消息声明:

同步消息使用IPC_SYNC_MESSAGE_*宏来声明。这个宏含有输入和返回参数(非同步消息没有返回参数的概念)。如果控制函数有2个输入参数,1个返回参数,则需要在宏后面加2_1。:

IPC_SYNC_MESSAGE_CONTROL2_1(SomeMessage, // Message name
 GURL, //input_param1
 int, //input_param2
 std::string); //result

同样的,也有路由到view的消息用IPC_SYNC_MESSAGE_ROUTED2_1来得到。也可以没有输入和返回参数。没有返回参数主要用于render必须等待browser做某事,但不需要返回值。比如确定绘制和剪切板操作。

三、消息发送

RenderWidgetHostRenderViewHost基类)提供了send函数

消息通过指针发送,并在被分派之后由IPC层来删除。因此可以new一个消息对象来传给send函数

Send(new ViewMsg_StopFinding(routing_id_));

注意,必须指定routing_id_以便消息能够被正确的View/ViewHost接收。RenderWidgetHost类和RenderWidget类(RenderView基类)都有routing_id_可以使用。

关于routing_id_,若其值为MSG_ROUTING_NONE则该消息不会发送,或被接收端忽略。若其值为MSG_ROUTING_CONTROL,则发送的是CONTROL类型的消息,与具体View无关。

消息发送流程:


四、消息处理

通过实现IPC::Channel::Listener接口来处理消息,该接口中最重要的函数是OnMessageReceived。在这个函数中我们有很多宏来简化消息处理。示例如下:

MyClass::OnMessageReceived(const IPC::Message& message) {
 IPC_BEGIN_MESSAGE_MAP(MyClass, message)
 // Will call OnMyMessage with the message. The parameters of the message will be unpacked for you.
 IPC_MESSAGE_HANDLER(ViewHostMsg_MyMessage, OnMyMessage) 
 ...
 IPC_MESSAGE_UNHANDLED_ERROR() // This will throw an exception for unhandled messages.
 IPC_END_MESSAGE_MAP()
}

// This function will be called with the parameters extracted from the ViewHostMsg_MyMessage message.
MyClass::OnMyMessage(const GURL& url, int something) {
 ...
}

也可以通过使用IPC_DEFINE_MESSAGE_MAP宏来实现函数声明,在这种情况下,不指定一个消息变量名,它会声明一个OnMessageReceived的函数在给定的类,并实现其内容。

其他宏:

IPC_MESSAGE_FORWARD:类似于IPC_MESSAGE_HANDLER,但是可以指定您自己的类来将消息发送过去,而不是把它发送到当前类:

IPC_MESSAGE_FORWARD(ViewHostMsg_MyMessage, some_object_pointer, SomeObject::OnMyMessage)

IPC_MESSAGE_HANDLER_GENERIC:这使您可以编写自己的代码,但你自己必须从消息中获取参数。

IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_MyMessage,printf("Hello, world, I got the message."))


Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐