Mojo Mojo是一个跨平台的IPC框架,诞生于chromium,用来实现chromium进程内或进程间通信。目前也被用于Chromeos。
Mojo的分层
从图中看Mojo分为4层:
Mojo Core :Mojo的实现层,不能独立使用,由** C++ **实现;
Mojo System API(C) :Mojo的C API层,它和Mojo Core对接,可以在程序中独立使用;
Mojo System API(C++/Java/JS) : Mojo的各种语言包装层,它将Mojo C API包装成多种语言的库,让其他语言可以使用。这一层也可以在程序中独立使用。
Mojo Bindings : 这一层引入一种称为Mojom的idl(接口定义)语言,通过它可以定义通信接口,这些接口生成接口类,使用户只要实现这些接口就可以和Mojo进行通信,这一层使得IPC两端不需要通过原始字节流进行通信,而是通过接口进行通信,有些类似于Protobuf和Thrift。
除了上述提到的那些层之外,在Chromium中还有两个模块对Mojo进行了包装,分别是Services(//services)模块和IPC(//ipc)模块。
services :一种更高层次的IPC机制,构建于Mojo之上,以service的级别来进行IPC通信,Chromium大量使用这种IPC机制来包装各种服务,用来取代Leagcy Chrome IPC,比如device服务,preferences服务,audio服务,viz服务。
Leagcy Chrome IPC :已经不推荐使用的Chrome IPC机制,提供IPC::Channer接口以及大量的宏来定义messages类,目前底层也是基于Mojo来实现的,但是上层接口和旧的Chrome IPC保持一致。Chromium中还有很多IPC来使用这种方式,但是不应该在新的服务中使用这种机制。可以在ipc/ipc_message_start.h
中来查看还有哪些服务使用了这种机制。
Mojo在Chromium中的分层
在Chromium中,还有两个基础模块使用Mojo,分别是Services和IPC::Channel。
Mojo的设计 在使用Mojo之前,我们来看一下Mojo的设计,这对理解后面的使用至关重要。
Mojo支持在多个进程之间相互通信,这一点和其他的IPC有很大不同, 其他大多只支持两个进程之间进行通信。由Mojo组成的这些可以互相通信的进程就形成了一个网络,在这个网络内的任意两个进程都可以进行通信,并且每个进程只能处于一个mojo网络中,在这个网络内每一个进程内部有且只有一个Node,每一个Node都可以提供多个Port,每个Port都对应一种服务,这点类似于TCP/IP中的IP地址和端口的关系。一个Node:Port对可以唯一确定一个服务。Node和Node之间通过Channel来实现通信,在不同平台上channel有不同的实现方式,在Linux上是domain socket,在windows上是name pipe,在MAC os上是Mach Port。在Port上一层,Mojo封装了三个“应用层协议”,分别为MessagePipe,DataPipe和SharedBuffer(类似于在TCP上封装了HTTP,SMTP)。整体结构如下图
上图展示了在两个进程之间使用Mojo的数据流。它有以下几个特点:
Channel : Mojo内部的实现细节,对外不可见,用于包装系统底层的通信通道,在Linux下是domain socket,Windows下是name pipe,在Mac os下是mach port。
Node : 每个进程都只有一个Node,它在Mojo中的作用相当于TCP/IP中的IP地址,同样是内部实现细节,对外不可见。
Port : 每个进程可以有成千上百个Port,它在Mojo中的作用相当于TCP/IP的端口,每个Port都会对应一种应用层接口,目前Mojo支持三种应用层接口。
MessagePipe : 应用层接口,用于进程间的双向通信,类似于UDP,消息是基于数据包的,底层使用Channel通道。
DataPipe : 应用层接口,用于进程间单向块数据传递,类似TCP,消息是基于数据流的,底层使用系统的Shared Memory实现
SharedBuffer : 应用层接口,支持双向块数据传递,底层使用系统Shared Memory实现;
MojoHandle : 所有的 MessagePipe,DataPipe,SharedBuffer 都使用MojoHandle来包装,有了这个Handle就可以对它们进行读写操作。还可以通过MessagePipe将MojoHandle发送到网络中的任意进程。
PlatformHandle : 用来包装系统的句柄或文件描述符,可以将它转换为MojoHandle然后发送到网络中的任意进程。
MessagePipe 一个进程中可以有N多个MessagePipe,所有的都共享底层的一条通信通道,就像下图这样
Mojo保证同一个MessagePipe中的数据发送和接收顺序一致,但是不保证多个MessagePipe之间的数据的有序
Mojo的应用 Mojo不仅可以在Chromium中使用,也可以在任何第三方程序中使用,因为其本身不依赖于Chromium中的业务逻辑部分。不过由于其源码在Chromium中,使用起来可能不是那么方便。
Mojo提供了不同层次的API,外部可以根据自己的需要选择使用的层次,下面我们简单介绍每种API的使用方法
初始化Mojo 初始化Mojo有两种方式,一种适用于静态链接的Mojo程序,一种适用于动态链接Mojo的程序。一下是静态链接时的初始化方法,动态链接时只需要把mojo:::core::Init()替换为MojoInitialize()即可
初始化接口头文件为
1 2 #include <mojo/core/embedder/embedder.h> #include <mojo/core/embedder/scoped_ipc_support.h>
初始化方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main (int argc,char ** argv) { base::commandLine::Init(argc, argv); mojo::core::Init(); base::Thread ipc_thread ("ipc!" ) ; ipc_thread.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0 )); mojo::core::ScopedIPCSupport ipc_support ( ipc_thread.task_runner(), mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN) ; .. }
Mojo c API Mojo C API都比较简单,主要的头文件位于:
1 2 3 4 5 6 #include "mojo/public/c/system/buffer.h" #include "mojo/public/c/system/data_pipe.h" #include "mojo/public/c/system/message_pipe.h"
下面是在单进程中使用MessagePipe发送和接收数据的方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 MojoHandle sender_handle, receiver_handle; MojoResult result = MojoCreateMessagePipe(NULL , &sender_handle, &receiver_handle); DCHEK_EQ(result, MOJO_RESULT_OK); { MojoMessageHandle message; result = MojoCreateMessage(nullptr, &message); DCHEK_EQ(result, MOJO_RESULT_OK); MojoAppendMessageDataOptions options; options.struct_size = sizeof (options); options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE; void * buffer; uint32_t buffer_size; result = MojoAppendMessageData(message, 6 , nullptr, 0 , &options, &buffer, &buffer_size); DCHECK_EQ(result, MOJO_RESULT_OK); memcpy (buffer, "hello" , 6 ); LOG(INFO) << "send: " << (const char *)buffer; result = MojoWriteMessage(sender_handle, message, nullptr); DCHECK_EQ(result, MOJO_RESULT_OK); } { MojoMessageHandle message; MojoResult result = MojoReadMessage(receiver_handle, nullptr, &message); DCHECK_EQ(result, MOJO_RESULT_OK); void * buffer = NULL ; uint32_t num_bytes; result = MojoGetMessageData(message, nullptr, &buffer, &num_bytes, nullptr, nullptr); LOG(INFO) << "receive: " << (const char *)buffer; }
其他关于DataPipe和SharedBuffer的使用方法都类似。由于实际项目很少直接使用C API,所以使用方法在这里省略。
Mojo C++ API 单进程 以下是在单进程中使用MessagePipe的方法,其中最重要的是要注意mojo::MessagePipe pipe;这一行(内部调用Moojo的C APIMojoCreateMessagePipe),它创建了一个MessagePipe , 本质上只是创建了一对随机数,对应pipe中的两个属性handle0和handle1,这两个功能上没有任何区别,向其中的一个handle写的数据可以从另外一个handle中读取出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include "mojo/public/cpp/system/buffer.h" #include "mojo/public/cpp/system/data_pipe.h" #include "mojo/public/cpp/system/message_pipe.h" #include "mojo/public/cpp/system/simple_watcher.h" #include "mojo/public/cpp/system/wait.h" mojo::MessagePipe pipe; { const char kMessage[] = "Hello" ; result = mojo::WriteMessageRaw (pipe.handle0.get (), kMessage, sizeof (kMessage), nullptr , 0 , MOJO_WRITE_MESSAGE_FLAG_NONE); DCHECK_EQ (result, MOJO_RESULT_OK); LOG (INFO) << "send: " <<kMessage; } { std::vector<uint8_t > data; result = mojo::ReadMessageRaw (pipe.hanle1.get (), &data, nullptr , MOJO_READ_MESSAGE_FLAG_NONE); DCHECK_EQ (result, MOJO_RESULT_OK); LOG (INFO) << "receive msg: " <<(char *)&data[0 ]; }
关于DataPipe和SharedBuffer的使用这里不再赘述。
多进程 一个**MessagePipe中有一对handle,分别对handle0和handle1,向其中一个handle写的数据可以从另外一个handle中读出来,这是前面已经说过的,如果把其中一个handle发送到另外一个进程,这一对handle之间依然可以相互收发数据。Mojo提供了多种方法来发送handle到其他的进程,最简单的则是使用Invitation
要在多个进程之间使用MOJO,必须先通过Invitation将这些进程“连接”起来,这需要一个进程发送Invitation,另外一个进程接收Invitation,发送Invitation的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 mojo::PlatformChannel channel; LOG (INFO) << "local: " << channel.local_endpoint ().platform_handle ().GetFD ().get () << "remote: " << channel.remote_endpoint ().platform_handle ().GetFD ().get (): mojo::OutgoingInvitation invitation; mojo::ScopedMessagePipeHandle pipe = invitation.AttachMessagePipe ("my raw pipe" ); LOG (INFO) << "pipe: " << pipe->value ();base::LaunchOptions options; base::CommandLine command_line ( base::CommandLine::ForCurrentProcess()->GetProgram()) ;channel.PreareToPassRemoteEndPoint (&options, &command_line); base::Process child_process = base::LaunchProcess (command_line, options); channel.RemoteProcessLaunchAttempted (); mojo::OutgoingInvitation::Send ( std::move (invitation), child_process.Handle (), channel.TakeLocalEndpoint (), base::BindRepeating ( [](const std::string& error) {LOG (ERROR) << error; }));
在新进程接收Invitation的方法如下
1 2 3 4 5 6 7 mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept ( mojo::PlatformChannel::RecovePassedEndpointFromCommdLine ( *base::CommandLine::ForCurrentProcess ())); mojo::ScopedMessagePipeHandle pipe = invitation.ExtractMessagePipe ("my raw pipe" ); LOG (INFO) << "pipe: " << pipe->value ();
这样就实现了将piep中的一个handle发送到其他进程了,这两个进程可以开始使用pipe进行收发数据了。 以上只是将handle 从一个进程发送到另一个进程的一种方法,这种方法一般用在新进程创建的时候,如果两个进程已经通过Invitation 连接起来了,那么可以通过已经建立起来的MessagePipe 来发送新的MessagePipe 的handle 到接收进程,发送端的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 const std::string kMessage ("MessagePipe\0" , 12 ) ;mojo::ScopedMessagePipeHandle client; mojo::ScopedMessagePipeHandle server; result = mojo::CreateMessagePipe (nullptr , &client, &server); DCHECK_EQ (result, MOJO_RESULT_OK);result = mojo::Windows (pipe.get (), kMessage.c_str (), kMessage.length (), &client->value (), 1 , MOJO_WRITE_MESSAGE_FLAG_DONE); DCHECK_EQ (result, MOJO_RESULT_OK);
//接收端代码如下
1 2 3 4 5 6 7 8 9 10 std::vector<uint8_t > data; std::vector<mojo::ScopedHandle> handles; result = mojo::ReadMessageRaw (pipe.get (), &data, &handles, MOJO_READ_MESSAGE_FLAG_NONE); mojo::ScopedMessagePipeHandle client = mojo::ScopedMessagePipeHandle::From (std::move (handles[0 ]));
Mojo C++ Bindings API Bindings API是使用mojo的重点,在项目中会大量使用
Mojom Mojo在Binding层引入了Mojom 这种IDL语言,用它来定义接口。接口定义文件的后缀一般为.mojo,,一个简单的接口定义如下