Macos 可以使用Mac OS X XPC(如IPC)在进程之间交换消息吗?怎么用?
据苹果称,Lion中引入的新XPC服务API为与Grand Central Dispatch(GCD)和launchd集成的基本进程间通信提供了一种轻量级机制 似乎可以将此API用作一种IPC,如POSIX IPC,但是,我找不到如何做到这一点 我尝试使用XPCAPI与两个进程通信,以便在它们之间传递消息,但在服务器端总是出现“XPCConnection invalid”错误 我不想要XPC服务,我只想使用客户机-服务器体系结构交换消息 我使用了两个类似BSD的进程,所以没有Info.plist或其他什么 我一直在关注这个讨论,但这个话题似乎有点模糊,没有记录在案Macos 可以使用Mac OS X XPC(如IPC)在进程之间交换消息吗?怎么用?,macos,ipc,osx-lion,sandbox,xpc,Macos,Ipc,Osx Lion,Sandbox,Xpc,据苹果称,Lion中引入的新XPC服务API为与Grand Central Dispatch(GCD)和launchd集成的基本进程间通信提供了一种轻量级机制 似乎可以将此API用作一种IPC,如POSIX IPC,但是,我找不到如何做到这一点 我尝试使用XPCAPI与两个进程通信,以便在它们之间传递消息,但在服务器端总是出现“XPCConnection invalid”错误 我不想要XPC服务,我只想使用客户机-服务器体系结构交换消息 我使用了两个类似BSD的进程,所以没有Info.plist
谢谢 是的,这是可能的,但不是你期望的那样 您可以不拥有一个(非启动)进程来提供服务。这是出于安全原因,因为这将使中间人攻击变得容易 不过,您仍然可以实现您想要的:您必须设置一个提供XPC/mach服务的启动服务。然后进程A和进程B都连接到您的启动服务。然后,进程A可以创建一个所谓的匿名连接,并将其发送给启动服务,启动服务将其转发给进程B。一旦发生这种情况,进程A和B可以通过该连接直接相互通信(即,启动服务可以在不中断连接的情况下退出) 这可能看起来很简单,但出于安全考虑,这是必要的 有关匿名连接的详细信息,请参阅
xpc\u对象(3)
手册页
这有点违反直觉,因为进程a将使用
xpc\u connection\u create()
创建一个侦听器对象。然后,A使用xpc\u endpoint\u create()
从侦听器创建端点对象,并将该端点通过线路(通过xpc)发送到进程B。B然后可以将该对象转换为具有xpc\u connection\u create\u from\u endpoint()
的连接。然后,A的侦听器事件处理程序将接收一个连接对象,该对象与B使用xpc\u connection\u create\u from\u endpoint()
创建的连接相匹配。这与xpc\u connection\u create\u mach\u service()
的事件处理程序在客户端连接时接收连接对象的方式类似。以下是我如何使用xpc实现双向IPC
助手(登录项)是服务器或侦听器。主应用程序或任何其他应用程序都被视为客户端
我创建了以下管理器:
标题:
@class CommXPCManager;
typedef NS_ENUM(NSUInteger, CommXPCErrorType) {
CommXPCErrorInvalid = 1,
CommXPCErrorInterrupted = 2,
CommXPCErrorTermination = 3
};
typedef void (^XPCErrorHandler)(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error);
typedef void (^XPCMessageHandler)(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message);
typedef void (^XPCConnectionHandler)(CommXPCManager *peerConnection);
@interface CommXPCManager : NSObject
@property (readwrite, copy, nonatomic) XPCErrorHandler errorHandler;
@property (readwrite, copy, nonatomic) XPCMessageHandler messageHandler;
@property (readwrite, copy, nonatomic) XPCConnectionHandler connectionHandler;
@property (readonly, nonatomic) BOOL clientConnection;
@property (readonly, nonatomic) BOOL serverConnection;
@property (readonly, nonatomic) BOOL peerConnection;
@property (readonly, nonatomic) __attribute__((NSObject)) xpc_connection_t connection;
@property (readonly, strong, nonatomic) NSString *connectionName;
@property (readonly, strong, nonatomic) NSNumber *connectionEUID;
@property (readonly, strong, nonatomic) NSNumber *connectionEGID;
@property (readonly, strong, nonatomic) NSNumber *connectionProcessID;
@property (readonly, strong, nonatomic) NSString *connectionAuditSessionID;
- (id) initWithConnection:(xpc_connection_t)aConnection;
- (id) initAsClientWithBundleID:(NSString *)bundleID;
- (id) initAsServer;
- (void) suspendConnection;
- (void) resumeConnection;
- (void) cancelConnection;
- (void) sendMessage:(NSDictionary *)dict;
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event;
@end
实施:
@interface CommXPCManager ()
@property (readwrite, nonatomic) BOOL clientConnection;
@property (readwrite, nonatomic) BOOL serverConnection;
@property (readwrite, nonatomic) BOOL peerConnection;
@property (readwrite, strong, nonatomic) __attribute__((NSObject)) dispatch_queue_t dispatchQueue;
@end
@implementation CommXPCManager
@synthesize clientConnection, serverConnection, peerConnection;
@synthesize errorHandler, messageHandler, connectionHandler;
@synthesize connection = _connection;
@synthesize dispatchQueue = _dispatchQueue;
#pragma mark - Message Methods:
- (void) sendMessage:(NSDictionary *)dict {
dispatch_async( self.dispatchQueue, ^{
xpc_object_t message = dict.xObject;
xpc_connection_send_message( _connection, message );
xpc_release( message );
});
}
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply {
dispatch_async( self.dispatchQueue, ^{
xpc_object_t message = dict.xObject;
xpc_connection_send_message_with_reply( _connection, message, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type( object );
if ( type == XPC_TYPE_ERROR ) {
/*! @discussion Reply: XPC Error */
reply( [NSDictionary dictionary], [NSError errorFromXObject:object] );
} else if ( type == XPC_TYPE_DICTIONARY ) {
/*! @discussion Reply: XPC Dictionary */
reply( [NSDictionary dictionaryFromXObject:object], nil );
}
}); xpc_release( message );
});
}
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event {
xpc_object_t message = [dict xObjectReply:event];
xpc_connection_t replyConnection = xpc_dictionary_get_remote_connection( message );
xpc_connection_send_message( replyConnection, message );
xpc_release( message );
}
#pragma mark - Connection Methods:
- (void) suspendConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_suspend( _connection ); });
}
- (void) resumeConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_resume(_connection); });
}
- (void) cancelConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_cancel(_connection); });
}
#pragma mark - Accessor Overrides:
- (void) setDispatchQueue:(dispatch_queue_t)queue {
if ( queue ) dispatch_retain( queue );
if ( _dispatchQueue ) dispatch_release( _dispatchQueue );
_dispatchQueue = queue;
xpc_connection_set_target_queue( self.connection, self.dispatchQueue );
}
#pragma mark - Getter Overrides:
- (NSString *) connectionName {
__block char* name = NULL;
dispatch_sync(self.dispatchQueue, ^{ name = (char*)xpc_connection_get_name( _connection ); });
if(!name) return nil;
return [NSString stringWithCString:name encoding:[NSString defaultCStringEncoding]];
}
- (NSNumber *) connectionEUID {
__block uid_t uid = 0;
dispatch_sync(self.dispatchQueue, ^{ uid = xpc_connection_get_euid( _connection ); });
return [NSNumber numberWithUnsignedInt:uid];
}
- (NSNumber *) connectionEGID {
__block gid_t egid = 0;
dispatch_sync(self.dispatchQueue, ^{ egid = xpc_connection_get_egid( _connection ); });
return [NSNumber numberWithUnsignedInt:egid];
}
- (NSNumber *) connectionProcessID {
__block pid_t pid = 0;
dispatch_sync(self.dispatchQueue, ^{ pid = xpc_connection_get_pid( _connection ); });
return [NSNumber numberWithUnsignedInt:pid];
}
- (NSNumber *) connectionAuditSessionID{
__block au_asid_t auasid = 0;
dispatch_sync(self.dispatchQueue, ^{ auasid = xpc_connection_get_asid( _connection ); });
return [NSNumber numberWithUnsignedInt:auasid];
}
#pragma mark - Setup Methods:
- (void) setupConnectionHandler:(xpc_connection_t)conn {
__block CommXPCManager *this = self;
xpc_connection_set_event_handler( conn, ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type( object );
if ( type == XPC_TYPE_ERROR ) {
/*! @discussion Client | Peer: XPC Error */
NSError *xpcError = [NSError errorFromXObject:object];
if ( object == XPC_ERROR_CONNECTION_INVALID ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorInvalid, xpcError );
} else if ( object == XPC_ERROR_CONNECTION_INTERRUPTED ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorInterrupted, xpcError );
} else if ( object == XPC_ERROR_TERMINATION_IMMINENT ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorTermination, xpcError );
}
xpcError = nil; return;
} else if ( type == XPC_TYPE_CONNECTION ) {
/*! @discussion XPC Server: XPC Connection */
CommXPCManager *xpcPeer = [[CommXPCManager alloc] initWithConnection:object];
if ( this.connectionHandler )
this.connectionHandler( xpcPeer );
xpcPeer = nil; return;
} else if ( type == XPC_TYPE_DICTIONARY ) {
/*! @discussion Client | Peer: XPC Dictionary */
if ( this.messageHandler )
this.messageHandler( this, object, [NSDictionary dictionaryFromXObject:object] );
}
});
}
- (void) setupDispatchQueue {
dispatch_queue_t queue = dispatch_queue_create( xpc_connection_get_name(_connection), 0 );
self.dispatchQueue = queue;
dispatch_release( queue );
}
- (void) setupConnection:(xpc_connection_t)aConnection {
_connection = xpc_retain( aConnection );
[self setupConnectionHandler:aConnection];
[self setupDispatchQueue];
[self resumeConnection];
}
#pragma mark - Initialization:
- (id) initWithConnection:(xpc_connection_t)aConnection {
if ( !aConnection ) return nil;
if ( (self = [super init]) ) {
self.peerConnection = YES;
[self setupConnection:aConnection];
} return self;
}
- (id) initAsClientWithBundleID:(NSString *)bundleID {
xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [bundleID UTF8String], nil, 0 );
if ( (self = [super init]) ) {
self.clientConnection = YES;
[self setupConnection:xpcConnection];
}
xpc_release( xpcConnection );
return self;
}
- (id) initAsServer {
xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [[[NSBundle mainBundle] bundleIdentifier] UTF8String],
dispatch_get_main_queue(),
XPC_CONNECTION_MACH_SERVICE_LISTENER );
if ( (self = [super init]) ) {
self.serverConnection = YES;
[self setupConnection:xpcConnection];
}
xpc_release( xpcConnection );
return self;
}
@end
显然,我使用的是一些自解释的分类方法。
例如:
@implementation NSError (CategoryXPCMessage)
+ (NSError *) errorFromXObject:(xpc_object_t)xObject {
char *description = xpc_copy_description( xObject );
NSError *xpcError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:@{
NSLocalizedDescriptionKey:
[NSString stringWithCString:description encoding:[NSString defaultCStringEncoding]] }];
free( description );
return xpcError;
}
@end
好的,使用这个,我为客户端和服务器端设置了一个接口。标题如下所示:
@class CommXPCManager;
@protocol AppXPCErrorHandler <NSObject>
@required
- (void) handleXPCError:(NSError *)error forType:(CommXPCErrorType)errorType;
@end
static NSString* const kAppXPCKeyReturn = @"AppXPCInterfaceReturn"; // id returnObject
static NSString* const kAppXPCKeyReply = @"AppXPCInterfaceReply"; // NSNumber: BOOL
static NSString* const kAppXPCKeySEL = @"AppXPCInterfaceSelector"; // NSString
static NSString* const kAppXPCKeyArgs = @"AppXPCInterfaceArguments"; // NSArray (Must be xObject compliant)
@interface AppXPCInterface : NSObject
@property (readonly, strong, nonatomic) CommXPCManager *managerXPC;
@property (readonly, strong, nonatomic) NSArray *peerConnections;
- (void) sendMessage:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (void) sendMessageToPeers:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (id) initWithBundleID:(NSString *)bundleID andDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (id) initListenerWithDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (void) observeListenerHello:(CommReceptionistNoteBlock)helloBlock;
- (void) removeListenerObserver;
- (void) startClientConnection;
- (void) startListenerConnection;
- (void) stopConnection;
@end
以下是启动客户端的实现:
- (void) startClientConnection {
[self stopConnection];
self.managerXPC = [[CommXPCManager alloc] initAsClientWithBundleID:self.identifierXPC];
__block AppXPCInterface *this = self;
self.managerXPC.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {
[this processMessage:message forEvent:event];
};
self.managerXPC.errorHandler = ^(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error) {
[this processError:error forErrorType:errorType];
};
}
现在事情的顺序是这样的
它的bundleID对任何一直在努力解决这个问题的人来说,我终于能够使用
NSXPCConnection在两个应用程序进程之间100%地实现通信
需要注意的关键是,您只能为三件事创建NSXPCConnection
XPC服务。您可以严格通过以下方式连接到XPCService
名字
马赫数服务。您还可以连接到马赫服务
严格地通过一个名字
一个NSXPCEndpoint
。这就是我们要做的
寻找,以便在两个应用程序进程之间进行通信
问题是我们不能直接将nsxpclisterendpoint
从一个应用程序传输到另一个应用程序
它涉及到创建一个包含nsxpclisterendpoint
属性的machservice启动代理()。一个应用程序可以连接到machservice,并将该属性设置为它自己的[NSXPCListener anonymousListener].endpoint
然后,另一个应用程序可以连接到服务,并请求该端点
然后使用该端点,可以创建一个NSXPCConnection
,它成功地在两个应用程序之间建立了一个桥梁。我已经测试过来回发送对象,一切正常
请注意,如果您的应用程序是沙盒的,必须创建一个XPCService
,作为应用程序和服务之间的中间人
我很高兴我能做到这一点——我在这方面相当活跃,所以如果有人对源代码感兴趣,只需添加一条评论,我就可以发布更多细节
我遇到的一些障碍:
您必须启动您的machservice,以下是线路:
OSStatus err;
AuthorizationExternalForm extForm;
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
NSLog(@"SUCCESS AUTHORIZING DAEMON");
}
assert(err == errAuthorizationSuccess);
Boolean success;
CFErrorRef error;
success = SMJobBless(
kSMDomainSystemLaunchd,
CFSTR("DAEMON IDENTIFIER HERE"),
self->_authRef,
&error
);
此外,每次重建守护程序时,都必须使用以下bash命令卸载以前的启动代理:
sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool
(当然,使用相应的标识符)似乎有人已经做到了这一点……但我自己仍然不知道如何做到这一点……如果你真的有父子关系,那么XPC是为你准备的,但如果你有两个独立的进程,XPC就不是出路。macOS基于Mach微内核,因此它有一个非常强大的IPC机制,比其他任何东西都快:Mach消息。它的工作原理有点像通过套接字发送数据,但您也可以让它通过共享内存为您传输数据(即写时拷贝)。这是一个有点糟糕的文件和概念是复杂的开始,但它值得学习。macOS中的所有其他IPC实际上都是在Mach消息之上实现的。如果我不想让进程B连接到该服务怎么办?例如,假设B是bash脚本或已经编译的C程序,那么是否可以通过XPC实现“经典”IPC?或者,我可以在某些情况下绕过XPC吗
sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool