macos - ¿Es posible usar Mac OS X XPC como IPC para intercambiar mensajes entre procesos? ¿Cómo?
osx-lion sandbox (3)
Según Apple, la nueva API de servicios XPC, presentada en Lion, proporciona un mecanismo ligero para la comunicación básica entre procesos integrada con Grand Central Dispatch (GCD) y launchd.
Parece posible utilizar esta API como un tipo de IPC, como POSIX IPC, sin embargo, no puedo encontrar cómo hacerlo.
Estoy intentando comunicar dos procesos utilizando la API de XPC para poder pasar mensajes entre ellos, pero siempre recibo un error de "conexión de XPC no válida" en el lado del servidor.
No quiero un servicio XPC, solo quiero intercambiar mensajes usando una arquitectura cliente-servidor.
Estoy usando dos procesos similares a BSD, así que no hay Info.plist o lo que sea ...
He estado siguiendo esta discusión http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.html pero este tema parece un poco oscuro e indocumentado.
¡Gracias!
Aquí es cómo estoy haciendo IPC bidireccional usando XPC.
El Ayudante (elemento de inicio de sesión) es el servidor o el oyente. La aplicación principal o cualquier otra aplicación se consideran clientes.
He creado el siguiente gestor:
Encabezamiento:
@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
Implementación:
@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
Obviamente, estoy usando algunos métodos de Categoría que son autoexplicativos. Por ejemplo:
@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
De acuerdo, usando esto, me configuro una interfaz para el lado del cliente y del lado del servidor. El encabezado se ve así:
@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
Aquí está la implementación para iniciar el oyente:
- (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];
}
Aquí está la implementación para iniciar el cliente:
- (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];
};
}
Ahora aquí está el orden de las cosas.
- Su aplicación principal comienza su ayudante El ayudante comienza a escuchar usando su ID de paquete <--- ¡Importante!
- La aplicación principal escucha una notificación global y luego envía un mensaje.
- Cuando el cliente envía un mensaje se establece la conexión.
Ahora el servidor puede enviar mensajes al cliente y el cliente puede enviar mensajes al servidor (con o sin respuesta).
Es muy rápido, funciona bien y está diseñado para OS X 10.7.3 o superior.
Algunas notas:
- El nombre del ayudante debe ser el mismo nombre que el ID del paquete
- El nombre debe comenzar con su ID de equipo
- Para el sandboxing, tanto la configuración de la aplicación principal como la del grupo de aplicaciones de ayuda deben comenzar con el prefijo de la ID del paquete de ayuda
por ejemplo, el identificador del paquete de ayuda es: ABC123XYZ.CompanyName.GroupName.Helper El ID de grupo de la aplicación será: ABC123XYZ.CompanyName.GroupName
Hay detalles adicionales que dejé fuera para no aburrir a nadie. Pero si aún no está claro solo pregunta y te responderé.
Ok, espero que esto ayude. Arvin
Bien para cualquiera que haya estado luchando con esto, finalmente pude obtener el 100% de comunicación trabajando entre dos procesos de aplicación, utilizando NSXPCConnection
La clave a tener en cuenta es que solo puede crear una NSXPCConnection
a tres cosas.
- Un servicio XPC. Puede conectarse a un XPCService estrictamente a través de un nombre
- Un servicio de Mach. También puede conectarse a un Servicio Mach estrictamente a través de un nombre
- Un punto de entrada
NSXPCEndpoint
. Esto es lo que estamos buscando, para comunicarnos entre dos procesos de aplicación.
El problema es que no podemos transferir directamente un NSXPCListenerEndpoint
de una aplicación a otra.
Implica la creación de un agente de inicio de servicio de máquina ( consulte este ejemplo para saber cómo hacerlo ) que contenía una propiedad NSXPCListenerEndpoint
. Una aplicación puede conectarse al servicio de máquina y establecer esa propiedad en su propio [NSXPCListener anonymousListener].endpoint
Luego, la otra aplicación puede conectarse al servicio de maquinaria y solicitar ese punto final.
Luego, utilizando ese punto final, se puede crear una NSXPCConnection
, que estableció con éxito un puente entre las dos aplicaciones. He probado el envío de objetos de ida y vuelta, y todo funciona como se esperaba.
Tenga en cuenta que si su aplicación se encuentra en un espacio aislado, tendrá que crear un servicio XPCService
, como intermediario entre su aplicación y Machservice.
Estoy bastante entusiasmado de que haya funcionado. Soy bastante activo en SO, así que si alguien está interesado en el código fuente, simplemente agregue un comentario y puedo hacer un esfuerzo para publicar más detalles.
Algunos obstáculos que encontré:
Tienes que lanzar tu servicio de máquina, estas son las líneas:
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
);
Además, cada vez que reconstruyas tu demonio, debes descargar el agente de inicio anterior, con estos comandos 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
(Con sus identificadores correspondientes, por supuesto)
Sí, eso es posible, pero no de la manera que esperas.
No se puede tener un proceso (no launchd) para vender un servicio. Esto es por razones de seguridad, ya que facilitaría los ataques de intermediarios.
Sin embargo, aún puede lograr lo que desea: debe configurar un servicio de lanzamiento que ofrezca un servicio XPC / mach. Tanto el proceso A como el B se conectan a su servicio launchd. Luego, el proceso A puede crear una llamada conexión anónima y enviarla al servicio launchd, que la enviará al proceso B. Una vez que esto sucede, los procesos A y B pueden comunicarse entre sí directamente a través de esa conexión (es decir, el servicio launchd puede salir). sin que se rompa la conexión).
Esto puede parecer general, pero es necesario por razones de seguridad.
Consulte la página del xpc_object(3)
para obtener detalles sobre las conexiones anónimas.
Es un contador de bits intuitivo, porque el proceso A creará un objeto de escucha con xpc_connection_create()
. A luego crea un objeto de punto final desde el oyente con xpc_endpoint_create()
y lo envía a través del cable (sobre XPC) al proceso B. B puede convertir ese objeto en una conexión con xpc_connection_create_from_endpoint()
. El controlador de eventos de A para el oyente recibirá un objeto de conexión que coincide con la conexión que B creó con xpc_connection_create_from_endpoint()
. Esto funciona de manera similar a la forma en que el controlador de xpc_connection_create_mach_service()
de xpc_connection_create_mach_service()
recibirá objetos de conexión cuando los clientes se conecten.