Можно ли использовать Mac OS X XPC, например, IPC для обмена сообщениями между процессами? Как?
Согласно Apple, новый API услуг XPC, представленный в Lion, обеспечивает легкий механизм для базовой межпроцессной связи, интегрированной с Grand Central Dispatch (GCD) и launchd.
Кажется возможным использовать этот API как своего рода IPC, например POSIX IPC, однако я не могу найти, как это сделать.
Я пытаюсь передать два процесса с помощью API XPC, чтобы передавать сообщения между ними, но всегда получаю сообщение об ошибке "Ошибка соединения с XPC" на стороне сервера.
Мне не нужна служба XPC, я просто хочу обмениваться сообщениями с использованием архитектуры клиент-сервер.
Я использую два BSD-подобных процесса, поэтому нет Info.plist или что-то еще...
Я обсуждал это обсуждение http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.html, но этот вопрос кажется немного неясным и недокументированным.
Спасибо!
Ответы
Ответ 1
Да, это возможно, но не так, как вы ожидали.
Вы можете не иметь (не запущенный) процесс, чтобы отправить услугу. Это по соображениям безопасности, так как это облегчит выполнение атак типа "человек в середине".
Вы все равно можете достичь того, чего хотите: вы должны настроить службу запуска, которая поддерживает службу XPC/mach. Оба процесса A и B затем подключаются к вашей службе запуска. Затем процесс A может создать так называемое анонимное соединение и отправить его службе запуска, которая будет перенаправлять ее на процесс B. Как только это произошло, процессы A и B могут разговаривать друг с другом напрямую через это соединение (т.е. служба запуска может выйти без нарушения соединения).
Это может показаться круглым, но это необходимо по соображениям безопасности.
Подробную информацию об анонимных подключениях см. в справочной странице xpc_object(3)
.
Это бит счетчик интуитивно понятен, потому что процесс A создаст объект-слушатель с помощью xpc_connection_create()
. Затем A создает объект конечной точки из прослушивателя с помощью xpc_endpoint_create()
и отправляет эту конечную точку через провод (поверх XPC) для обработки B. B может затем превратить этот объект в соединение с xpc_connection_create_from_endpoint()
. Обработчик события для слушателя затем получит объект соединения, соответствующий соединению B, созданному с помощью xpc_connection_create_from_endpoint()
. Это работает так же, как обработчик событий xpc_connection_create_mach_service()
будет получать объекты соединения при подключении клиентов.
Ответ 2
Вот как я делаю двунаправленный 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
Очевидно, что я использую некоторые методы категории, которые являются самоочевидными.
Например:
@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
Есть дополнительные данные, которые я забыл, чтобы никого не ронять. Но если все еще неясно спросить, и я отвечу.
Хорошо, надеюсь, это поможет.
Арвин
Ответ 3
Хорошо для всех, кто боролся с этим, я наконец смог на 100% получить связь между двумя процессами приложения, используя NSXPCConnection
Ключом к сведению является то, что вы можете создавать только NSXPCConnection
три вещи.
- XPCService. Вы можете подключиться к XPCService строго через
имя
- Служба Маха. Вы также можете подключиться к службе Mach
строго через имя
- An
NSXPCEndpoint
. Это то, что мы
ищет связь между двумя процессами приложения.
Проблема состоит в том, что мы не можем напрямую передавать NSXPCListenerEndpoint
из одного приложения в другое.
Это связано с созданием агента запуска machservice (см. этот пример, как это сделать), который содержал свойство NSXPCListenerEndpoint
. Одно приложение может подключаться к макссервису и устанавливать для него это свойство [NSXPCListener anonymousListener].endpoint
Затем другое приложение может подключиться к макссервису и запросить эту конечную точку.
Затем, используя эту конечную точку, может быть создан NSXPCConnection
, который успешно установил мост между двумя приложениями. Я протестировал отправку объектов туда и обратно, и все работает так, как ожидалось.
Обратите внимание, что если ваше приложение изолировано, вы должны создать XPCService
, как средний человек между вашим приложением и Machservice
Я очень накачиваюсь, что у меня это работает. Я довольно активен в SO, поэтому, если кто-то заинтересован в исходном коде, просто добавьте комментарий, и я могу попытаться опубликовать более подробную информацию.
Некоторые препятствия, с которыми я столкнулся:
Вам нужно запустить свой макссервис, это строки:
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
(Разумеется, с вашими соответствующими идентификаторами)