概述

监控文件系统的变化,不是一个常见的需求,但是随着对PHP使用的深入,不可避免的会碰到这类问题。通常使用文件轮询的通知机制,但是这种机制只适用于经常改变的文件。其他情况下都非常低效,并且有时候会丢失某些类型的变化。

Inotify是什么

Inotify是一种文件变化通知机制,Linux内核从2.6.13开始引入。它可以高效地实时跟踪Linux文件系统的变化。

Inotify-tools 工具包

此工具包是Linux下C语言编写的一个简单的命令行工具包。在CentOS下通过以下命令安装:

yum install inotify-tools

假如我们打算监控/srv/test文件夹上的操作,只需执行:

inotifywait -rme modify,attrib,move,close_write,create,delete,delete_self /srv/test

上述任务运行的同时,我们在另一个shell里依次执行以下操作:创建文件夹,然后在新文件夹下创建文件,接着删除新创建的文件:

% mkdir /srv/test/infoq
% echo TODO > /srv/test/infoq/article.txt
% rm /srv/test/infoq/article.txt

在运行inotifywait的shell中将会打印以下信息:

/srv/test/ CREATE,ISDIR infoq
/srv/test/infoq/ CREATE article.txt
/srv/test/infoq/ MODIFY article.txt
/srv/test/infoq/ CLOSE_WRITE,CLOSE article.txt
/srv/test/infoq/ DELETE article.txt

显而易见,只要有变化我们就会收到相关的通知.其它用法请参考相关文档

在PHP中调用Inotify库

inotify在PHP中使用也很简单,使用inotify_init创建一个句柄,然后通过inotify_add_watch/inotify_rmwatch增加/删除对文件和目录的监听。PHP中提供了inotify扩展,支持了inotify系统调用

安装inotify扩展

pecl install inotify

操作成功后,修改php.ini,加入

extension=inotify.so

查看扩展是否加载成功:

php -m | grep inotify

使用

首先在当前目录创建一个inotify.data文件,示例就用来监听此文件

//创建一个inotify句柄
$fd = inotify_init();
//监听文件,仅监听修改操作,如果想要监听所有事件可以使用IN_ALL_EVENTS 
$watch_descriptor = inotify_add_watch($fd, __DIR__.'/inotify.data', IN_MODIFY);  
while (true) {     
    //阻塞地读取数据     
    $events = inotify_read($fd);     
    if ($events) {         
        foreach ($events as $event) {             
            echo "inotify Event :".var_export($event, 1)."\n";         
        }     
    } 
} 
//释放inotify句柄 
inotify_rm_watch($fd, $watch_descriptor); 
fclose($fd); 

修改inotify.data,就可以看到程序输出了信息。

echo "hello world" > inotify.data 

inotify Event :array (   
    'wd' => 1,   
    'mask' => 2,   
    'cookie' => 0,   
    'name' => '', 
)

使用swoole实现异步非阻塞

//创建一个inotify句柄 
$fd = inotify_init(); 
//监听文件,仅监听修改操作,如果想要监听所有事件可以使用IN_ALL_EVENTS 
$watch_descriptor = inotify_add_watch($fd, __DIR__.'/inotify.data', IN_MODIFY); 

//加入到swoole的事件循环中 
swoole_event_add($fd, function ($fd) {     
    $events = inotify_read($fd);     
    if ($events) {         
        foreach ($events as $event) {             
            echo "inotify Event :" . var_export($event, 1) . "\n";         
        }     
    } 
});

这里使用了swoole扩展提供swoole event add函数,将inotify句柄设置为非阻塞,并加入到epoll事件循环中。程序变成异步非阻塞模式。当有事件发生时才会执行inotify_ read获取事件。没有事件发生时,程序可以执行其他的逻辑。

总结

Inotify为Linux提供了一套高效监控和跟踪文件变化的机制,它可以实时地处理、调试以及监控文件变化,而轮询是一种延迟机制。PHP通过扩展的方式对Inotify提供了支持,可以方便使用。

案例

先来看一个例子

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
`enum VendingMachineError: ErrorType {
case InvalidSelection
case OutOfStock
}
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func vend(itemName name: String) throws {
guard var item = inventory[name] else {
throw VendingMachineError.InvalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
}
}
func buyFavoritesSnack(person: String, vendingMachine: VendingMachine) {
let snckName = "Candy Bar"
do {
try vendingMachine.vend(itemName: snckName)
} catch VendingMachineError.InvalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.OutOfStock {
print("Invalid OutOfStock.")
}
}

`在playground中运行会提示以下报错,但在do-try-catch中已经对定义的每种Error都进行了处理,为何还会提示没有穷举全部的Errors呢

Errors throws from here are not handled because the enclosing catch is not exhaustive

分析

在Swift2 error handling 模型中,有两个重要的关键点:

  1. exhaustiveness(穷举)
  2. resiliency(弹性)

两者一起决定了do-catch需要处理所有可能的error,而不仅是自已定义的。上述例子中func vend 只是声明会抛出异常,并没有指明具体是异常种类。不允许指明具体异常体现了弹性的设计理念。假设将来出于其它目的增加了异常的种类,不希望每个调用方法的地方都要修改catch。调用者由于不知道vend会抛出种异常。基于穷举的设计理念,在catch自定义的error外,还要处理未知的error。

1
2
3
4
5
6
7
8
9
10
11
12
` do {
try vendingMachine.vend(itemName: snckName)
} catch VendingMachineError.InvalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.OutOfStock {
print("Invalid OutOfStock.")
} catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
print("Invalid InsufficientFunds: \(coinsNeeded)")
} catch { //一定要增加处理未知error的通用catch 语句
print("universal error")
}

`

One More Thing

对于调用者来说,使用上面自定义的异常时,在每个catch处都要处理异常描述。意味着将来如果异常改变时,每个调用者也都要变,不够resiliency

比较好的做法是集中化处理Error描述:

1
2
3
4
5
6
7
8
`extension VendingMachineError: CustomStringConvertible {
var description: String {
switch self {
case .InvalidSelection: return "InvalidSelection"
case .OutOfStock: return "OutOfStock"
}
}
}

`加了这个extension后,调用者catch的异常就可以集中交给自己处理:

1
2
3
4
5
6
7
do {
try vendingMachine.vend(itemName: snckName)
} catch let error as VendingMachineError {
print(error.description)
} catch {
print("I dunno")
}

`

总结

Swift为了减少运行时的错误率,其异常处理机制要求我们必须处理出全部的异常,包括已知和未知的。同时又巧妙地添加了弹性特性,尽量减少修改代码对程序的影响。

参考资料: swift-do-try-catch-syntax

导读

在上一篇文章解决往可重用Cell中手动添加View时重复显示问题中我提到了类似如下这种复杂的横向多控件布局,我未有好的AutoLayout实现方式,暂时使用了手动编写视图代码的方法。

image

难点

这种横向的布局难点主要有:

1.左边的Label长度不固定

2.右边由ImageView和Label叠加成。且Image长度随Label变化。Label长度也不固定

3.左边和右边长度相加不超过屏幕宽度。如果超过,保持右边不变,左边缩略。

但对于这种布局,我使用耿耿于怀,希望能用AutoLayout完美解决。在大牛JohnLui的帮助下,终于搞定。为了帮助其它遇到类似问题的小伙伴同时也基于分享的精神,我制作了一个小DemoHorizontalMultiViewDemo,方便大家使用,欢迎大家fork。

思路

使用AutoLayout解决这类布局的关键点在于:

1.给右边ImageView增加一个到右侧距离 >=0 的约束!

2.给右边ImageView增加一个宽度约束,然后用代码调整这个约束的值!

3.两个Label不要设置宽度约束

引言

AutoLayout能快速地构建页面,提高开发效率。相对于纯手写代码,新手能快速入门。然而,在实际开发过程中,会遇到有些页面如果单纯使用AutoLayout布局会很困难,有种无从下手的感觉。这时就可以和代码写视图的形式一起混合使用。

实际案例

例如以下TableViewCell,由三部份组合而成。1和2的布局固定。可以很方便使用AutoLayout以拖控件的形式实现。但第2部份有以下难点:

1.左边的Label长度不固定

2.右边的视图由Image和Label叠加成。且Image的长度随Label变化。Label长度也不固定

3.左边和右边长度相加不能超过屏幕宽度。如果超过,要保持右边的长度,左边缩略

对于这种复杂的横向多控件,长度不统一的布局,我暂时未有想到很好的方向。所以使用了手动编写视图代码的方法实现。

image

主要的代码如下:

@interface ServiceItemListCell ()
@property (nonatomic, strong) UIView *titleView;
@end

- (void)setupTitleView {

_titleView = [[UIView alloc] initWithFrame:CGRectMake(87, 23, self.contentView.width - 87 - 10, 18)];
[self.contentView addSubview:_titleView];

NSString *saveString = [NSString stringWithFormat:@"减%ld",[_entity.originalPrice integerValue]-[_entity.price integerValue]];
CGSize labelSize = [saveString sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12]}];

UILabel *saveMoneyLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, labelSize.width, labelSize.height)];
saveMoneyLabel.text = saveString;
saveMoneyLabel.font = [UIFont systemFontOfSize:12];
saveMoneyLabel.textColor = [UIColor colorWithHexString:@"#3e82ff" alpha:1.0];


UIImageView *imageView = [[UIImageView alloc]  initWithImage:[[UIImage imageNamed:@"service_price_subtract_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
imageView.width = saveMoneyLabel.width + 22 ;


CGSize nameSize= [_entity.name sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:18]}] ;
UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, nameSize.width, 18)];
nameLabel.text = _entity.name;
nameLabel.font = [UIFont systemFontOfSize:18];
nameLabel.textColor = [UIColor colorWithHexString:@"#25292c" alpha:1.0];

if (nameLabel.width > _titleView.width - 10 - imageView.width) {
    nameLabel.width = _titleView.width - 10 - imageView.width;
}

imageView.x = nameLabel.width + 10;
saveMoneyLabel.x = imageView.x + 12;
saveMoneyLabel.centerY = imageView.centerY;

[_titleView addSubview:nameLabel];
[_titleView addSubview:imageView];
[_titleView addSubview:saveMoneyLabel];

}

滚动重复问题

在自定义Cell中手动添加视图后,滚动列表,由于Cell重用机制,会出现重复添加的问题。在网上查阅相关的解决方案,发现最快的方法是重写prepareForReuse方法,在重用时移除添加的视图:

-(void)prepareForReuse {
[super prepareForReuse];
[_titleView removeFromSuperview];
}

image

引言

今天重温经典著作《黑客与画家》,书中防止垃圾邮件的一种方法一章中介绍了作者识别垃圾邮件的方法不是通过直觉上的特征识别,而是转向使用统计学方法(贝叶斯判断)的过程。我突然意识到和两个月之前解决一个需求的方案有异曲同工之处。

需求

团队编辑人员的工作是收集网上最新的资讯录入到公司的平台上,小编们发现各平台发布的资讯有可能会重复(换了个标题或改了些内容)。由于每天要录入的文章很多且每个小编都负责各版块,经常会录入重复的文章。

思路

我当时脑洞大开地想到可以统计每篇文章中词频出现最多的top100单词,然后比较每篇文章top100的词的重复个数来检测是否是相似的文章。

分词

对录入db的每篇文章去噪后使用SCWS中文分词引擎先分好词,统计每个单词出现的次数,排好序,将出现次数最多的top100(测试发现100个已经足够用了)单词入库。

相似度检测

对新录的文章先分词,得到top100词,然后和db的每篇文章的top100词比较(db取数据可能有瓶颈,可以考虑用缓存),计算(可以使用多线程)重复词出现的比例换算出相应的相似度(%)。功能上线两个月,除了可爱的小编偶尔撒撒娇抱怨下速度,其它妥妥的,给自己点个赞。

启发

发散自己的思维,构建自己的知识体系,扩充知识面。

原因

说来惭愧,刚毕业工作那会,还不知道用谷歌来解决搜索技术问题,后来从前辈得知:内事不决问百度,外事不决问谷歌,才发现外面居然是另外一个天地。但那时谷歌已经无法访问了,从需求出发,学会了上网找一些vpn软件安装,勉强用了一年,实在无法忍受手动切换网络。又是从大牛处得知红杏,可以通过谷歌插件的形式动态使用代理,真是解决用户的刚需!最近一段时间红杏也受到影响,无法使用。想想自己也是个追求自由、喜欢开源,具有黑客精神的程序员,遂决定搭建自己的服务器,顺带解决科学上网的刚需要。

要求

  • 基本的Linux基本操作命令(cat、tail、sudo等)
  • Windows或Mac下ssh工具
  • Linux下文本编辑工具Vim(鸟哥教程
  • 支持VISA支付的信用卡

云服务器购买

Vultr是近几年才悄然兴起的VPS品牌,主要特点是SSD硬盘、价格便宜、全球机房、10Gb服务器带宽。相同vps配置下,Vultr价格比digitalocean略低。每月5美元可购买768MB内存、15GB SSD硬盘空间、1000GB流量。
vps1
大家也可选择其它国外流行的VPS品牌,如果想体验Vultr,可以通过以下优惠链接购买

Vultr 优惠链接

注册登陆成功后在管理控制台选择Billing选项绑定信用卡,绑定成功后会扣除2.5刀进行验证,之后会返还,而且还会赠送5刀,意味着可以免费体验一个月!
信用卡绑定会再选择Deploy布署,一般一步步往下进行操作就行了,注意的Operating System最好选择64 bit Ubuntu 14.04,服务器选择最低配置(5刀一月),最后点Place Order提交订单,大概30分钟左右,如果在My Servers选项看到Running状态的Server,就代表服务器启动了。

服务器配置

连接远程主机

想在自己的电脑上对服务器进行操作,一般都会使用SSH客户端连接远程主机。Winow要下推荐用Xshell,Mac下使用自带的Terminal或iTerm。安装成功后运行软件通过以下命令连接到主机上,ip地址和初始的root密码可以在Manage中看到。

ssh root@ip地址

代理软件安装配置及运行

apt-get install python-setuptools
apt-get install m2crypto
apt-get install supervisor
easy_install pip
pip install shadowsocks

运行以下命令配置ss服务信息

vim /etc/shadowsocks.json

在打开的vim编辑器中粘贴以下信息,保存退出

{
"server":"0.0.0.0",
"server_port":客户端访问的端口号,  #20000~50000,只要不冲突就行
"local_port":1080,
"password":"客户端访问的密码",  #最好复杂些
"timeout":600,
"method":"rc4-md5"
}

运行以下命令配置supervisor服务信息

vim  /etc/supervisor/supervisord.conf

在打开的vim中,追加以下信息

[program:shadowsocks]
command=ssserver -c /etc/shadowsocks.json
autostart=true
autorestart=true
user=root
log_stderr=true
logfile=/var/log/shadowsocks.log   

启动相关服务

/etc/init.d/supervisor start

至此服务器配置工作基本完成。

电脑客户端软件安装

####mac
点击此链接安装软件

安装运行后按以下步骤将在服务器配置的ip、密码、端口号填上,就能愉快地科学上网!

####windows
点击此链接安装软件,注意要选择Shadowsocks-win-dotnet4.0-x.x.x.zip的安装包,具体的设置方法和mac一样。

###总结
工作这些年,真心觉得谷歌帮助解决了很多技术问题,尤其是在学习IOS开发时,国外很多博客或技术网站提供资料真的用心而且详细。相比用百度搜索到的,基本都是复制粘贴。网上已经有很多类似的教程,写下这篇博文也是希望能尽自己一点点的力量。

ssl-icon

免费证书申请

www.startssl.com是一家提供SSL证书的网站,各大主流浏览器基本都支持。并且还提供了一年的免费Class 1证书,非常适合个人及小型企业使用。

  • 注册

startssl的注册非常特别,并不是使用帐号密码登陆,而是要在注册时填写非常完整的个人信息,提交后等待审核,通过之后安装该网址的数字证书才能登陆.之后只能在安装了数字证书的电脑上才能登陆,最好将证书备份出来方便在其它电脑上登陆。
sign-up

  • 域名验证

注册成功之后,登陆网站,完成域名的验证
domain-valid-1
domain-valid-2

  • 私钥(key)和 证书请求文件(csr)

我使用的是Ubuntu系统,自带openssl命令
openssl req -new -newkey rsa:2048 -nodes -out pachttps.csr -keyout pachttps.key
运行上面脚本后并按提示填写相应的信息后生成key和csr文件

  • 证书(crt)生成

crt-1
crt-2
crt-3
crt-4
crt-5
将生成的crt文件上传到服务器上。

Nginx配置https支持

/etc/ssl/pachttps.key
/etc/ssl/pachttps.csr
/etc/ssl/pachttps.crt

假设三个ssl文件存放于上面目录中,使用vim编辑nginx.conf文件 ,添加对ssl的支持

listen 443 ssl;
ssl_certificate /etc/ssl/pachttps.crt;   
ssl_certificate_key /etc/ssl/pachttps.key; 

重启nginx,使用https前缀刚才的子域名,一切正常

https-success

证书续期

在证书到期前两周左右,会收到startssl发来的证书将过期邮件。到时到更新此博文 :)

  • 千挑万选想找个容易好记富有意义有思想有远见又见文知意的好域名,发现真正好难,无奈之下选个这个随大流的域名,理想很丰满,现实却挺骨感,坚持才是最最重要的 ¥116.93/两年。
  • VPS 从 vultr.com 上购买,原来主要是为了搭个代理绕过你懂的那些事联结世界 ¥35/每月。
  • Hexo 用来搭建静态个人blog真是方便简单到家,几个命令搞定发布、生成、本地预览、布署一条龙服务。
  • blog 代码托管在coding.net上,和github挺像,关键速度飞快,免费的私有代码仓库真是良心。
  • 麻雀虽小,漂泊在外,没想到在网上先有个小窝,一直在路上。

  • dotCloud 旧金山一家做PaaS平台的公司,为开发者或开发商提供技术服务
  • PaaS Platform as a Service 平台即服务。提供相关的配套设置,包括语言环境、运行环境、存储和各种基础服务
  • SaaS Software as a Service
  • LXC LinuX Containers
  • Docker 轻量级虚拟化技术 能够把开发的应用程序自动部署到容器的开源引擎
  • 容器 直接运行在操作系统内核之上的用户空间,操作系统级虚拟化,可以让多个独立的用户空间运行在同一台宿主机上
  • HV hypervisor virtualization

View的功能

  1. 管理矩形区域里的内容
  2. 处理矩形中的事件
  3. 子视图的管理
  4. 实现动画

结构体 CGPoint CGSize CGRect

1
2
3
4
struct CGPoint {
CGFloat x;
CGFloat y;
};
1
2
3
4
struct CGSize {
CGFloat width;
CGFloat height;
};
1
2
3
4
struct CGRect {
CGPoint origin; //偏移是相对父视图的
CGSize size;
};

基本属性

  1. frame 是CGRect frame的origin是相对于父视图的左上角原点(0,0)的位置,改变视图的frame会改变center
  2. center 是CGPoint 指的是整个视图的中心点
  3. bounds 是CGRect 是告诉子视图本视图的原点位置
  4. 添加子类视图,越晚添加、视图就在越上层

基本界面元素 window、view、screen

  1. UIView

    负责做显示的画布

  2. UIWindow

    继承自UIView,相当于画框

  3. UIScreen

1
2
3
4
5
6
7
8
9
10
CGRect screenBounds = [ [UIScreen mainScreen]bounds]; //返回的是带有状态栏的Rect
CGRect viewBounds = [ [UIScreen mainScreen]applicationFrame]; //不包含状态栏的Rect
//screenBounds 与 viewBounds 均是相对于设备屏幕来说的
//所以 screenBounds.origin.x== 0.0 ; screenBounds.oringin.y = 0.0;
screenBounds.size.width == 320; screenBounds.size.height == 480(或者其他分辨率有所差异)
//所以 screenBounds.origin.x== 0.0 ; screenBounds.oringin.y = 20.0;(因为状态栏的高度是20像素) screenBounds.size.width == 320; screenBounds.size.height == 480
//取得StatusBar的位置和大小
[self.view addSubview:theToolbar];
CGRect statusBarRect = [[UIApplication sharedApplication]statusBarFrame];

方法

  1. 新增加和移除subview
  2. 前后移动一个图层 bringSubviewToFront、sendSubviewToBack
  3. 使用索引index交换两个subview彼此图层关系

    1
    [UIView exchangeSubviewAtIndex:indexA withSubviewAtIndex:indexB];
  4. NSInteger index = [[UIView subviews] indexOfObject:Subview名称]; //取得Index

  5. 替Subview加上NSInteger 的註记(Tag)好让之后它们分辨彼此

    1
    2
    [Subview setTag:NSInteger];       //加上标记
    [UIView viewWithTag:NSInteger];  //通过标记得到view 返回值为UIView
  6. [UIView subviews] ; //取的UIView下的所有Subview

bounds 和frame关系

图片

  1. frame: 该view在父view坐标系统中的位置和大小。(参照点是,父亲的坐标系统)
  2. bounds:该view在本地坐标系统中的位置和大小。(参照点是,本地坐标系统,就相当于ViewB自己的坐标系统,以0,0点为起点)
  3. center:该view的中心点在父view坐标系统中的位置和大小。(参照电是,父亲的坐标系统)