Можно ли использовать Mac OS X XPC, например IPC, для обмена сообщениями между процессами? Как? - PullRequest
19 голосов
/ 26 декабря 2011

Согласно Apple, новый API-интерфейс XPC Services, представленный в 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 ]

17 голосов
/ 08 февраля 2012

Да, это возможно, но не так, как вы ожидаете.

Вы можете не иметь (не запускаемый) процесс продажи услуги. Это сделано из соображений безопасности, поскольку было бы легко проводить атаки «человек посередине».

Тем не менее, вы все равно можете достичь того, чего хотите: вам нужно настроить сервис launchd, который реализует службу XPC / mach. Затем оба процесса A и B подключаются к вашему сервису launchd. Затем процесс A может создать так называемое анонимное соединение и отправить его в сервис launchd, который перенаправит его на процесс B. Как только это произошло, процессы A и B могут общаться друг с другом напрямую через это соединение ( т.е. служба launchd может выйти без разрыва соединения).

Это может показаться обходным, но это необходимо по соображениям безопасности.

Подробнее об анонимных подключениях см. Справочную страницу xpc_object(3).

Это немного противоречит интуитивно, потому что процесс A создаст объект listener с xpc_connection_create(). Затем A создает из слушателя объект конечная точка с помощью xpc_endpoint_create() и отправляет эту конечную точку по проводам (через XPC) для обработки B. Затем B может превратить этот объект в соединение с xpc_connection_create_from_endpoint(). Затем обработчик события A для прослушивателя получит объект соединения, соответствующий соединению, которое B создал с помощью xpc_connection_create_from_endpoint(). Это работает аналогично тому, как обработчик событий xpc_connection_create_mach_service() будет получать объекты подключения при подключении клиентов.

11 голосов
/ 30 августа 2012

Вот как я делаю двунаправленный 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];
    };
}

Теперь вот порядок вещей.

  1. Ваше основное приложение запускает свой помощник Помощник начинает слушать, используя его bundleID <--- Важно!</li>
  2. Основное приложение прослушивает глобальное уведомление и затем отправляет сообщение
  3. Когда клиент отправляет сообщение, соединение установлено

Теперь сервер может отправлять сообщенияклиенту и клиент может отправлять сообщения на сервер (с ответом или без него).

Это очень быстро, хорошо работает и разработано для OS X 10.7.3 или выше.

Несколько замечаний:

  • Имя помощника должно совпадать с именем пакета
  • Имя должно начинаться с идентификатора вашей команды
  • ДляВ «песочнице» настройки как основного приложения, так и группы приложений вспомогательного приложения должны начинаться с префикса идентификатора вспомогательного пакета

, например, идентификатор вспомогательного пакета: ABC123XYZ.CompanyName.GroupName.Helper. Идентификатор группы приложений будет следующим:ABC123XYZ.CompanyName.GroupName

Есть дополнительные детали, которые я пропустил, чтобы никому не надоедать.Но если все еще неясно, просто спросите, и я отвечу.

Хорошо, надеюсь, это поможет.Арвин

7 голосов
/ 07 февраля 2017

Хорошо, для всех, кто боролся с этим, я наконец смог на 100% установить связь между двумя процессами приложения, используя NSXPCConnection

Ключ к сведению заключается в том, что вы можете создать NSXPCConnection только для трех вещей.

  1. XPCService. Вы можете подключиться к XPCService строго через имя
  2. Сервис Маха. Вы также можете подключиться к службе Mach строго через имя
  3. An 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

(с вашими соответствующими идентификаторами, конечно)

...