谈谈Mac进程间通信--XPC

概述

XPC Service是一种整合了GCDlaunchd的一种轻量级进程间通信机制,其主要目的是提供:权限分离和稳定性。

权限分离:利用xpc服务具有自己的沙箱环境,将应用程序分割为若干个小组件来控制权限,来实现严格的沙箱环境,减小被攻击的风险;其中xpc服务是私有的,仅用于包含它的主应用有效;其运行在更严格的沙箱环境中,如最小的文件访问权限、网络访问等,且不支持将其服务权限提升为root权限;

稳定性:通过将应用不稳定的功能与应用核心功能分开,来避免稳定性对整个应用的影响;

XPC Service由launchd来管理其启动、监视及终止,比如崩溃恢复,服务完成或者闲置会被kill -9终止,更好地管理XPC服务的生命周期。

通过find /System/Library/Frameworks -name \*.xpc或者find /Applications -name \*.xpc搜索系统框架及应用下的XPC服务,可以发现:XPC被广泛使用在系统框架、系统应用及第三方应用中,如XcodeChromeCleanMyMac等。不过对于iOS,只能苹果使用对于第三方开发者无法使用(xcode中也未提供相应的模版)。

使用及原理实现

苹果提供了两种处理xpc服务端api:

  • 基于纯c实现的服务api(包含在libSystem中),包含两部分:

    • xpc.h,定义了XPC支持的对象和数据类型及其操作接口,以及服务启动、事务管理接口;

    • connection.h,定义了用于XPC服务连接的建立/激活/暂停、消息发送/响应、连接属性信息获取及设置事件处理程序及目标队列等的接口;

image.png
  • 基于Objective-C实现的NSXPCConnection接口,提供了远程过程调用机制,允许本地进程通过代理对象调用另一个进程的方法,并自动处理数据序列化及反序列化,主要由以下几部分:

    • NSXPCConnection,用于两个进程的双向通信;
    • NSXPCInterface,用于约定通信双方的调用行为;
    • NSXPCListener,用于监听传入的连接并设定NSXPCListenerDelegate协议的代理对象来接收处理传入的连接;
    • NSXPCListeneEndpoint,一个可以唯一标识NSXPCListener实例的类,可以使用NSXPCConnection将其发送到远程进程。这允许一个进程在其他两个进程之间构造一个直接的通信通道;

下面将介绍下典型的使用上述接口来创建使用XPC服务。

创建XPC服务

XPC服务典型应用就是应用内组件之间通信以实现权限分离保证核心功能稳定性,其创建相对简单,xcode已经提供了相应的模版,编译后会直接添加到相应的应用包中,路径为/Contens/XPCSercices,其包结构与应用包结构类似,都包含二进制程序、Info.plist文件及添加的资源文件;

image.png

xcode创建服务模版默认使用NSXPCConnection方式,Info.plist中配置的XPCService字典服务类型ServiceTypeApplicationInfo.plist还包含其他定义服务属性及类型字段,如下:

  • CFBundleIdentifier,服务bundle id,命名应符合反向DNS风格,如com.fengyunsky.xpc.xpcmain
  • CFBundlePackageType,服务类型,必须为XPC!
  • XPCService,服务属性字段,包含
    • EnvironmentVariables字典属性来设置环境变量值;
    • JoinExistingSession BOOL类型属性,用于指示是否需要创建新的安全会话,默认为false,即创建新会话;若为true,则可以访问当前用户的keychain、剪贴板及其他用户的资源及服务;
    • RunLoopType,字符串类型,用于指示服务runloop类型,默认为dispatch_main,或者NSRunLoop;
    • ServiceType,服务类型,默认为Application,也可以为User或者System

dispatch_main()pthread_exit的包装,其主要作用是退出主线程并继续执行其他子线程,对于添加到主队列的block由workqueue线程来处理;

创建服务并监听连接典型使用如下:

static void XPCService_peer_event_handler(xpc_connection_t peer, xpc_object_t event)
{
    xpc_type_t type = xpc_get_type(event);
    if (type == XPC_TYPE_ERROR)
    {
        if (event == XPC_ERROR_CONNECTION_INVALID)
        {
            //连接无效
        }
        else if (event == XPC_ERROR_TERMINATION_IMMINENT)
        {
            //即将终止
        }
    } else {
        //处理连接业务,如发送消息
        const char* data = "hello world!";
        xpc_object_t dictionary = xpc_dictionary_create(NULL, NULL, 0);
        xpc_dictionary_set_data(dictionary, "msg", data, strlen(data));
        xpc_connection_send_message(peer, dictionary);
    }
}

static void XPCService_event_handler(xpc_connection_t peer)
{
    xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
        XPCService_peer_event_handler(peer, event);
    });
    xpc_connection_resume(peer);
}

int main(int argc, const char *argv[]) {
    xpc_main(XPCService_event_handler);
 
    // The xpc_main() function never returns.
    exit(EXIT_FAILURE);
}

xpc_main函数来启动服务并设置事件处理函数,其中事件处理函数XPCService_event_handler主要设置事件连接处理函数并xpc_connection_resume恢复连接;默认连接处理队列是DISPATCH_TARGET_QUEUE_DEFAULT,可以通过xpc_connection_set_target_queue修改GCD处理队列;xpc_connect提供了同步或者异步的发送/接收消息接口,如下:

image.png

其中xpc_connection_send_message_with_reply_sync为同步接口,阻塞直到收到应答消息,其余为异步接口;xpc_connection_send_barrier可以设定最后一条消息发送完成后的执行block,类似dispatch栅栏接口。

注意:消息发送形式必须为字典对象;

消息发送实现如图:

image.png

其实质是通过mach_msg发送消息,即通过mach消息机制实现;

基于Objective-c形式如下:

//Objective-C接口形式创建
@interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
@end

@implementation ServiceDelegate

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(xpcmainProtocol)];
    xpcmain *exportedObject = [xpcmain new];
    newConnection.exportedObject = exportedObject;
    [newConnection resume];
    
    return YES;
}

@end

int main(int argc, const char *argv[]) {
    MyDelegateClass *myDelegate = ...
    NSXPCListener *listener =
        [NSXPCListener serviceListener];
 
    listener.delegate = myDelegate;
    [listener resume];
 
    // The resume method never returns.
    exit(EXIT_FAILURE);
}

NSXPCConnection相关的类提供更为高级的接口,通过NSXPCListener来监听服务连接,并通过指定NSXPCListenerDelegate代理方法listener:shouldAcceptNewConnection:来处理新的连接请求;NSXPCConnection属性exportedInterfaceexportedObject来指定导出接口(通过协议实现)及对象,用于对端服务进程分别通过指定的NSXPCConnection属性remoteObjectInterfaceremoteObjectProxy来获取远端约定的导出接口及导出对象,进而实现本地调用远端方法,即远程过程调用;

消息发送

基于c实现的接口

典型使用接口如下:

//创建xpc_connection_t对象
//其中"com.fengyunsky.xpc.demo"为xpc服务的bundleID,需要指定正确否则launchd无法找到相应的服务
_connection = xpc_connection_create("com.fengyunsky.xpc.demo", NULL);
xpc_connection_set_event_handler(_connection, ^(xpc_object_t object){
        size_t len = 0;
    const char *data = xpc_dictionary_get_data(object, "msg", &len);
    fwrite(data, len, 1, stdout);
    fflush(stdout);
});
xpc_connection_resume(_connection);

//发送消息
const char *data = "hello world!";
xpc_object_t dictionary = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_data(dictionary, "msg", data, strlen(data));
xpc_connection_send_message_with_reply(_connection, dictionary, DISPATCH_TARGET_QUEUE_DEFAULT, ^(xpc_object_t  _Nonnull object) {
    xpc_type_t type = xpc_get_type(object);
    if (type == XPC_TYPE_ERROR)
    {
        if (object == XPC_ERROR_CONNECTION_INVALID)
        {
            //连接无效
        }
        else if (object == XPC_ERROR_TERMINATION_IMMINENT)
        {
            //即将终止
        }
    } else {
        const void *data = xpc_data_get_bytes_ptr(object);
        NSLog(@"reply:%s", (char *)data);
    }
});

//消息发送完成后可终止连接
xpc_connection_cancel(_connection);

主要的流程如下:

  • xpc_connection_create创建xpc连接对象;
  • xpc_connection_set_event_handler设置连接事件处理函数;
  • xpc_connection_resume启动服务开启通信;
  • xpc_connect_send_xxx调用发送消息接口来异步/同步等待响应消息;
  • launchd守护进程搜索主应用包匹配的CFBundleIdentifier服务并启动服务程序;
  • 连接的xpc服务程序连接设定的处理函数并调用;
  • 使用xpc_connect_send_xxx响应消息并发送消息;
  • 如果出现错误,就会通过xpc_connection_set_event_handler设置的事件处理函数来处理异常错误;
  • 可以通过xpc_connection_suspend暂停连接,但必须与xpc_connection_resume配对使用;
  • 连接完成后,就通过xpc_connection_cancel来终止连接;

对于创建的xpc_connection_t连接对象可通过全局对象保存,用于后续消息同一连接消息发送/接收;

基于Objective-C实现的接口

典型使用如下:

//创建NSXPCConnection对象,指定协议接口,启动连接
_connectionToService = [[NSXPCConnection alloc] initWithServiceName:"com.fengyunsky.xpc.demo"];
_connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(xpcmainProtocol)];
[_connectionToService resume];

//发送消息
[[_connectionToService remoteObjectProxy] upperCaseString:@"hello" withReply:^(NSString *aString) {
  // We have received a response. Update our text field, but do it on the main thread.
  NSLog(@"Result string was: %@", aString);
}];

//终止连接
[_connectionToService invalidate];

大致的流程如下图;

image.png

通过initWithServiceName来创建NSXPCConnection连接对象,interfaceWithProtocol来指定约定的协议方法,resume来启动连接;当通过remoteObjectProxy对象来调用xpc服务的方法时,launchd会搜索应用包中匹配的xpc服务并启动该服务,通过创建xpc服务的代理方法来接口连接,并执行导出接口方法。

总结

相比其他的进程间通信方式,如NSDistributedNotificationCenterMach Port、域套接字等,XPC服务实现更轻量,无需管理子进程的生命周期,并且能实现子进程崩溃恢复功能,通过NSXPCConnection相关的高级接口方便实现远程过程调用,使用更为简洁易用;

Reference

XPC

XPC介绍

XPC API

Creating XPC Services

Daemons and Services Programming Guide

Introducing XPC

利用XPC实现多进程之间的通信

《深入理解MacOS X &iOS操作系统》

进程间通信 (OSX/iOS)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345