Можно ли использовать Mac OS X XPC, например IPC, для обмена сообщениями между процессами? Как?
Согласно Apple, новый XPC Services API, представленный в Lion, обеспечивает легкий механизм для базового межпроцессного взаимодействия, интегрированный с Grand Central Dispatch (GCD) и запущенный.
Кажется возможным использовать этот API как своего рода IPC, например POSIX IPC, однако я не могу найти, как это сделать.
Я пытаюсь связать два процесса, используя XPC API, чтобы я мог передавать сообщения между ними, но я всегда получаю ошибку "Недопустимое соединение XPC" на стороне сервера.
Я не хочу службы XPC, я просто хочу обмениваться сообщениями, используя архитектуру клиент-сервер.
Я использую два BSD-подобных процесса, поэтому нет Info.plist или чего-то еще...
Я следил за этим обсуждением http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.html но эта тема кажется немного неясной и недокументированной.
Спасибо!
3 ответа
Да, это возможно, но не так, как вы ожидаете.
Вы не можете иметь (не запускаемый) процесс продажи услуги. Это сделано из соображений безопасности, поскольку было бы легко проводить атаки "человек посередине".
Тем не менее, вы все равно можете достичь желаемого: вам нужно настроить запускаемый сервис, который предоставляет сервис XPC / mach. Затем оба процесса A и B подключаются к вашему сервису launchd. Затем процесс A может создать так называемое анонимное соединение и отправить его в сервис launchd, который перенаправит его в процесс B. После того, как это произошло, процессы A и B могут общаться друг с другом напрямую через это соединение (т. Е. Служба launchd может выйти без разрыва соединения).
Это может показаться обходным, но это необходимо по соображениям безопасности.
Увидеть xpc_object(3)
Страница man для подробностей об анонимных подключениях.
Это немного противоречит интуитивно, потому что процесс A создаст объект слушателя с xpc_connection_create()
, А затем создает объект конечной точки из слушателя с xpc_endpoint_create()
и отправляет эту конечную точку через провод (через XPC) для обработки B. Затем B может превратить этот объект в соединение с xpc_connection_create_from_endpoint()
, Затем обработчик события A для слушателя получит объект подключения, соответствующий соединению, созданному B xpc_connection_create_from_endpoint()
, Это работает аналогично тому, как обработчик события xpc_connection_create_mach_service()
будет получать объекты подключения при подключении клиентов.
Вот как я делаю двунаправленный IPC с использованием XPC.
Помощник (элемент входа в систему) - это сервер или слушатель. Основное приложение или любое другое приложение считаются клиентами.
Я создал следующий менеджер:
Заголовок:
@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
Очевидно, я использую некоторые методы Category, которые говорят сами за себя. Например:
@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) startListenerConnection {
[self stopConnection];
self.managerXPC = [[CommXPCManager alloc] initAsServer];
__block AppXPCInterface *this = self;
self.managerXPC.connectionHandler = ^(CommXPCManager *peerConnection) {
[(NSMutableArray *)this.peerConnections addObject:peerConnection];
peerConnection.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {
[this processMessage:message forEvent:event];
};
peerConnection.errorHandler = ^(CommXPCManager *peer, CommXPCErrorType errorType, NSError *error) {
[this processError:error forErrorType:errorType];
[(NSMutableArray *)this.peerConnections removeObject:peer];
};
};
[CommReceptionist postGlobalNote:kAppXPCListenerNoteHello];
}
Вот реализация для запуска клиента:
- (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 <--- Важно!
- Основное приложение прослушивает глобальное уведомление, а затем отправляет сообщение
- Когда клиент отправляет сообщение, соединение установлено
Теперь сервер может отправлять сообщения клиенту, а клиент может отправлять сообщения на сервер (с ответом или без него).
Это очень быстро, хорошо работает и разработано для OS X 10.7.3 или выше.
Несколько заметок:
- Имя помощника должно совпадать с именем пакета
- Имя должно начинаться с идентификатора вашей команды
- Для "песочницы" параметр приложения "Основное приложение" и "Приложение-помощник" должен запускаться с префиксом идентификатора пакета помощника.
Например, идентификатор пакета помощника: ABC123XYZ.CompanyName.GroupName.Helper Идентификатор группы приложений будет: ABC123XYZ.CompanyName.GroupName
Есть дополнительные детали, которые я пропустил, чтобы никому не надоедать. Но если все еще неясно, просто спросите, и я отвечу.
Хорошо, надеюсь, это поможет. Арвин
Ладно, для всех, кто боролся с этим, я наконец смог на 100% установить связь между двумя процессами приложения, используя NSXPCConnection
Ключ к сведению, что вы можете только создать NSXPCConnection
до трех вещей.
- XPCService. Вы можете подключиться к XPCService строго через имя
- Маха Сервис. Вы также можете подключиться к службе Mach строго через имя
-
NSXPCEndpoint
, Это то, что мы ищем, чтобы общаться между двумя прикладными процессами.
Проблема в том, что мы не можем напрямую передать NSXPCListenerEndpoint
из одного приложения в другое.
Он включал создание агента запуска machservice ( см. Этот пример, как это сделать), который содержал NSXPCListenerEndpoint
имущество. Одно приложение может подключиться к machservice и установить это свойство на свое собственное [NSXPCListener anonymousListener].endpoint
Затем другое приложение может подключиться к machservice и запросить эту конечную точку.
Затем, используя эту конечную точку, NSXPCConnection
может быть создан, который успешно установил мост между двумя приложениями. Я проверил отправку объектов туда и обратно, и все работает как положено.
Обратите внимание, что если ваше приложение находится в "песочнице", вам придется создать XPCService
как посредник между вашим Приложением и Machservice
Я очень рад, что у меня все получилось - я довольно активен в SO, поэтому, если кто-то заинтересован в исходном коде, просто добавьте комментарий, и я могу приложить усилия, чтобы опубликовать больше деталей
Некоторые препятствия, с которыми я столкнулся:
Вы должны запустить свой 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
(С вашими соответствующими идентификаторами, конечно)