你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS進程間通信之CFMessagePort

iOS進程間通信之CFMessagePort

編輯:IOS開發基礎

iOS系統是出了名的封閉,每個應用的活動范圍被嚴格地限制在各自的沙盒中。盡管如此,iOS還是提供了若干進程間通信機制,CFMessagePort就是其中之一。

從類名可以看出,CFMessagePort屬於Core Foundation層的東西,其實現部分是開源的,代碼在可以在蘋果的開源代碼庫中找到。

使用方式

1、消息接收者

CFMessagePort端口消息的接收者需要實現以下功能:

1.1 注冊監聽

消息接收者需要通過以下方式注冊消息監聽:

-(void)startListenning
{
  if (0 != mMsgPortListenner && CFMessagePortIsValid(mMsgPortListenner))
  {
     
      CFMessagePortInvalidate(mMsgPortListenner);
      
  }
  
    mMsgPortListenner = CFMessagePortCreateLocal(kCFAllocatorDefault,CFSTR(LOCAL_MACH_PORT_NAME),onRecvMessageCallBack, NULL, NULL);
    
    CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mMsgPortListenner, 0);
    
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    
    NSLog(@"start listenning");
}

其中LOCAL_MACH_PORT_NAME的定義為:

#define LOCAL_MACH_PORT_NAME    "com.wangzz.demo"

經過查看源碼發現,CFMessagePort實際上是通過mach port實現的。Mach port是iOS系統提供的基於端口的輸入源,可用於線程或進程間通訊。而Runloop支持的輸入源類型中就包括基於端口的輸入源,因此可以使用Runloop做為CFMessagePort端口源事件的監聽者。

上述代碼有幾點需要說明:

  • 通過CFMessagePortCreateLocal可以創建一個本地CFMessagePortRef對象

  • CFMessagePort對象是靠一個字符串來唯一標識的,這一點非常重要,在這裡字符串是由宏LOCAL_MACH_PORT_NAME定義的;

  • 創建CFMessagePort對象的同時設置了端口源事件的回調函數onRecvMessageCallBack,用於處理端口源事件;

  • 將創建的對象作為輸入源添加到Runloop中,從而實現對端口源事件的監聽,當Runloop收到對應的端口源事件時,會調用上一步中指定的回調方法;

1.2 實現回調方法

回調函數為CFMessagePortCallBack類型,其定義部分為:

typedef CFDataRef (*CFMessagePortCallBack) (
   CFMessagePortRef local,
   
   SInt32 msgid,
   
   CFDataRef data,
   
   void *info
);

各個參數的含義為:

  • CFMessagePortRef local

當前接收消息的CFMessagePortRef對象。

  • SInt32 msgid

這個字段非常有用,用於標識消息。如果通信雙方進程約定號每個msgid對應的數據結構,即可實現較為復雜的通信。

  • CFDataRef data

通信的真正數據部分。

  • void *info

為使用CFMessagePortCreateLocal方法創建port端口時指定的CFMessagePortContext對象的info字段,通常為空。

該回調方法可以返回一個CFDataRef類型的數據給port消息的發送者,以實現有效的雙方通信,這一點也非常重要。

我的回調函數onRecvMessageCallBack的實現:

CFDataRef onRecvMessageCallBack(CFMessagePortRef local,SInt32 msgid,CFDataRef cfData, void*info)
{
    NSLog(@"onRecvMessageCallBack is called");
    
    NSString *strData = nil;
    
    if (cfData)
    {
          const UInt8  * recvedMsg = CFDataGetBytePtr(cfData);
          
      strData = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding];
      
        /**
         
         實現數據解析操作
         
         **/
         
        NSLog(@"receive message:%@",strData);
    }
    //為了測試,生成返回數據
    NSString *returnString = [NSString stringWithFormat:@"i have receive:%@",strData];
    
    const char* cStr = [returnString UTF8String];
    
    NSUInteger ulen = [returnString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  
    CFDataRef sgReturn = CFDataCreate(NULL, (UInt8 *)cStr, ulen);
    
    return sgReturn;
}

該方法實現的較為簡單,解析約定的數據(測試代碼中約定傳送的是string),為了測試,同時生成一個CFDataRef數據返回給port消息的發送者。

1.3 取消端口監聽

可以通過如下方式取消對port端口的監聽:

- (void)endLisenning
{
    CFMessagePortInvalidate(mMsgPortListenner);
    
    CFRelease(mMsgPortListenner);
}

CFMessagePortInvalidate會停止port消息的發送和接收操作,而只有調用了CFRelease,CFMessagePortRef對象才真正的被釋放掉。

2、消息發送者

發送部分代碼如下:

-(NSString *)sendMessageToDameonWith:(id)msgInfo msgID:(NSInteger)msgid
{
    // 生成Remote port
    CFMessagePortRef bRemote = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR(MACH_PORT_REMOTE));
    
    if (nil == bRemote) {
    
        NSLog(@"bRemote create failed");
        
        return nil;
    }
    
    // 構建發送數據(string)
    NSString    *msg = [NSString stringWithFormat:@"%@",msgInfo];
    
    NSLog(@"send msg is :%@",msg);
    
    const char *message = [msg UTF8String];
    
    CFDataRef data,recvData = nil;
    
    data = CFDataCreate(NULL, (UInt8 *)message, strlen(message));
    
    // 執行發送操作
    CFMessagePortSendRequest(bRemote, msgid, data, 0, 100 , kCFRunLoopDefaultMode, &recvData);
    
    if (nil == recvData) {
    
        NSLog(@"recvData date is nil.");
        
        CFRelease(data);
        
        CFMessagePortInvalidate(bRemote);
        
        CFRelease(bRemote);
        
        return nil;
    }
    // 解析返回數據
    const UInt8  * recvedMsg = CFDataGetBytePtr(recvData);
    
    if (nil == recvedMsg) {
    
        NSLog(@"receive date err.");
        
        CFRelease(data);
        
        CFMessagePortInvalidate(bRemote);
        
        CFRelease(bRemote);
        
        return nil;
    }
    NSString    *strMsg = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@",strMsg);
    
    CFRelease(data);
    
    CFMessagePortInvalidate(bRemote);
    
    CFRelease(bRemote);
    
    CFRelease(recvData);
    
    return strMsg;
}

其中MACH_PORT_REMOTE的定義為:

#define MACH_PORT_REMOTE    "com.wangzz.demo"

發送消息時要相對簡單,首先通過CFMessagePortCreateRemote生成一個Remote的CFMessagePortRef,這裡需要注意的是CFMessagePortCreateRemote時傳入的字符串唯一標識MACH_PORT_REMOTE必須和消息接收者創建local的CFMessagePortRef時使用的字符串唯一標識是同一個!

通過查看源碼發現,CFMessagePortCreateRemote會根據MACH_PORT_REMOTE定義的字符串為唯一標識獲取消息接收者通過CFMessagePortCreateLocal使用相同字符串創建的底層mach port端口,從而實現向消息接收者發送信息。

如果消息接收者還沒有創建或者通過CFMessagePortCreateLocal創建local端口失敗時,想要通過CFMessagePortCreateRemote去創建remote端口肯定是失敗的。

說明

  • 很遺憾的是,在iOS7及以後系統中,CFMessagePort的通信機制不再可用。

在使用CFMessagePortCreateLocal/CFMessagePortCreateRemote創建CFMessagePortRef對象時會失敗,官方文檔中是這麼說的:

This method is not available on iOS 7 and later—it will return NULL and log a sandbox violation in syslog. See Concurrency Programming Guide for possible replacement technologies.
  • CFMessagePort只能用於本地進程通信。

  • CFMessagePort是基於mach port端口的通信方式,不但可以用於進程通信,也可以用於線程間通信,只是線程間通信有了GCD和Cocoa提供的原生方法,已經能很方便的實現了,沒必要再使用CFMessagePort。

  • 進程通信使用場景

iOS系統多任務機制,使得進程間通信基本都只能用於越獄開發。常用的場景是前端有一個UI程序用於界面展示,後端有一個daemo精靈程序用於任務處理。

demo工程

特地做了了個demo工程,以便更好地演示CFMessagePort的使用,可以到CSDN下載。

為了模擬進程間通信場景,我將消息接收進程CFMessagePortReceive做成了能夠後台播放音樂的程序,以便其切到後台後能繼續存活。

由於CFMessagePort不再支持iOS7及以後系統,本demo實在iOS6系統上測試的。

demo使用方式:

  • CFMessagePortReceive啟動後,點擊Start Listenning創建CFMessagePort接口並開始監聽port消息,然後將CFMessagePortReceive切到後台;

  • 啟動CFMessagePortSend程序,在輸入框中寫入內容,點擊發送按鈕即可和CFMessagePortReceive通信。

  • MessagePort通信過程中會有日志輸出,可以使用以下方式查看日志:

1.真機

選擇:Xcode->Window->Organizer->Devices,然後選中窗口左側當前設備的Console窗口查看。

2.模擬器

選擇:模擬器->調試->打開系統日志,或者直接使用快捷鍵?/直接打開系統控制台查看日志。

參考文檔

  • CF-855.14

  • Threading Programming Guide

  • CFMessagePort Reference


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved