Chromium进程间通信Mojo 进程间建立连接
发布于 2019-12-21
- Chromium新的进程间通信系统Mojo与Servicification
- Chromium进程间通信Mojo 初始化
- Chromium进程间通信Mojo 进程间建立连接
- Chromium进程间通信Mojo 收发消息
- Chromium进程间通信Mojo bindings
- Chromium进程间通信Mojo chromium中使用
invitation
Mojo中两个进程建立连接是通过发送和接收invitation实现的,一方发送OutgoingInvitation,另一方去接收IncomingInvitation。
Mojo底层的进程间通信技术依赖具体的系统,比如Windows是named pipe,UNIX系统是domain socket。以Windows系统为例,在建立好named pipe连接之后,通过往named pipe里面写入和读取一个特殊的invitation消息,来实现两个进程Mojo的连接。
这里有人可能会觉得奇怪了:建立好系统的named pipe的连接之后,就意味着进程间已经建立好IPC连接了。为什么还需要通过invitation去建立Mojo的连接?Mojo底层虽然还是通过系统的IPC连接去发送接收数据,但是Mojo对其做了一层封装,定义属于自己的一套IPC原语。另外可以把系统named pipe看作物理上IPC连接,在这之上还有逻辑上Mojo的MessagePipe连接。系统的named pipe上面可以同时承载着多个逻辑上的上MessagePipe。每个MessagePipe都有自己独立的消息队列。
建立连接
我们以一个例子来描述具体怎么建立Mojo的IPC连接。首先会创建一个parent进程,它创建好OutgoingInvitation并创建了child子进程,然把OutgoingInvitation发送给child子进程。child子进程那边则去接收IncomingInvitation来建立连接。
parent进程代码:
int main(int argc, char* argv[]) {
base::AtExitManager exit_manager;
base::CommandLine::Init(0, 0);
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_ALL;
settings.delete_old = logging::DELETE_OLD_LOG_FILE;
settings.log_file_path = L"mojo.log";
logging::InitLogging(settings);
base::MessageLoop loop;
mojo::core::Init();
base::Thread ipc_thread("ipc");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo::core::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
mojo::PlatformChannel channel;
mojo::OutgoingInvitation invitation;
mojo::ScopedMessagePipeHandle pipe =
invitation.AttachMessagePipe("pipe-name");
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
base::FilePath executable_path;
base::PathService::Get(base::DIR_MODULE, &executable_path);
base::FilePath client_path =
executable_path.AppendASCII("ipc_connect_platform_channel_child.exe");
command_line.SetProgram(client_path);
base::LaunchOptions options;
channel.PrepareToPassRemoteEndpoint(&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::RunLoop().Run();
return 0;
}
child子进程代码:
int main(int argc, char* argv[]) {
base::AtExitManager exit_manager;
base::CommandLine::Init(0, 0);
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_ALL;
settings.delete_old = logging::DELETE_OLD_LOG_FILE;
settings.log_file_path = L"mojo.log";
logging::InitLogging(settings);
base::MessageLoop loop;
mojo::core::Init();
base::Thread ipc_thread("ipc");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo::core::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
*base::CommandLine::ForCurrentProcess()));
mojo::ScopedMessagePipeHandle pipe =
invitation.ExtractMessagePipe("pipe-name");
base::RunLoop().Run();
return 0;
}
parent进程里首先我们创建了一个mojo::PlatformChannel
,它实际上会以当前进程id+线程id+随机数为名创建一个named pipe,比如\\.\pipe\mojo.19776.17344.14378832871110571506
。mojo::PlatformChannel
里面包含这个named pipe两端的句柄。把其中一端句柄发送给另外一个进程,那就建立了底层的named pipe连接。上面例子中是通过channel.PrepareToPassRemoteEndpoint(&options, &command_line);
把其中一端句柄值放到创建child子进程参数,另外把句柄设置成可继承,然后child子进程通过mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(*base::CommandLine::ForCurrentProcess())
来解析命令行参数获得named pipe一端的句柄。我们可以看到child子进程的子进程命令行参数是--mojo-platform-channel-handle=260
,其中260就是named pipe一端的句柄值。
其实我们也不必使用mojo提供的方法来传递named pipe一端的句柄值来建立IPC连接。只要我们自己能够把mojo::PlatformChannel
里面的remote_endpoint句柄传递给另外一个进程就可以了。我们可以通过自己定义的命令行参数或者其他已经建立好的途径。
parent进程创建了一个OutgoingInvitation
,然后就在它上面附加了一个MessagePipe
,MessagePipe
是Mojo自己发送和接收数据的逻辑管道。然后在child子进程里从IncomingInvitation
里面把MessagePipe
抽取出来。后面两个进程就可以通过MessagePipe
来读写数据了。
Isolated Invitation
上面建立Mojo连接的两个进程是由父子关系的。如果是两个独立没关系的进程,可以通过Isolated Invitation来建立连接。Isolated Invitation需要配合mojo::NamedPlatformChannel
来使用。
mojo::PlatformChannel
与mojo::NamedPlatformChannel
建立named pipe的机制有些差异。mojo::PlatformChannel
是在一个进程中把named pipe创建连接好,然后把其中的一个端口传递给另一个进程来使用。而mojo::NamedPlatformChannel
则是两个进程通过一个预先约定好的名称来创建建立named pipe连接。
parent进程代码:
int main(int argc, char* argv[]) {
base::AtExitManager exit_manager;
base::CommandLine::Init(0, 0);
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_ALL;
settings.delete_old = logging::DELETE_OLD_LOG_FILE;
settings.log_file_path = L"mojo.log";
logging::InitLogging(settings);
base::MessageLoop loop;
mojo::core::Init();
base::Thread ipc_thread("ipc");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo::core::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
mojo::NamedPlatformChannel::Options options;
options.server_name = L"test";
mojo::NamedPlatformChannel named_channel(options);
mojo::ScopedMessagePipeHandle pipe = mojo::OutgoingInvitation::SendIsolated(
named_channel.TakeServerEndpoint(), nullptr);
base::RunLoop().Run();
return 0;
}
child子进程代码:
int main(int argc, char* argv[]) {
base::AtExitManager exit_manager;
base::CommandLine::Init(0, 0);
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_ALL;
settings.delete_old = logging::DELETE_OLD_LOG_FILE;
settings.log_file_path = L"mojo.log";
logging::InitLogging(settings);
base::MessageLoop loop;
mojo::core::Init();
base::Thread ipc_thread("ipc");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo::core::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
mojo::PlatformChannelEndpoint endpoint =
mojo::NamedPlatformChannel::ConnectToServer(L"test");
mojo::ScopedMessagePipeHandle pipe =
mojo::IncomingInvitation::AcceptIsolated(std::move(endpoint));
base::RunLoop().Run();
return 0;
}
parent进程的mojo::NamedPlatformChannel
指定了一个test
名字,实际上上创建一个名为\Device\NamedPipe\mojo.test
的named pipe。child子进程也通过test
这个名字去连接上parent进程的named pipe连接。
此外Mojo还提供了IsolatedConnection来进行连接。