点到点通讯计算,OC音讯发送

课程介绍:

在上一篇中我们介绍了 mpi4py 中组合发送接收通信方法,至此我们就对 mpi4py 中提供的各种点到点通信方法都做了一个简略的介绍,并给出了简短的使用例程。下面我们对点到点通信做一个小结。

序言

因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。

RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

Objctive-C Runtime是个运行时的库,由C和汇编实现。通过Runtime封装的C结构体和函数可以在程序运行时创建、检查和修改类以及对象及其方法,甚至可以替换或交换方法的实现。

在OOP术语中,消息传递是指一种在对象之间发送和接收消息的通信模式。

通过前面的介绍可知,点到点通信有各种不同的模式,可分为阻塞通信,非重复非阻塞通信,可重复非阻塞通信,其中每一类还可分为标准、缓冲、就绪和同步模式。要理解各种模式通信的行为,关键是弄清楚各个模式对缓冲使用的方式。简言之,各个模式使用缓冲的特点可总结为:标准的 Send 实际利用了 MPI 环境提供的默认缓冲区;缓冲的 Bsend 实际相当于将 MPI 环境提供的缓冲区放在用户空间管理;就绪的 Rsend 实际相当于不要缓冲区,但发送端不能提前等待;同步的 Ssend 实际也相当于不要缓冲区,但允许等待。异步方式下各个模式工作原理也类似,只不过可将其理解为 MPI 环境会另起一个线程在后台做实际的消息传输,通过 MPI_Wait*,MPI_Test* 等机制与 MPI 进程的主线程进行通信和同步。

本文参考iOS Runtime 几种基本用法简记,感谢isabadguy作者。

图片 1

在Objective-C中,消息传递用于在调用类和类实例的方法,即接收者接收需要执行的消息。

点到点通信特别需要注意的一件事就是预防死锁,导致死锁的原因有很多,其中最典型的是缓冲区争夺导致的死锁,比如下面这个例子:

下面记录一下关于Runtime的一些基本用法

这次的练习是做一个Chrome的扩展,分享一下入门开发过程。因为在消息传递那块纠结了特别久,所以我会重点总结消息传递那块。

运行时原理探讨----OC消息发送

# Send_Recv_small.py

import numpy as np
from mpi4py import MPI


comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()

other = 1 if rank == 0 else 0

count = 10
# count = 1024 # will lead to dead lock
send_buf = np.arange(count, dtype='i')
recv_buf = np.empty(count, dtype='i')

print 'process %d trying sending...' % rank
comm.Send(send_buf, dest=other, tag=12)
print 'process %d trying receiving...' % rank
comm.Recv(recv_buf, source=other, tag=12)
print 'process %d done.' % rank
1. 消息机制

在OOP术语中,消息传递是指一种在对象之间发送和接收消息的通信模式。在Objective-C中,消息传递用于在调用类和类实例的方法,即接收者接收需要执行的消息。

  • 这次做这个插件的功能很简单,就是点击按钮后可以对当前网页的模块进行选择隐藏。
  • 做这个插件一方面是练习实例,还有一方面是,有的时候查资料啊,边上总有很多花花绿绿动来动去的小广告!
  • 很烦有木有,还根本不能关闭!就算有关闭按钮,点击了竟然还跳转到广告页面了(゚Д゚≡゚Д゚)
  • 所以就想做个小插件,让自己可以选择隐藏这些不想看的模块。

iOS开发之Runtime运行时原理探讨----OC消息发送机制

运行结果如下:

使用案例
// 通过类名获取类
Class catClass = objc_getClass("Cat"); 

//注意Class实际上也是对象,所以同样能够接受消息,向Class发送alloc消息
Cat *cat = objc_msgSend(catClass, @selector(alloc)); 

//发送init消息给Cat实例cat
cat = objc_msgSend(cat, @selector(init)); 

//发送eat消息给cat,即调用eat方法
objc_msgSend(cat, @selector(eat));

//汇总消息传递过程
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Cat"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));

配置文件

$ mpiexec -n 2 python Send_Recv_small.py
process 0 trying sending...
process 0 trying receiving...
process 0 done.
process 1 trying sending...
process 1 trying receiving...
process 1 done.
2. 方法交换

Objective-C 提供了一下API用于动态替换类方法或者实例方法的实现:

  • class_replaceMethod 替换类方法的定义
  • method_exchangeImplementations 交换两个方法的实现(具体使用案例如下)
  • method_setImplementation 设置一个方法的实现

注:class_replaceMethod 试图替换一个不存在的方法时候,会调用class_addMethod为该类增加一个新方法

每一个扩展都有一个JSON格式的manifest文件,叫manifest.json。

上面这个例子中两个进程同时向对方发送且从对方接收消息,但因为发送和接收的消息比较小,并未超出 MPI 环境提供的默认缓冲区容量,所以能够顺利执行。现在我们试着将上例中的 count 改成 1024(或者更大,依赖于你的 MPI 环境配置),则运行结果如下:

使用案例
//Cat.m
  (void) load {
    Method eatMethod = class_getInstanceMethod(self, @selector(eat));
        Method shirtMethod = class_getInstanceMethod(self, @selector(shirt));

    method_exchangeImplementations(eatMethod, shirtMethod);
}

- (void) eat {
    NSLog(@"cat eat....");
}

- (void) shirt {
    NSLog(@"cat shirt....");
}

所以第一步我们将创建一个manifest.json文件。如下:

$ mpiexec -n 2 python Send_Recv_small.py
process 0 trying sending...
process 1 trying sending...
3. 动态加载方法

当调用一个未实现的方法,或者说发送未知的消息给接收者时候,消息的接受者会调用resolveInstanceMethod

{      "manifest_version": 2, //固定的     "name": "Cissy's First Extension", //插件名称     "version": "1.0", //插件使用的版本     "description": "The first extension that CC made.", //插件的描述     "browser_action": { //插件加载后生成图标         "default_icon": "cc.gif",//图标的图片         "default_title": "Hello CC", //鼠标移到图标显示的文字          "default_popup": "popup.html" //单击图标执行的文件     },      "permissions": [ //允许插件访问的url         "http://*/",          "bookmarks",          "tabs",          "history"      ],      "background":{//background script即插件运行的环境         "page":"background.html"         // "scripts": ["js/jquery-1.9.1.min.js","js/background.js"]//数组.chrome会在扩展启动时自动创建一个包含所有指定脚本的页面     },       "content_scripts": [{  //对页面内容进行操作的脚本          "matches": ["http://*/*","https://*/*"],  //满足什么条件执行该插件           "js": ["js/jquery-1.9.1.min.js", "js/js.js"],             "run_at": "document_start",  //在document加载时执行该脚本     }]  } 

两个进程都阻塞在 Send 处无法前进,原因是两者的发送操作都先于接收操作启动,当发送数据量超过 MPI 环境提供的默认缓冲区空间大小时,每个进程都要等待对方启动接收动作把这个“过量”的数据直接取走。但是因为使用的是阻塞发送,在没有完成接收之前,双方的发送函数都不会返回,因而接收动作都得不到执行,于是两个进程都因等待接收而阻塞在发送步骤上,程序出现了死锁。

使用案例
// Cat.m

//An Objective-C method is simply a C function that take at least two arguments—self and _cmd. 
void run (id self, SEL _cmd, NSNumber *number) {
    NSLog(@"run for %@", number);
}

//收到run:消息时候,为该类添加一个方法实现
  (BOOL) resolveInstanceMethod:(SEL)sel {
    if(sel == NSSelectorFromString(@"run:")) {
        class_addMethod(self, @selector(run:), run, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

//另外针对类方法的为 resolveClassMethod

每个字段的信息我都用注释标明了,接下来就重点说下一些重要字段。

对于这种类型的死锁,解决办法有如下几个:

4. 消息转发

需要注意:

  1. 确保一次发送数据量小于 MPI 环境所能提供缓冲区容量;
  2. 利用 MPI 环境消息发送接收的顺序性约束,调整语句的执行顺序;
  3. 使用缓冲发送模式;
  4. 使用非阻塞通信;
  5. 利用组合发送接收。
使用案例
//  第一步,消息接收者没有找到对应的方法时候,会先调用此方法,可在此方法实现中动态添加新的方法
//  返回YES表示相应selector的实现已经被找到,或者添加新方法到了类中,否则返回NO
  (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;
}

//  第二步, 如果第一步的返回NO或者直接返回了YES而没有添加方法,该方法被调用
//  在这个方法中,我们可以指定一个可以返回一个可以响应该方法的对象, 注意如果返回self就会死循环
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

//  第三步, 如果forwardingTargetForSelector:返回了nil,则该方法会被调用,系统会询问我们要一个合法的『类型编码(Type Encoding)』
//  若返回 nil,则不会进入下一步,而是无法处理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

// 当实现了此方法后,-doesNotRecognizeSelector: 将不会被调用
// 在这里进行消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 在这里可以改变方法选择器
    [anInvocation setSelector:@selector(unknown)];
    // 改变方法选择器后,需要指定消息的接收者
    [anInvocation invokeWithTarget:self];
}

- (void)unknown {
    NSLog(@"unkown method.......");
}

// 如果没有实现消息转发 forwardInvocation  则调用此方法
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"unresolved method :%@", NSStringFromSelector(aSelector));
}

chrome不允许扩展中的HTML页面内直接内嵌js脚本,而要求所有的脚本都作为外部src来引入

我们不一一列举这些解决方案,只给出其中的一个例子,即调整语句的执行顺序来防止死锁,如下:

5. 动态关联属性

对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化,所以无法在运行时动态给对象增加成员变量。相对的,对象的方法定义都保存在类的可变区域中。

如下图所示为Class 的描述信息,其中methodList为可访问类中定义的方法的指针的指针,通过修改该指针所指向的指针的值,我们可以实现为类动态增加方法实现。

因此,我们可以实现动态为一个类增加成员方法,但是却不能直接为类增加成员变量,这就是category的实现原理。

browser_action

# Send_Recv_large.py

import numpy as np
from mpi4py import MPI


comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()

other = 1 if rank == 0 else 0

count = 1024
send_buf = np.arange(count, dtype='i')
recv_buf = np.empty(count, dtype='i')

if rank == 0:
    print 'process %d trying sending...' % rank
    comm.Send(send_buf, dest=other, tag=12)
    print 'process %d trying receiving...' % rank
    comm.Recv(recv_buf, source=other, tag=12)
    print 'process %d done.' % rank
elif rank == 1:
    print 'process %d trying receiving...' % rank
    comm.Recv(recv_buf, source=other, tag=12)
    print 'process %d trying sending...' % rank
    comm.Send(send_buf, dest=other, tag=12)
    print 'process %d done.' % rank
原理
//<objc/runtime.h>

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
  1. 如果browser action拥有一个popup(即设置了default_popup),popup 可以包含任意你想要的HTML内容,并且会自适应大小。popup 会在用户点击图标后出现。若没有设置default_popup,将执行chrome.browserAction.onClicked的内容,若没有设置,就什么都不执行了。也就是如果设置了default_popup,就不会执行chrome.browserAction.onClicked了。
  2. 和browser_action对应的还有一个page_action,区别在于:Browser Action对在浏览器中加载的所有网页都生效;Page Action针对特定的网页生效。一个Extension最多可以有一个Browser Action或者Page Action。这里选用Browser Action。

运行结果如下:

使用案例
//Cat Extend.h

@interface Cat (extend)

@property(nonatomic, copy) NSString *name;

@end

//Cat Extend.m

@implementation Cat (extend)

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name{
    return objc_getAssociatedObject(self, "name");
}
@end

background

$ mpiexec -n 2 python Send_Recv_large.py
process 0 trying sending...
process 0 trying receiving...
process 0 done.
process 1 trying receiving...
process 1 trying sending...
process 1 done.
6. 字典转模型应用

通过Class的结构体内容,可以看到ivars指针指向包含了类中成员变量的结构体,通过它可以得到类中定义的成员变量,而Objective-C中提供了相应的API方法: class_copyIvarList

  1. background是插件的运行环境。若设置了scripts字段,浏览器的扩展系统会自动根据scripts字段指定的所有js文件自动生成背景页。也可以直接page字段,指定背景页。两者只能设置一个。
  2. 一般情况下,我们会让将扩展的主要逻辑都放在 background 中比较便于管理。其它页面可以通过消息传递的机制与 background 进行通讯。理论上 content script 与 popup 之间也可以传递消息,但不建议这么做。

经过语句的调整,每个进程的发送操作都可优先匹配到对方的接收动作,待消息传输完毕后才进行下一次通信,因此可消除由缓冲区竞争所导致的死锁。

原理
//<objc/runtime.h>

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

消息传递

MPI 对消息传递的执行顺序有一定的规定,但 MPI 环境并不提供保证“公平性”的措施,具体如下:

使用案例
//Cat.h

@interface Cat : NSObject

@property(nonatomic, copy) NSString *cid;

@property(nonatomic, copy) NSString *age;

  (instancetype)modelWithDict:(NSDictionary *)dict;

@end

//Cat.m
@implementation Cat

  (instancetype)modelWithDict:(NSDictionary *)dict{
    id model = [[self alloc] init];
    unsigned int count = 0;

    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0 ; i < count; i  ) {
        Ivar ivar = ivars[i];

        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

        //这里注意,拿到的成员变量名为_cid,_age
        ivarName = [ivarName substringFromIndex:1];
        id value = dict[ivarName];

        [model setValue:value forKeyPath:ivarName];
    }

    return model;
}

@end 

由于插件的js运行环境有区别,所以消息传递机制是一个重要内容。

  • 顺序规定
    • MPI 环境保证了消息传递的顺序,即先后发送的消息在接收端不会发生顺序颠倒。
    • 如果发送进程向同一个目标进程先后发送了 m1 和 m2 两个消息,即使 m1 和 m2 都与同一个接收函数匹配,接收进程也只能先接收 m1 再接收 m2。
    • 如果接收进程先后启动两个接收动作 r1 和 r2,都尝试接收同一个消息,则 r1 必须在 r2 之前接收到消息。
    • 但对使用了多线程的 MPI 环境而言,上述顺序规定不一定成立。
  • 公平性
    • MPI 环境并不提供保证“公平性”的措施,用户自己必须在程序设计中解决“操作饥饿”问题,例如进程 0 和进程 1 同时向进程 2 发送消息,并与其中的同一个接收操作相匹配,则只有一个进程的发送动作能够完成。

一次简单的请求

以上简要总结了 mpi4py 中的点到点通信,在下一篇中我们将介绍组与通信子的基本概念。

如果仅需要给你自己的扩展的另外一部分发送一个消息(可选的是否得到答复),你可以简单地使用chrome.extension.sendRequest()或者chrome.tabs.sendRequest()方法。这个方法可以帮助你传送一次JSON序列化消息从content script到扩展,反之亦然。如果接受消息的一方存在的话,可选的回调参数允许处理传回来的消息。

sendRequest() 是Chrome33之前的API,33之后还是使用sendMessage()吧。

1.内容脚本发送消息到扩展程序

chrome.extension.sendMessage({hello: "Cissy"}, function(response) { console.log(response.farewell); }); 

2.扩展程序发送消息到内容脚本

chrome.tabs.sendMessage(tab.id, {hello: "Cissy"}, function(response) { console.log(response.farewell); }); 

3.接收消息

chrome.extension.sendMessage()向扩展内的其它监听者发送一条消息。此消息发送后会触发扩展内每个页面的chrome.extension.onMessage()事件。

我用的是长时间的保持连接,原理差不多,就是调用接口的区别,所以就不具体介绍这个了 详细的可以看开发文档

长时间的保持连接

background 和 popup

同一个Extension的Extension Page(包括background、popup、tab、infobar、notification)都是运行在同一个进程中的,所以background 和 popup 之间可以直接相互调用对方的方法,不需要消息传递。

1.popup调用background中变量或方法

var bg = chrome.extension.getBackgroundPage();//获取background页面 console.log(bg.a);//调用background的变量或方法。 

2.background调用popup中变量或方法

var pop = chrome.extension.getViews({type:'popup'});//获取popup页面 console.log(pop[0].b);//调用第一个popup的变量或方法。 

这里要注意一定要指明type,如果没有指定,则获取Background Page之外的所有Extension Page的window对象 。(。•ˇ‸ˇ•。)这个地方真的纠结好久。然后就是background是一个运行在扩展进程中的HTML页面。它在你的扩展的整个生命周期都存在,而popup是在你点击了图标之后才存在,所以,在获取popup变量时,请确认popup已打开。

background 和 content

持续长时间的保持会话需要在content script和扩展建立一个长时间存在的通道。当建立连接,两端都有一个Port 对象通过这个连接发送和接收消息。

1.内容脚本发送消息到扩展程序

var bac = chrome.extension.connect({name: "ConToBg"});//建立通道,并给通道命名 bac.postMessage({hello: "Cissy"});//利用通道发送一条消息。 

2.扩展程序发送消息到内容脚本扩展程序发送消息到内容脚本与前面类似,但需要指定哪个标签需要连接,(获取tab.id的方法我试了很多,但只有下面这个有效,所以如果大家有什么其他有效的方法,求求求分享!!)

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {//获取当前Tab var cab = chrome.tabs.connect(tabId, {name: "BgToCon"});//建立通道,指定tabId,并命名 cab.postMessage({ hello: "Cissy"});//利用通道发送一条消息。 } 

3.接收消息为了处理正在等待的连接,需要用chrome.extension.onConnect 事件监听器,对于content script或者扩展页面,这个方法都是一样的

chrome.extension.onConnect.addListener(function(bac) {//监听是否连接,bac为Port对象 bac.onMessage.addListener(function(msg) {//监听是否收到消息,msg为消息对象 console.log(msg.hello); }) }) 

安装调试

设置 —>拓展程序—>加载已解压的拓展程序—>选择文件就行了,记得要打开开发者模式哦

总结

插件功能的开发我就不写了,实现起来比较简单,这篇文章就当是chrome拓展开发的学习笔记了,不足之处还望指出,最后还是放一下插件源码吧,写的比较乱很多没用到的代码也没删掉,因为是练习中用到的。嗯嗯好了去吃饭。

【编辑推荐】

本文由星彩网app下载发布于计算机编程,转载请注明出处:点到点通讯计算,OC音讯发送

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。