Mach Ports Distrbuted Notifications Distrbuted Objects AppleEvents & AppleScript Pasteboard XPC

Mach Ports

所有进程间通信最终落实依赖的还是Mach 内核 API 提供的功能。

Mach Port 是在系统内核实现和维护的一种 IPC 消息队列,持有用于 IPC 通信的 mach messages。

只有一个进程可以从对应的 port 里 dequeue 一条消息,这个进程被持有接收权利(receive-right)。

可以有多个进程往某个 port 里 enqueue 消息,这些进程持有该 port 的发送权利(send-rights)。

我们可以简单把 mach port 看做是一个单向的数据发送渠道,构建一个消息结构体后通过mach_msg() 方法发出去。

因为只能单向发送,所以当 B 进程收到了 A 进程发来的消息之后要自己创建一个新的 Port 然后又发回去 A 进程。

Mach 端口发送一个信息调用一次 mach_msg_send

送之前需要做初始化。

natural_t data;
mach_port_t port;

struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_type_descriptor_t type;
} message;

message.header = (mach_msg_header_t) {
    .msgh_remote_port = port,
    .msgh_local_port = MACH_PORT_NULL,
    .msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0),
    .msgh_size = sizeof(message)
};

message.body = (mach_msg_body_t) {
    .msgh_descriptor_count = 1
};

message.type = (mach_msg_type_descriptor_t) {
    .pad1 = data,
    .pad2 = sizeof(data)
};

mach_msg_return_t error = mach_msg_send(&message.header);

if (error == MACH_MSG_SUCCESS) {
    // ...
}

接收消息的时候,只需要声明不用初始化

mach_port_t port;

struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_type_descriptor_t type;
    mach_msg_trailer_t trailer;
} message;

mach_msg_return_t error = mach_msg_receive(&message.header);

if (error == MACH_MSG_SUCCESS) {
    natural_t data = message.type.pad1;
    // ...
}

CFMessagePort 确实非常适合用于简单的一对一通讯。简简单单几行代码,一个本地端口就被附属到runloop源上,只要获取到消息就执行回调。

static CFDataRef Callback(CFMessagePortRef port,
                          SInt32 messageID,
                          CFDataRef data,
                          void *info)
{
    // ...
}

CFMessagePortRef localPort =
    CFMessagePortCreateLocal(nil,
                             CFSTR("com.example.app.port.server"),
                             Callback,
                             nil,
                             nil);

CFRunLoopSourceRef runLoopSource =
    CFMessagePortCreateRunLoopSource(nil, localPort, 0);

CFRunLoopAddSource(CFRunLoopGetCurrent(),
                   runLoopSource,
                   kCFRunLoopCommonModes);