• iOS进程间通信之CFMessagePort


    本文转载至 http://www.cocoachina.com/industry/20140606/8701.html

    iOS系统是出了名的封闭,每个应用的活动范围被严格地限制在各自的沙盒中。尽管如此,iOS还是提供了若干进程间通信机制,CFMessagePort就是其中之一。

     
    阅读器

    iOSCFMessagePort

     
    iOS系统是出了名的封闭,每个应用的活动范围被严格地限制在各自的沙盒中。尽管如此,iOS还是提供了若干进程间通信机制,CFMessagePort就是其中之一。
     
    从类名可以看出,CFMessagePort属于Core Foundation层的东西,其实现部分是开源的,代码在可以在苹果的开源代码库中找到。
     
    使用方式
    1、消息接收者
    CFMessagePort端口消息的接收者需要实现以下功能:
     
    1.1 注册监听
    消息接收者需要通过以下方式注册消息监听:
    1. -(void)startListenning 
    2.   if (0 != mMsgPortListenner && CFMessagePortIsValid(mMsgPortListenner)) 
    3.   { 
    4.       CFMessagePortInvalidate(mMsgPortListenner); 
    5.   } 
    6.     mMsgPortListenner = CFMessagePortCreateLocal(kCFAllocatorDefault,CFSTR(LOCAL_MACH_PORT_NAME),onRecvMessageCallBack, NULL, NULL); 
    7.     CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mMsgPortListenner, 0); 
    8.     CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); 
    9.     NSLog(@"start listenning"); 
     
    其中LOCAL_MACH_PORT_NAME的定义为:
    1. #define LOCAL_MACH_PORT_NAME    "com.wangzz.demo" 
     
    经过查看源码发现,CFMessagePort实际上是通过mach port实现的。Mach port是iOS系统提供的基于端口的输入源,可用于线程或进程间通讯。而Runloop支持的输入源类型中就包括基于端口的输入源,因此可以使用Runloop做为CFMessagePort端口源事件的监听者。
     
    上述代码有几点需要说明:
    1. 通过CFMessagePortCreateLocal可以创建一个本地CFMessagePortRef对象
    2. CFMessagePort对象是靠一个字符串来唯一标识的,这一点非常重要,在这里字符串是由宏LOCAL_MACH_PORT_NAME定义的;
    3. 创建CFMessagePort对象的同时设置了端口源事件的回调函数onRecvMessageCallBack,用于处理端口源事件;
    4. 将创建的对象作为输入源添加到Runloop中,从而实现对端口源事件的监听,当Runloop收到对应的端口源事件时,会调用上一步中指定的回调芳芳;
     
    1.2 实现回调方法
    回调函数为CFMessagePortCallBack类型,其定义部分为: 
    1. typedef CFDataRef (*CFMessagePortCallBack) ( 
    2.    CFMessagePortRef local, 
    3.    SInt32 msgid, 
    4.    CFDataRef data, 
    5.    void *info 
    6. ); 
     
    各个参数的含义为:
    CFMessagePortRef local:当前接收消息的CFMessagePortRef对象。
     
    SInt32 msgid:这个字段非常有用,用于标识消息。如果通信双方进程约定号每个msgid对应的数据结构,即可实现较为复杂的通信。
     
    CFDataRef data:通信的真正数据部分。
     
    void *info:为使用CFMessagePortCreateLocal方法创建port端口时指定的CFMessagePortContext对象的info字段,通常为空。
     
    该回调方法可以返回一个CFDataRef类型的数据给port消息的发送者,以实现有效的双方通信,这一点也非常重要。
     
    我的回调函数onRecvMessageCallBack的实现:
    1. CFDataRef onRecvMessageCallBack(CFMessagePortRef local,SInt32 msgid,CFDataRef cfData, void*info) 
    2.     NSLog(@"onRecvMessageCallBack is called"); 
    3.     NSString *strData = nil; 
    4.     if (cfData) 
    5.     { 
    6.           const UInt8  * recvedMsg = CFDataGetBytePtr(cfData); 
    7.       strData = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding]; 
    8.         /** 
    9.           
    10.          实现数据解析操作 
    11.           
    12.          **/ 
    13.  
    14.         NSLog(@"receive message:%@",strData); 
    15.     } 
    16.  
    17.     //为了测试,生成返回数据 
    18.     NSString *returnString = [NSString stringWithFormat:@"i have receive:%@",strData]; 
    19.     const char* cStr = [returnString UTF8String]; 
    20.   NSUInteger ulen = [returnString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 
    21.     CFDataRef sgReturn = CFDataCreate(NULL, (UInt8 *)cStr, ulen); 
    22.  
    23.     return sgReturn; 
     
    该方法实现的较为简单,解析约定的数据(测试代码中约定传送的是string),为了测试,同时生成一个CFDataRef数据返回给port消息的发送者。
     
    1.3 取消端口监听
    可以通过如下方式取消对port端口的监听:
    1. - (void)endLisenning 
    2.     CFMessagePortInvalidate(mMsgPortListenner); 
    3.     CFRelease(mMsgPortListenner); 
     
    CFMessagePortInvalidate会停止port消息的发送和接收操作,而只有调用了CFRelease,CFMessagePortRef对象才真正的被释放掉。
     
    2、消息发送者
    发送部分代码如下:
    1. -(NSString *)sendMessageToDameonWith:(id)msgInfo msgID:(NSInteger)msgid 
    2.     // 生成Remote port 
    3.     CFMessagePortRef bRemote = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR(MACH_PORT_REMOTE)); 
    4.     if (nil == bRemote) { 
    5.         NSLog(@"bRemote create failed"); 
    6.         return nil; 
    7.     } 
    8.  
    9.     // 构建发送数据(string) 
    10.     NSString    *msg = [NSString stringWithFormat:@"%@",msgInfo]; 
    11.     NSLog(@"send msg is :%@",msg); 
    12.     const char *message = [msg UTF8String]; 
    13.     CFDataRef data,recvData = nil; 
    14.     data = CFDataCreate(NULL, (UInt8 *)message, strlen(message)); 
    15.  
    16.     // 执行发送操作 
    17.     CFMessagePortSendRequest(bRemote, msgid, data, 0, 100 , kCFRunLoopDefaultMode, &recvData); 
    18.     if (nil == recvData) { 
    19.         NSLog(@"recvData date is nil."); 
    20.         CFRelease(data); 
    21.         CFMessagePortInvalidate(bRemote); 
    22.         CFRelease(bRemote); 
    23.         return nil; 
    24.     } 
    25.  
    26.     // 解析返回数据 
    27.     const UInt8  * recvedMsg = CFDataGetBytePtr(recvData); 
    28.     if (nil == recvedMsg) { 
    29.         NSLog(@"receive date err."); 
    30.         CFRelease(data); 
    31.         CFMessagePortInvalidate(bRemote); 
    32.         CFRelease(bRemote); 
    33.         return nil; 
    34.     } 
    35.  
    36.     NSString    *strMsg = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding]; 
    37.     NSLog(@"%@",strMsg); 
    38.  
    39.     CFRelease(data); 
    40.     CFMessagePortInvalidate(bRemote); 
    41.     CFRelease(bRemote); 
    42.     CFRelease(recvData); 
    43.  
    44.     return strMsg; 
     
    其中MACH_PORT_REMOTE的定义为:
    1. #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端口肯定是失败的。
     
    说明
    1. 很遗憾的是,在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. 
     
    2. CFMessagePort只能用于本地进程通信。
     
    3. CFMessagePort是基于mach port端口的通信方式,不但可以用于进程通信,也可以用于线程间通信,只是线程间通信有了GCD和Cocoa提供的原生方法,已经能很方便的实现了,没必要再使用CFMessagePort。
     
    4. 进程通信使用场景
     
    iOS系统多任务机制,使得进程间通信基本都只能用于越狱开发。常用的场景是前端有一个UI程序用于界面展示,后端有一个daemo精灵程序用于任务处理。
     
    demo工程
    特地做了了个demo工程,以便更好地演示CFMessagePort的使用,可以到CSDN下载
     
    为了模拟进程间通信场景,我将消息接收进程CFMessagePortReceive做成了能够后台播放音乐的程序,以便其切到后台后能继续存活。
     
    由于CFMessagePort不再支持iOS7及以后系统,本demo实在iOS6系统上测试的。
     
    demo使用方式
    1. CFMessagePortReceive启动后,点击Start Listenning创建CFMessagePort接口并开始监听port消息,然后将CFMessagePortReceive切到后台;
     
    2. 启动CFMessagePortSend程序,在输入框中写入内容,点击发送按钮即可和CFMessagePortReceive通信。
     
    3. MessagePort通信过程中会有日志输出,可以使用以下方式查看日志:
     
    1. 真机
    选择:Xcode->Window->Organizer->Devices,然后选中窗口左侧当前设备的Console窗口查看。
     
    2. 模拟器
    选择:模拟器->调试->打开系统日志,或者直接使用快捷键?/直接打开系统控制台查看日志。
     
    参考文档:
     
     
     
  • 相关阅读:
    3.3 x86指令简介
    8.2 DRAM和SRAM
    7.2 流水线的优化
    8.3 主存的工作原理
    matlab练习程序(ACF/PACF系数计算)
    Github——常用命令介绍
    Mac上永久修改默认Python版本
    FIO – IO压力测试工具 规格严格
    postgresqlco.nf 规格严格
    pg9.6单纯写性能探索 规格严格
  • 原文地址:https://www.cnblogs.com/Camier-myNiuer/p/4098402.html
Copyright © 2020-2023  润新知