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. {
  3. if (0 != mMsgPortListenner && CFMessagePortIsValid(mMsgPortListenner))
  4. {
  5. CFMessagePortInvalidate(mMsgPortListenner);
  6. }
  7. mMsgPortListenner = CFMessagePortCreateLocal(kCFAllocatorDefault,CFSTR(LOCAL_MACH_PORT_NAME),onRecvMessageCallBack, NULL, NULL);
  8. CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mMsgPortListenner, 0);
  9. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  10. NSLog(@"start listenning");
  11. }

其中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. {
  3. NSLog(@"onRecvMessageCallBack is called");
  4. NSString *strData = nil;
  5. if (cfData)
  6. {
  7. const UInt8  * recvedMsg = CFDataGetBytePtr(cfData);
  8. strData = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding];
  9. /**
  10. 实现数据解析操作
  11. **/
  12. NSLog(@"receive message:%@",strData);
  13. }
  14. //为了测试,生成返回数据
  15. NSString *returnString = [NSString stringWithFormat:@"i have receive:%@",strData];
  16. const char* cStr = [returnString UTF8String];
  17. NSUInteger ulen = [returnString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  18. CFDataRef sgReturn = CFDataCreate(NULL, (UInt8 *)cStr, ulen);
  19. return sgReturn;
  20. }

该方法实现的较为简单,解析约定的数据(测试代码中约定传送的是string),为了测试,同时生成一个CFDataRef数据返回给port消息的发送者。

1.3 取消端口监听

可以通过如下方式取消对port端口的监听:

  1. - (void)endLisenning
  2. {
  3. CFMessagePortInvalidate(mMsgPortListenner);
  4. CFRelease(mMsgPortListenner);
  5. }

CFMessagePortInvalidate会停止port消息的发送和接收操作,而只有调用了CFRelease,CFMessagePortRef对象才真正的被释放掉。

2、消息发送者

发送部分代码如下:

  1. -(NSString *)sendMessageToDameonWith:(id)msgInfo msgID:(NSInteger)msgid
  2. {
  3. // 生成Remote port
  4. CFMessagePortRef bRemote = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR(MACH_PORT_REMOTE));
  5. if (nil == bRemote) {
  6. NSLog(@"bRemote create failed");
  7. return nil;
  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. CFMessagePortSendRequest(bRemote, msgid, data, 0, 100 , kCFRunLoopDefaultMode, &recvData);
  17. if (nil == recvData) {
  18. NSLog(@"recvData date is nil.");
  19. CFRelease(data);
  20. CFMessagePortInvalidate(bRemote);
  21. CFRelease(bRemote);
  22. return nil;
  23. }
  24. // 解析返回数据
  25. const UInt8  * recvedMsg = CFDataGetBytePtr(recvData);
  26. if (nil == recvedMsg) {
  27. NSLog(@"receive date err.");
  28. CFRelease(data);
  29. CFMessagePortInvalidate(bRemote);
  30. CFRelease(bRemote);
  31. return nil;
  32. }
  33. NSString    *strMsg = [NSString stringWithCString:(char *)recvedMsg encoding:NSUTF8StringEncoding];
  34. NSLog(@"%@",strMsg);
  35. CFRelease(data);
  36. CFMessagePortInvalidate(bRemote);
  37. CFRelease(bRemote);
  38. CFRelease(recvData);
  39. return strMsg;
  40. }

其中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. 模拟器

选择:模拟器->调试->打开系统日志,或者直接使用快捷键?/直接打开系统控制台查看日志。

参考文档:

CF-855.14

Threading Programming Guide

CFMessagePort Reference

时间: 2024-08-02 11:03:35

iOS进程间通信之CFMessagePort的相关文章

iOS8下bundle路径变更

至少是模拟器目录有变 iOS8下路径变为: /Users/username/Library/Developer/CoreSimulator/Devices/786824FF-6D4C-4D73-884A-696514481F7C/data/Containers/Data/Application/7D5B082E-53D5-4C60-86A0-1F6A0A1B98E3/Library/Caches/... 路径够深的..想必是出于iOS进程间通信的考虑 iOS8之前为: /Users/xiejin

IOS基础面试题

最近离职了,找工作,光会做项目,对基础不熟,今天就总结了一点面试题. 废话不多说,上题吧: 1.objective-c中的数字对象都有哪些,简述它们与基本数据类型的区别是什么. 基本类型和C一样,主要是有int.long.double.float.char.void.bool.对于基本数据类型,不需要使用指针,NSNumber是OC的数字对象,需要考虑内存释放问题.数字类型有:NSInteger.CGFloat.数据对象有NSNumber.对象和变量的差别.可以拆装效果.其他的类型有NSStri

深入浅出--iOS的TCP/IP协议族剖析&&Socket

深入浅出--iOS的TCP/IP协议族剖析&&Socket 简介 该篇文章主要回顾--TCP/IP协议族中的TCP/UDP.HTTP:还有Socket.(--该文很干,酝酿了许久!你能耐心看完吗?) 我在这个文章中,列举了常见的TCP/IP族中的协议,今天主角是--传输层协议. 传输层(Transport Layer)是OSI(七层模型)中最重要.最关键的一层,它负责总体的数据传输和数据控制的一层,传输层提供端到端(应用会在网卡注册一个端口号)的交换数据的机制,检查分组编号与次序.传输层对

iOS多线程开发(三)---Run Loop(四)

四,配置Run Loop源---配置源的过程就是源的创建调用过程 配置过程分为以下几个阶段---定义/创建(一个源)---安装(将输入源安装到所在Run Loop中)---注册(将输入源注册到客户端,协调输入源的客户端)---调用(通知输入源,开始工作) 4-1,定义自定义输入源 创建自定义输入源需要定义以下内容 1)输入源要处理的信息 2)使感兴趣的客户端知道如何和输入源交互的调度例程 3)处理其他任何客户发送请求的例程 4)使输入源失效的取消例程 Figure 3-2 Operating a

iOS异常捕获

文章目录 一. 系统Crash 二. 处理signal 下面是一些信号说明 关键点注意 三. 实战 四. Crash Callstack分析 – 进?一步分析 五. demo地址 六. 参考文献 前言 今天在ios高级群,有朋友问到iOS的异常捕捉的问题,这一块以前也没有研究过,趁此机会研究了一把.并写了一个demo,如有需要可以在文章最下面去下载. 在阅读文章之前,建议大家在阅读完此篇文章后可以阅读漫谈iOS Crash收集框架,了解一下原理. 开发iOS应用,解决Crash问题始终是一个难题

李洪强iOS开发之RunLoop的原理和核心机制

李洪强iOS开发之RunLoop的原理和核心机制 搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研究了RunLoop的原理和特性. RunLoop的定义 当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程.RunLoop就是控制线程生命周期并接收事件进行处理的机制. RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统. Foundation: NSRunLo

iOS闪退捕获

主要内容 一.闪退信息传递过程 二.Unix信号捕获异常 三.NSUncaughtExceptionHandler捕获异常 四.总结 五.参考链接 一.闪退信息传递过程 底层内核产生Mach异常->通过转换发出Unix信号:所以我们可以通过监听Unix信号来获得闪退信息,当然如果通过捕获Mach异常来获取会更准确,毕竟少了一步转换嘛 二.Unix信号捕获异常 1.关于信号 1) SIGHUP  本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一sessio

iOS多线程 && Runloop

一.线程概述 有些程序是一条直线,起点到终点:有些程序是一个圆,不断循环,直到将它切断.直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样:圆如操作系统,一直运行直到你关机.  一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流.Mac和iOS中的程序启动,创建好一个进程的同时, 一个线程便开始运行,这个线程叫主线程.主线程在程序中的地位和其他线程不同,它是其他线程最终的父线程,且所有界面的显示操作即AppKit或 U

iOS开发之RunLoop--转

RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理.之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池.延迟回调.触摸事件.屏幕刷新等功能的. IndexRunLoop 的概念RunLoop 与线程的关系RunLoop 对外的接口RunLoop 的 ModeRunLoop 的内部逻辑RunLoop 的底层实现苹果用 RunLoop 实现的功能AutoreleaseP