Gowhich

Durban's Blog

随着人们对Web即时应用需求的不断上升,Server Push(推送)技术在聊天、消息提醒尤其是社交网络等方面开始兴起,成为实时应用的数据流核心。这篇日志试图探讨的便是各种适合于PHP的Push的实现方式以及其优劣。

什么是Server Push

想象在聊天应用中,如果使用传统的ajax来承担消息的传入,那么一般是通过每隔一定时间拉取一次信息的方式实现,但是其实这种方式有大量查询是浪费的。聊天等Web应用更需要服务器在特定时间来主动告知前端有新的消息(Push),而不是前端每时每刻问服务器:“来消息了吗?”(Pull)。这也正是为什么这个技术常被叫做反向ajax。

其他别名:Comet,反向Ajax

如何实现Push

其实所谓的推送技术也没有多么复杂,目前从大类上有3种,一种仍然建立在ajax基础上,还有一种建立在框架基础上,最后一种抛弃了传统的HTTP协议,使用Flash或者HTML5的WebSockets技术。接下来将对这三种类别产生的不同的方式进行探讨。

  1. Ajax 长轮询

Ajax长轮询从本质上来说仍然是一种pull,但是实时性较高,无用请求减少很多,是一种不错的Push实现方案。不过它只减少了网络上的无谓消耗。

核心: 客户端发起一个ajax请求,服务端将请求搁置(pending)或者说挂起,直到到了超时时间(timeout)或需要推送时返回;客户端则等待ajax返回后处理数据,再发起下一个ajax请求。

优点: 兼容性较高,实现简单

缺点: 对于php这种语言来说,如果要做到实时,那么服务端就要承受大得多的压力,因为搁置到什么时候往往是不确定的,这就要php脚本每次搁置都进行一个while循环。
当然,如果服务器刷新每秒级,那尚可接受,只是实时性上退化了。

注意: 浏览器有连接数限制。我得出的结论是如果当前页面上有一个ajax请求处于等待返回状态,那么其他ajax请求都会被搁置(Chrome, Firefox已测)。似乎跟页面标记有关,一个规范的HTML可以同时有多个请求。如果页面有一般ajax需求怎么办?解决方法是开个框架,框架中使在另一个域名下进行Comet长轮询,需要注意跨域问题。

PHP实现:Jquery+php实现comet

相关:Ajax跨域和js跨域解决方案

  1. Frame 长连接

受到ajax启发,出现了框架下的长连接。

核心: Frame中发起一个普通请求,服务器将其搁置;需要推送时输出直接执行
脚本,然后继续保持连接。如果担心超时问题可以改成框架论询。

优点: 与1一样具有高兼容特性

缺点: 最大的问题是如果框架在载入,那么浏览器就好一直显示“载入中”,这就弱爆了(解决方法参见文末的相关阅读资源)。同样服务器也要能hold住大量循环……另外,是否有同域连接限制没测试。

  1. Flash/HTML5 WebSockets

用flash来发起WebSockets,秒杀前面一切问题。

优点: 标准化, RealTime, Push

缺点: 服务器需要能应对WebSockets;还有如果既没有Flash又不支持HTML5的怎么办?

PHP实现:Start Using HTML5 WebSockets Today

  1. 使用兼容封装层(socket.io)

以上每种方法都有优劣,那么终极解决方案便是合在一起!能WebSockets时候就WebSockets,不支持HTML5特性就退化到Flash,没有Flash则退化到Ajax长轮询。这也是我的Rainbowfish所采用的方式。

优点: 高度封装,编写非常容易,几乎不需要关心如何去实现的。实时,超低负载,高并发。

缺点: 其实算不上缺点,socket.io的服务器端要求是node.js,而不是php。

个人看法: 如果你是独立主机,能运行程序,那么socket.io配合node.js是个非常高效的选择。为什么呢?因为它还可以避免php的服务端高负载。

Rainbowfish的消息系统通过这种方式实现: 所有客户端都通过socket.io挂在nodejs服务器上(注意: 只是挂着,不需要任何循环,因为它是事件驱动的);需要推送消息了,服务器就与nodejs通信(比如访问某个地址来实现),告诉它推送什么消息到哪里;nodejs收到推送信号后,则通过socket.io实时传输数据给浏览器。这个其实也是一条单向的路,因为nodejs服务器不具备与php通信的能力,实际上也不需要,网页上直接连php就可以了。

结束语

事实上,第一个方法(Ajax Long Pull)是一个不错的方法,只是如果使用php完成的话服务器负载上有点大,但这其实是通病;而最后列举的socket.io方案完全避免了这个问题,因为它属于另一种架构,并且这种组合也可以配合几乎所有的脚本语言实现push。

对于实时性要求非常高的应用,或许使用php实现实时部分并不是一个好的选择,将会面临非常大的服务器负载(可以通过编写支持等待事件的扩展来解决这个问题);如果只是消息提示等,则可以调整服务器上刷新的间隔降低到秒的级别,负载尚可接受。不过无论哪种用途,配合那些非阻塞语言或许才是最好的选择。

相关阅读

How to implement COMET with PHP

Start Using HTML5 WebSockets Today

Comet(Wikipedia)

Ajax跨域和js跨域解决方案

Jquery+php实现comet

今天看了一下关于IOS的Push的机制,对于初学者我看了一下,顺便在自己的博客中进行记录一下

Push机制的介绍

iPhone 对于应用程序在后台运行有诸多限制(除非你越狱)。因此,当用户切换到其他程序后,原先的程序无法保持运行状态。对于那些需要保持持续连接状态的应用程序(比如社区网络应用),将不能收到实时的信息。

为解决这一限制,苹果推出了APNs(苹果推送通知服务)。APNs 允许设备与苹果的推送通知服务器保持常连接状态。当你想发送一个推送通知给某个用户的iPhone上的应用程序时,你可以使用 APNs 发送一个推送消息给目标设备上已安装的某个应用程序。

Push机制的类型:

四种:徽章、提示框、声音和横幅

Push机制的4个组件

Provider

APNS

iPhone设备

Client App

其中APNS(Apple Push Notification Service)是由苹果提供的消息推送服务中心,所有的消息都经由这里转发给相应的设备。

Provider和Device与APNS进行通信时,时建立在SSL/TLS安全连接之上的。如下面的两个图所示(TSL的建立过程)

Provider与APNS之间的通信还需要—–DeviceToken

DeviceToken是设备令牌,有APNS生成,并返回给设备,再由设备提供给Provider。

Push机制的使用

Push通知的使用可以分为以下几个步骤。其中前4个步骤相当于准备工作,也非常重要。

接下来,我们就一个步骤一个步骤的进行讲解。

a 证书请求:

证书请求制作的目的是为了获取ssl证书。通过在“钥匙串访问”程序中来创建证书请求,具体的操作过程,建议大家看看本文开头给出的视频连接。

b 创建appid

appid在制作ssl证书和profile文件会用到。具体创建过程看如下图(建议大家看看本文开头给出的视频连接。)

登录网站https://developer.apple.com/devcenter/ios/

c 生成ssl证书

ssl证书的一个主要作用就是运行程序接收从apns发送过来的消息

具体生成过程,看下面的图,当然,这里也建议观看本文开头给出的视频

d 创建profile文件

profile文件的主要作用是运行程序可以被安装在手机上(push测试需要在真机上进行)

Push故障排除

Demo示例代码打包在如下文件中

PushMeBaby.rar

PushClient.rar

apn.rar

参考地址:http://www.devdiv.com/iOS_iPhone-_ios_push_-thread-130543-1-1.html

DevDiv视频地址:http://www.devdiv.com/article-4042-1.html

Youku视频地址:http://v.youku.com/v_show/id_XNDI5ODExNzMy.html

服务器端步骤

生成app在服务端需要的许可证

1)进入Provisioning Portal, 下载Certificates在development下的证书。

2) 找到需要测试的app id,然后enable它在development下的Apple Push Notification service: Development Push SSL Certificate。需要输入1)中的签名证书才可以生成一个aps_developer_identity.cer.

  1. 双击aps_developer_identity.cer,会打开系统的key chain.

在My certificates下找到Apple Development Push Services。需要为certificate和它之下的private key各自export出一个.p12文件。(会出现设置密码过程)

4)需要将上面的2个.p12文件转成.pem格式:

1
2
openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12
openssl pkcs12 -nocerts -out key.pem -in key.p12

5)如果需要对 key不进行加密:

1
openssl rsa -in key.pem -out key.unencrypted.pem

6)然后就可以 合并两个.pem文件, 这个ck.pem就是服务端需要的证书了。

1
cat cert.pem key.unencrypted.pem > ck.pem

4个pem,另外加上php文件,打包放到服务器上

apn_pem.zip

user: handholder crakced you

code:DKFTCCXCMWOX35TZKPRN5YNR2NYUTJJAY52VHWKX2H5URTUB72KW-RCRTQJCC2ZZV5BTHSKCNQXTAOSGSLN46V3E7NIJKDBLRDY37NRVD-IXQWZ5SVPHBN67JZDZTTAQ6MS4ROVXRCGDZGKGE2VGOGHEYMPRGY-O5Y243GTBKPZLPP55QSBIHR6MDEUBMVQT4Q3SESPWETRG6PJM

查看iphone的手机通讯录的话,需要用到一个库AddressBook。

可以使用里面的方法调用我们自己的通许录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-(void) getPhoneContacts{
ABAddressBookRef addressBook = nil;
if([[UIDevice currentDevice].systemVersion doubleValue] >= 6.0)
{
addressBook = ABAddressBookCreateWithOptions(NULL, NULL);

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
else
{
addressBook = ABAddressBookCreate();
}

NSArray *temPeoples = (__bridge NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook);
for(id temPerson in temPeoples)
{
NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithCapacity:2];
NSMutableArray *phoneArray = [[NSMutableArray alloc] initWithCapacity:3];

NSString *tmpFirstName = (__bridge NSString *) ABRecordCopyValue((__bridge ABRecordRef)(temPerson), kABPersonFirstNameProperty);
NSString *tmpLastName = (__bridge NSString *) ABRecordCopyValue((__bridge ABRecordRef)(temPerson), kABPersonLastNameProperty);

[dic setValue:[NSString stringWithFormat:@"%@ %@", tmpFirstName, tmpLastName] forKey:@"name"];
ABMultiValueRef phone = ABRecordCopyValue((__bridge ABRecordRef)(temPerson), kABPersonPhoneProperty);

for(int k = 0; k < ABMultiValueGetCount(phone); k++)
{
NSString *personPhone = (__bridge NSString *) ABMultiValueCopyValueAtIndex(phone, k);
[phoneArray addObject:personPhone];
}

[dic setValue:phoneArray forKey:@"phone"];
[resultArray addObject:dic];
}
}

ABAddressBookCreate

ABAddressBookCreateWithOptions,ABAddressBookRequestAccessWithCompletion

是不同版本所使用的方法

ABAddressBookCreate适用于6.0以上的,另外的则使用雨6.0以后的,

之后调用ABAddressBookCopyArrayOfAllPeople,获取通讯录的内容。

代码如下:

PhoneContactsViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//
// PhoneContactsViewController.m
// PhoneContacts
//
// Created by david on 13-9-25.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import "PhoneContactsViewController.h"
#import <AddressBook/AddressBook.h>

@interface PhoneContactsViewController ()

@end

@implementation PhoneContactsViewController

@synthesize resultArray;
@synthesize tableView;

- (void)viewDidLoad
{
[super viewDidLoad];
// Dispose of any resources that can be recreated.
self.navigationItem.title = @"查看手机通讯录";
resultArray = [[NSMutableArray alloc] initWithCapacity:100];
[self getPhoneContacts];

//left的button
UIButton *btnBack = [UIButton buttonWithType:UIButtonTypeCustom];
[btnBack setFrame:CGRectMake(0, 0, 25, 26)];
[btnBack setTitle:@"返回" forState:UIControlStateNormal];
[btnBack addTarget:self action:@selector(pressBtnBack) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *leftBtn = [[UIBarButtonItem alloc] initWithCustomView:btnBack];
self.navigationItem.leftBarButtonItem = leftBtn;


//表表格处理
tableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height - 44.0) style:UITableViewStylePlain];
[tableView setDelegate:self];
[tableView setDataSource:self];
[tableView setShowsHorizontalScrollIndicator:NO];
[tableView setShowsVerticalScrollIndicator:NO];
[self.view addSubview:tableView];
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];

}

-(void) pressBtnBack{
[self.navigationController popViewControllerAnimated:YES];
}

-(void) getPhoneContacts{
ABAddressBookRef addressBook = nil;
if([[UIDevice currentDevice].systemVersion doubleValue] >= 6.0)
{
addressBook = ABAddressBookCreateWithOptions(NULL, NULL);

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
else
{
addressBook = ABAddressBookCreate();
}

NSArray *temPeoples = (__bridge NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook);
for(id temPerson in temPeoples)
{
NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithCapacity:2];
NSMutableArray *phoneArray = [[NSMutableArray alloc] initWithCapacity:3];

NSString *tmpFirstName = (__bridge NSString *) ABRecordCopyValue((__bridge ABRecordRef)(temPerson), kABPersonFirstNameProperty);
NSString *tmpLastName = (__bridge NSString *) ABRecordCopyValue((__bridge ABRecordRef)(temPerson), kABPersonLastNameProperty);

[dic setValue:[NSString stringWithFormat:@"%@ %@", tmpFirstName, tmpLastName] forKey:@"name"];
ABMultiValueRef phone = ABRecordCopyValue((__bridge ABRecordRef)(temPerson), kABPersonPhoneProperty);

for(int k = 0; k < ABMultiValueGetCount(phone); k++)
{
NSString *personPhone = (__bridge NSString *) ABMultiValueCopyValueAtIndex(phone, k);
[phoneArray addObject:personPhone];
}

[dic setValue:phoneArray forKey:@"phone"];
[resultArray addObject:dic];
}
}

-(NSInteger) tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 1;
}

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [resultArray count];
}

-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
[cell setSelectionStyle:UITableViewCellSelectionStyleGray];
}


NSDictionary *dic = [resultArray objectAtIndex:indexPath.row];
NSString *strName = [dic valueForKey:@"name"];

strName = [strName stringByReplacingOccurrencesOfString:@"(null)" withString:@""];

cell.textLabel.text = strName;
cell.detailTextLabel.text = @"邀请";
return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSelector:@selector(deleteSelectedCell) withObject:nil afterDelay:0.1];
NSDictionary *dic = [resultArray objectAtIndex:indexPath.row];
NSArray *numArray = [dic valueForKey:@"phone"];
UIActionSheet *sheet = [[UIActionSheet alloc] init];
[sheet setTitle:@"请选择号码"];
for(NSString *number in numArray)
{
[sheet addButtonWithTitle:number];
}

[sheet addButtonWithTitle:@"取消"];
[sheet setDelegate:self];
[sheet setCancelButtonIndex:[numArray count]];

[sheet setActionSheetStyle:UIActionSheetStyleBlackTranslucent];
[sheet showInView:self.view];
}

-(void) deleteSelectedCell
{
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
}

-(void) actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *string = [actionSheet buttonTitleAtIndex:buttonIndex];
if([string isEqualToString:@"取消"])
{
return;
}

[self showMSMViewByNumber:string];
}

-(void) showMSMViewByNumber:(NSString *)string
{
if([MFMessageComposeViewController canSendText]){
[self displaySMSComposeSheet:string];
}else{
NSLog(@"Device not configured to send SMS.");
}
}

-(void) displaySMSComposeSheet:(NSString *)string
{
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = self;
picker.body = @"老婆我爱你";
picker.recipients = [NSArray arrayWithObjects:string, nil];
[self presentModalViewController:picker animated:YES];
}

-(void) messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
switch (result) {
case MessageComposeResultSent:
NSLog(@"信息发送成功!");
break;
case MessageComposeResultFailed:
NSLog(@"信息发送失败!");
break;

default:
break;
}
[self dismissModalViewControllerAnimated:YES];
}

@end

PhoneContactsViewController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// PhoneContactsViewController.h
// PhoneContacts
//
// Created by david on 13-9-25.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>

@interface PhoneContactsViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, UIActionSheetDelegate, MFMessageComposeViewControllerDelegate>

@property (strong, nonatomic) NSMutableArray *resultArray;
@property (strong, nonatomic) UITableView *tableView;

@end

别忘记引入文件:MessageUI和AddressBook

如果你想开发一个同时支持IOS2和IOS3的应用,那你就需要获取当前的IOS版本了。因为IOS2中的部分方法在IOS3中已被移除。

例如,在旋转开始之后,最后的旋转动画发生之前将会自动调用willAnimateRotationToInterfaceOrientation:duration:方法,而该方法是IOS3中新增的方法,在以前的SDK版本中,可以使用willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:方法,但是,IOS3以前的版本中使用的两段式动画比willAnimateRotationToInterfaceOrientation:duration:方法要慢得多,所以应避免这些方法,除非确实需要在应用程序中支持旧的IOS版本。

用宏指令判断版本号:

1
2
3
4
5
#ifdef __IPHONE_6_0  
// code
#else
// code
#endif

示例:

1
2
3
4
5
6
#ifdef __IPHONE_6_0  
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
#else
- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation: (UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
#endif
}

你还可以通过以下方法获取IOS的版本:

1
[UIDevice currentDevice].systemVersion

这将会返回IOS的当前版本。

在方法体中判断:

1
2
3
if([[UIDevice currentDevice].systemVersion doubleValue] >= 6.0){
//你的逻辑
}

\ 转义符

(), (?:), (?=), [] 圆括号和方括号

*, +, ?, {n}, {n,}, {n,m} 限定符

^, $, \anymetacharacter 位置和顺序

|“或”操作

\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\‘ 匹配 “" 而 “(“ 则匹配 “(“。

^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。

$ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。

\ 匹配前面的子表达式零次或多次。例如,zo 能匹配 “z” 以及 “zoo”。* 等价于{0,}。

+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。

? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。

{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。

{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*‘。

{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。

? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。

. 匹配除 “\n” 之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用象 ‘[.\n]’ 的模式。

(pattern) 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 ‘(‘ 或 ‘)‘。

(?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。例如, ‘industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。

(?=pattern) 正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,’Windows (?=95|98|NT|2000)’ 能匹配 “Windows 2000” 中的 “Windows” ,但不能匹配 “Windows 3.1” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?!pattern) 负向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如’Windows (?!95|98|NT|2000)’ 能匹配 “Windows 3.1” 中的 “Windows”,但不能匹配 “Windows 2000” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始

x|y 匹配 x 或 y。例如,’z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。

[xyz] 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。

[^xyz] 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’。

[a-z] 字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。

[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。

\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。

\B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。

\cx 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。

\d 匹配一个数字字符。等价于 [0-9]。

\D 匹配一个非数字字符。等价于 [^0-9]。

\f 匹配一个换页符。等价于 \x0c 和 \cL。

\n 匹配一个换行符。等价于 \x0a 和 \cJ。

\r 匹配一个回车符。等价于 \x0d 和 \cM。

\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。

\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。

\t 匹配一个制表符。等价于 \x09 和 \cI。

\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。

\w 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]’。

\W 匹配任何非单词字符。等价于 ‘[^A-Za-z0-9_]’。

\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。.

\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\1’ 匹配两个连续的相同字符。

\n 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。

\nm 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。

\nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。

\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。

常用的正则表达式

1、非负整数:”^\d+$”

2、正整数:”^[0-9]*[1-9][0-9]*$”

3、非正整数:”^((-\d+)|(0+))$”

4、负整数:”^-[0-9]*[1-9][0-9]*$”

5、整数:”^-?\d+$”

6、非负浮点数:”^\d+(.\d+)?$”

7、正浮点数:”^((0-9)+.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*.[0-9]+)|([0-9]*[1-9][0-9]*))$”

8、非正浮点数:”^((-\d+.\d+)?)|(0+(.0+)?))$”

9、负浮点数:”^(-((正浮点数正则式)))$”

10、英文字符串:”^[A-Za-z]+$”

11、英文大写串:”^[A-Z]+$”

12、英文小写串:”^[a-z]+$”

13、英文字符数字串:”^[A-Za-z0-9]+$”

14、英数字加下划线串:”^\w+$”

15、E-mail地址:”^[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+$”

16、URL:”^[a-zA-Z]+://(\w+(-\w+)*)(.(\w+(-\w+)*))*(?\s*)?$”

当磁盘大小超过标准时会有报警提示,这时如果掌握df和du命令是非常明智的选择。

df可以查看一级文件夹大小、使用比例、档案系统及其挂入点,但对文件却无能为力。

du可以查看文件及文件夹的大小。

两者配合使用,非常有效。比如用df查看哪个一级目录过大,然后用df查看文件夹或文件的大小,如此便可迅速确定症结。

下面分别简要介绍

df命令可以显示目前所有文件系统的可用空间及使用情形

请看下列这个例子:

1
2
3
4
5
6
7
8
[xx@xx]:~/php-oauth/itv_data_control$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 64G 11G 50G 17% /
udev 367M 4.0K 367M 1% /dev
tmpfs 150M 4.2M 146M 3% /run
none 5.0M 0 5.0M 0% /run/lock
none 374M 0 374M 0% /run/shm
none 4.0T 0 4.0T 0% /media/psf

参数 -h 表示使用「Human-readable」的输出,也就是在档案系统大小使用 GB、MB 等易读的格式。

上面的命令输出的第一个字段(Filesystem)及最后一个字段(Mounted on)分别是档案系统及其挂入点。我们可以看到 /dev/sda1 这个分割区被挂在根目录下。

接下来的四个字段 Size、Used、Avail、及 Use% 分别是该分割区的容量、已使用的大小、剩下的大小、及使用的百分比。 FreeBSD下,当硬盘容量已满时,您可能会看到已使用的百分比超过 100%,因为 FreeBSD 会留一些空间给 root,让 root 在档案系统满时,还是可以写东西到该档案系统中,以进行管理。

du查询文件或文件夹的磁盘使用空间

如果当前目录下文件和文件夹很多,使用不带参数du的命令,可以循环列出所有文件和文件夹所使用的空间。这对查看究竟是那个地方过大是不利的,所以得指定深入目录的层数,参数:–max-depth=,这是个极为有用的参数!如下,注意使用“*”,可以得到文件的使用空间大小.

提醒:一向命令比linux复杂的FreeBSD,它的du命令指定深入目录的层数却是比linux简化,为 -d。

1
2
3
[xx@xx]:~/php-oauth/itv_data_control$ du -h --max-depth=1 /home
2.6G /home/davidzhang
2.6G /home
1
2
3
[xx@xx]:~/vlinkage_itv_data$ du -h --max-depth=1 /home/davidzhang/vlinkage_itv_data/*
13M /home/davidzhang/vlinkage_itv_data/文广快照.tsv
7.0M /home/davidzhang/vlinkage_itv_data/深圳IP快照.tsv
1
2
[xx@xx]:~/vlinkage_itv_data$ du -h --max-depth=1 /home/davidzhang/vlinkage_itv_data/
20M /home/davidzhang/vlinkage_itv_data/

值得注意的是,看见一个针对du和df命令异同的文章:《du df 差异导致文件系统误报解决》。

du 统计文件大小相加

df 统计数据块使用情况

如果有一个进程在打开一个大文件的时候,这个大文件直接被rm 或者mv掉,则du会更新统计数值,df不会更新统计数值,还是认为空间没有释放。直到这个打开大文件的进程被Kill掉。

如此一来在定期删除 /var/spool/clientmqueue下面的文件时,如果没有杀掉其进程,那么空间一直没有释放。

使用下面的命令杀掉进程之后,系统恢复。

1
fuser -u /var/spool/clientmqueue

今天在写一个小功能,就是添加视频,然后将视频的标题和链接添加到textarea中,其实是如此的简单,我确如此的…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//插入数据到指定的位置
function insertText(string){
var obj = $("#description").get(0);
var str = string;
if (document.selection) {
obj.focus();
var sel = document.selection.createRange();
sel.text = str;
} else if (typeof obj.selectionStart === 'number' && typeof obj.selectionEnd === 'number') {
var startPos = obj.selectionStart;
var endPos = obj.selectionEnd;
var tmpStr = obj.value;
obj.value = tmpStr.substring(0, startPos) + str + tmpStr.substring(endPos, tmpStr.length);
} else {
obj.value += str;
}
}

从iOS7的Beta版开始,就着手做兼容工作,到Beta4的時候,应用已经基本兼容,只是偶然发现,iOS样式的手势返回,也就是用interactivePopGestureRecognizer返回的时候,出现下面一些列问题。各方调研,无果,史无前例(废话,刚出来的7,上哪有例去–#)。

A,我的应用是自定义的返回按钮图标(默认返回按钮样式不会出现问题3),为了保险,写了这句代码[self.navigationItem setHidesBackButton:YES]。 由于自定义返回按钮,所以iOS7自带返回手势无效。在需要的页面加上navigationController.interactivePopGestureRecognizer.delegate = self 返回手势好用了。

B,于是出现了第二个问题。 在一级视图中,iOS样式返回的手势滑动一下,然后进入二级视图,发现,画面卡住了,按Home键转入后台,再返回应用,发现并没有Crash掉,而是直 接跳到了二级视图里,运行正常了,大家知道push和pop的原理是用进栈出栈完成的,可能因为在一级视图中滑动那一下,影响了视图在栈中的位置。 好,先解决一下这个问题,一级视图中一定要加入self.navigationController.interactivePopGestureRecognizer.enabled = NO;,先把iOS7手势返回屏蔽掉,到二级视图再用self.navigationController.interactivePopGestureRecognizer.enabled = YES打开。就Ok了。

C,好,第三个问题相继出现(其实是跟第二个一起出来的)。 手势返回拖动一半,放手,navigationBar上会出现三个小蓝点,而且位置不规律,可以肯定这个不是项目代码或者图片搞出来的东西,一定是SDK自己蹦出來的。 后台尝试发现UIBarButtonItem的title如果是nil的话,就会有这个问题。 解决方案:把[self.navigationItem setHidesBackButton:YES];去掉,然後把假装成返回按钮的UIBarButtonItem的title设置成@””。

D,大功告成。可见设计要是不按苹果规范来的话,就会遇到各种坑啊。

出于什么样的原因你会希望用户从你的iOS app中进入App Store呢?可能你想用户去App Store 为你的应用评分,也可能你希望用户看到你其他的iOS app。iOS 6引入了SKStoreProductViewController类,可以让用户在不离开当前应用的前提下展示App Store中的其他产品。

Store Kit

SKStoreProductViewController类是Store Kit框架的一部分。SKStoreProductViewController使用起来非常简单,在用实例讲解之前,了解一些基本的知识很有必要。

SKStoreProductViewController类是UIViewController的子类, 如果你对view controller比较熟悉的话,那SKStoreProductViewController使用起来也非常简单了。当你希望向用户展示App Store中产品时,你需要:

1.实例化一个SKStoreProductViewController类
2.设置它的delegate
3.把sotre product视图控制器显示给消费者

剩下的就交给操作系统来处理了。需要记住一点的是SKStoreProductViewController只能以模态的方式显示。 SKStoreProductViewControllerDelegate协议定义了一个单独的方法— productViewControllerDidFinish:,当消费者离开App Store时会调用这个方法—一般是通过点击左上角画面中的取消按钮。通过给代理发送productViewControllerDidFinish:消 息,操作系统就会把控制权返回到你的程序。下面我来演示一下如何在一个简单的程序中使用SKStoreProductViewController类。

Step 1: Setting Up the Project

我们将要创建的app不是多实用,仅有一个按钮,可以把用户带入App Store,向用户展示我最近发布的一款简单的天气类app。通过实例我们可以了解不同的部分如何很好地契合在一起,还可以了解如何在项目中使用 SKStoreProductViewController类。

从模版列表中选择一个Single View Application模版,在Xcode中创建一个新的项目。

将程序的名称设置为UsingStoreProduct,然后输入一个company identifier,并将device family设置为iPhone,最后勾选上Automatic Reference Counting。剩余的勾选框不要勾选。“告诉”Xcode你希望保存项目的地方,点击创建按钮。

Step 2: Adding the Store Kit Framework

由于SKStoreProductViewController类是Store Kit框架的一部分,所以我们需要将这个Store Kit框架链接到我们的工程中。在工程导航器中选中工程,然后在target列表中选中target。在画面的顶部,选择Build Phase选项,然后打开Link Binary With Libraries。点击‘+’按钮,并在列表中搜索StoreKit并选择StoreKit.framework。这样就可以成功的将Store Kit框架链接到工程中。

为了使用UsingStoreProductViewController类里的Store Kit框架,我们需要输入框架的头文件,打开UsingStoreProductViewController.h,在顶部添加下边这个引入语法:

1
#import <StoreKit/StoreKit.h>

Step 3: Using the SKStoreProductViewController Class

在视图控制器的viewDidLoad方法中,在下面的代码片段中创建一个新的按钮。按钮的类型是UIButtonTypeRoundedRect,然后 我把这个按钮放在视图控制器view的正中间。同时我还给这个按钮制定了一个title,并添加了一个target-action——匹配 UIControlEventTouchUpInside事件。这意味无论何时,用户点击按钮,view controller就会收到“前往寻艺 App Store”的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)viewDidLoad
{
[super viewDidLoad];


[super viewDidLoad];
//初始化一个按钮
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:@"前往寻艺 App Store" forState:UIControlStateNormal];
[button setFrame:CGRectMake(0.0, 0.0, 200.0, 44.0)];
[button setCenter:self.view.center];
[self.view addSubview:button];
[button addTarget:self
action:@selector(openAppStore:)
forControlEvents:UIControlEventTouchUpInside];

}

在openAppStore: 方法中,我对SKStoreProductViewController进行了初始化,并将自己设置为它的delegate,然后在给这个实例发送一个 loadProductWithParameters:completionBlock:消息。
loadProductWithParameters:completionBlock:接收两个参数:

(1)一个字典:用一个key指定我们想要显示给用的程序的标示符。

(2)一个completion block。
当App store请求结束时会调用这个completion block。在完成的block中,我们要核实是否有错误遗漏,并把store product 视图控制器展示给用户。

请记住,即使用户没有离开你的程序,操作系统仍然会在内部进行与App store的连接。由于在请求App Store过程中,会需要稍微长的一段时间,也就是说,最好在请求还没有返回响应时给用户显示一个风火轮。一旦请求完成(成功或者不成功),已经完成的 block将会允许我们解除activity indicator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(void) openAppStore:(id)sender
{
//初始化Product View Controller
SKStoreProductViewController *storeProductViewController = [[SKStoreProductViewController alloc] init];
//配置View Controller
[storeProductViewController setDelegate:self];
[storeProductViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:@"685836302"}
completionBlock:^(BOOL result, NSError *error){
if(error)
{
NSLog(@"Error %@ with User Info %@.", error, [error userInfo]);
}
else
{
[self presentViewController:storeProductViewController
animated:YES
completion:nil];
}
}];

}

注意:你可以在iTunes Connect找到app的唯一识别符,App Store中的每个app都有一个唯一识别符/Apple ID,注意你需要将在参数字典中以字符串的形式传递apple id。

在生成和运行程序之前,我们需要MTViewController类通过实现productViewControllerDidFinish:方法以遵循 SKStoreProductViewControllerDelegate协议。我们可以通过告诉编译器“UsingStoreProductController类符合 SKStoreProductViewController授权协议”来更新view controller的接口文件,看下边:

1
2
3
4
5
6
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>

@interface UsingStoreProductViewController : UIViewController<SKStoreProductViewControllerDelegate>

@end

Step 4: Build and Run

虽然苹果表示SKStoreProductViewController类可以向用户展示其他app,但这是一种理想的在用户不离开当前app的情况下,让用户去App Store评分的方法。

整个的运行逻辑代码:

UsingStoreProductViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//
// UsingStoreProductViewController.m
// UsingStoreProduct
//
// Created by david on 13-9-23.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import "UsingStoreProductViewController.h"

@interface UsingStoreProductViewController ()

@end

@implementation UsingStoreProductViewController

@synthesize indicatorView;

- (void)viewDidLoad
{
[super viewDidLoad];


[super viewDidLoad];
//初始化一个按钮
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:@"前往寻艺 App Store" forState:UIControlStateNormal];
[button setFrame:CGRectMake(0.0, 0.0, 200.0, 44.0)];
[button setCenter:self.view.center];
[self.view addSubview:button];
[button addTarget:self
action:@selector(openAppStore:)
forControlEvents:UIControlEventTouchUpInside];

}


-(void) openAppStore:(id)sender
{
[self showIndicator];
//初始化Product View Controller
SKStoreProductViewController *storeProductViewController = [[SKStoreProductViewController alloc] init];
//配置View Controller
[storeProductViewController setDelegate:self];
[storeProductViewController loadProductWithParameters:@{SKStoreProductParameterITunesItemIdentifier:@"685836302"}
completionBlock:^(BOOL result, NSError *error){
if(error)
{
NSLog(@"Error %@ with User Info %@.", error, [error userInfo]);
}
else
{
[self hideIndicator];
[self presentViewController:storeProductViewController
animated:YES
completion:nil];
}
}];

}

-(void) productViewControllerDidFinish:(SKStoreProductViewController *)viewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}

- (void)showIndicator
{
indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
indicatorView.autoresizingMask =
UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
| UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
[self.view addSubview:indicatorView];
[indicatorView sizeToFit];
[indicatorView startAnimating];
indicatorView.center = self.view.center;
}

- (void)hideIndicator
{
[indicatorView stopAnimating];
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

@end
0%