Legacy Chrome IPC
再次强调这种机制是被废弃的,但是因为 Chromium 非常核心的逻辑依然遗留有大量对它的使用,所以大家目前还是要理解这种用法。下图这张经典的图可以比较好的说明 Chromium 中是如何使用 Legacy IPC 的:
我们可以看到以下和Legacy IPC相关的信息:
- Browser进程有2个Channel,说明一个进程中可以有多个Channel;
- 每个Render进程都有一条Legacy IPC和Browser连接;
- ResourceDispatcher 通过Filter和Channel连接起来;
这里先介绍几个Legacy IPC的关键术语:
- IPC::Channel: 一条数据传输通道,提供了数据的发送和接收接口;
- IPC::Message: 在Channel中传输的数据,主要通过宏来定义新的Message;
- IPC::Listener: 提供接收消息的回调,创建Channel的必须提供一个Listener;
- IPC::Sender: 提供发送IPC::Message的Send方法,IPC::Channel就实现了IPC::Sender接口;
- IPC::MessageFilter: 也就是Filter,用来对消息进行过滤,类似管道的机制,它所能过滤的消息必须由其他Filter或者Listener传给它;
- IPC::MessageRouter: 一个用来处理 Routed Message 的类(见下文);
Legacy IPC 的本质就是通过IPC::Channel接口发送IPC::Message,IPC::Channel是封装好的类,IPC::Message需要用户自己定义,所以接下来我们先来看一下如何定义 Message。
Messages
IPC::Message 有两类,一类是路由消息 “routed message”,一类是控制消息 “control message”,他们从消息内容上看唯一的不同就是 routing_id() 不同,每一个 IPC::Message 都会有一个 routing_id,控制消息的 routing_id 始终是 MSG_ROUTING_CONTROL,这是一个常量,除此之外,所有 routing_id 不是这个常量的消息都是路由消息。这两种消息在处理上也没有什么不同,唯一区别对待它们的地方就在 IPC::MessageRouter 类中,或者自己写代码进行区分。
定义一个消息的方法如下:
// demo_ipc_messages.h
#ifndef DEMO_IPC_MESSAGES_H_
#define DEMO_IPC_MESSAGES_H_
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_param_traits.h"
// 使用 IPCTestMsgStart 来测试,它不能随意命名,必须存在于 ipc/ipc_message_start.h 中
// 详情见 ipc/ipc_message_macros.h 文件头的解释
#define IPC_MESSAGE_START IPCTestMsgStart
// 定义2个控制消息,宏后面的1表示有1个参数
IPC_MESSAGE_CONTROL1(IPCTestMsg_Hello,std::string)
IPC_MESSAGE_CONTROL1(IPCTestMsg_Hi,std::string)
// 定义2个路由消息,宏后面的1表示有1个参数
IPC_MESSAGE_ROUTED1(IPCTestMsg_RoutedHello,std::string)
IPC_MESSAGE_ROUTED1(IPCTestMsg_RoutedHi,std::string)
#endif //DEMO_IPC_MESSAGES_H_
这里面有一个“魔数”IPCTestMsgStart,这个是不能随便命名的,可以认为它是用来给消息分类的,消息必须属于某一个类,所有的类别可以在 ipc/ipc_message_start.h 中查看。
这样定义了之后还不能使用,还要按照添加所谓的 message generator,具体内容见 demo_ipc_message_generator.h 和 demo_ipc_message_generator.cc。
这样之后 Message 就算定义好了,就可以进行发送了。要发送 Message,需要创建 IPC::Channel,但是在创建 IPC::Channel 的时候需要传入一个 IPC::Listener 用来接收 IPC::Channel 受到的 Message,所以我们先来实现一个 IPC::Listener。
IPC::Listener
IPC::Listener 接口非常简单,最主要的是重写 OnMessageReceived 方法,这个方法会被 IPC::Channel 调用,在这个方法内部使用 IPC_BEGIN_MESSAGE_MAP 宏来进行消息的分发,这个宏可以处理控制消息也可以处理路由消息。当 IPC::Channel 收到 IPCTestMsg_Hi 消息的时候就会触发 OnHi 方法。
class ProducerListener : public IPC::Listener {
private:
void OnChannelConnected(int32_t peer_pid) override {
LOG(INFO) << "Producer OnChannelConnected: peer_pid= "<<peer_pid;
}
bool OnMessageReceived(const IPC::Message& message) override {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ProducerListener, message)
IPC_MESSAGE_HANDLER(IPCTestMsg_Hi,OnHi);
IPC_MESSAGE_UNHANDLED(handled = false);
IPC_END_MESSAGE_MAP()
LOG(INFO) << "Producer OnMessageReceived: handled= " << handled;
return handled;
}
void OnHi(const std::string& who) {
LOG(INFO) << "ProducerListener run: Hi " << who;
}
};
IPC::Channel
前面有提到过,当前的 Legacy IPC 的底层是使用 Mojo(的 MessagePipe)实现的,因此要在多进程下使用 Legacy IPC,肯定也要涉及 MessagePipe 的创建,以及进程间 MessagePipe 的传递,所以我们基于 demo_mojo_multiple_process 来实现 Legacy IPC 的 demo。
创建 IPC::Channel 的方法如下:
// 假设我们已经通过某种方法获得了一个MessagePipe的句柄
mojo::ScopedMessagePipeHandle pipe = ...;
ProducerListener listener;
// 创建一个Server端的Channel,在底层使用Mojo的情况下Server和Client是等价的
std::unique_ptr<IPC::Channel> ipc_channel =
IPC::Channel::CreateServer(pipe.release(),&listener,xxx.task_runner());
LOG(INFO) << "Producer Connect";
bool result = ipc_channel->Connect();
if(result) {
// 发送消息
ipc_channel->Send(new IPCTestMsg_Hello("Producer"));
ipc_channel->Send(new IPCTestMsg_RoutedHello(1,"Producer"));
}
在接收端可以使用同样的方法创建一个 IPC::Channel。这样两端就可以通过 IPC::Channel 接口进行 Message 的收发了。
可以看到,这里完全可以不使用任何的 IPC::MessageFilter 或者 IPC::MessageRouter,这些都不是使用 Legacy IPC 所必须的,如果你的 Listener 需要提供 Message 分发的能力,你可以在自己的 Listener 中使用这些类。在 demo 中有演示他们的使用方法,详见 demo_ipc.cc。
IPC::Channel 的接口并不复杂,之所以在 Chromium 中显的比较复杂,是因为 Chromium 对它进行了太多层的包装,这些都是业务的复杂性,并不是 Legacy IPC 接口的复杂。
参考:
https://keyou.github.io/blog/2020/01/03/Chromium-Mojo&IPC/
https://github.com/keyou/chromium_demo
← 上一节