0%

修改Nginx的网站根目录


Nginx的默认网站文件保存在nginxhtml目录下,由于我开发时都把项目放在自己的workspace里,所以准备更改网站根目录,更改方法为修改nginx.conf配置文件的root指令,root指令可以放在httpserverlocation块中,我选择把root指令放在server块中,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
http {
include mime.types;
default_type application/octet-stream;

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

server {
listen 80;
server_name localhost;
//配置root指令
root /Users/zhongwu/Documents/workspace/web;
}
}

配置完成后,执行sudo ./nginx -s reload命令,Nginx支持动态更新配置信息。

访问一下网页测一下,发现返回状态码403,既请求被服务器拒绝了,查看Nginx的error.log后,发现如下log,既Nginx没有打开该html文件的权限,所以得解决权限的问题。

1
2016/09/24 17:51:39 [error] 16397#0: *15 open() "/Users/zhongwu/Documents/workspace/web/lounge.html" failed (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET /lounge.html HTTP/1.1", host: "127.0.0.1"

首先我们来了解Nginx的用户权限,Nginx的master进程是root权限,而worker进程默认是nobody用户,用户组同为nobody,由于请求的处理是由worker进程处理的,所以,需要修改worker的用户,将其修改为我的workspace目录的用户,方法为直接修改Nginx配置文件,在nginx.conf配置文件中添加user zhongwu staff;//zhongwu是我的workspace用户,staff是用户组
再访问以下,成功。

解决127.0.0.1能访问,而localhost不能访问


项目出现127.0.0.1能访问,而localhost却访问不了的问题,定位,应该是dns域名解析的问题,查看/etc/hosts文件,发现有一条记录为:::1 localhost::1ipv6的表示方法,将其改为ipv4的,既将::1替换为127.0.0.1,因为Nginx默认并没有开启监听ipv6地址。

再次访问,localhost就能访问了。

前言


iOS8.3之后,存放通话日志的数据库的位置换成了/private/var/mobile/Library/CallHistoryDB/目录,数据库名为CallHistory.storedata,由于其采用了Sqlitewal机制,所以还有两个文件,分别为CallHistory.storedata-shmCallHistory.storedata-wal,一开始想得很简单,直接使用Sqlite打开该数据库,读取即可,后来发现使用NSFileManager并不能获取到该文件。接下来将详细列出解决思路。

读取通话日志文件


直接使用NSFileManager不能获取到通话日志文件的原因很简单,因为App本身是在一个沙盒里面的,App只能访问自己的存储空间,系统有一套安全机制来对其进行限制。所以,首先得解决这个限制,解决方案是添加Entitlements授权,在Entitlements文件中添加如下代码:

1
2
3
4
5
6
<key>com.apple.security.exception.files.absolute-path.read-write</key>
<array>
<string>/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata-wal</string>
<string>/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata-shm</string>
<string>/private/var/mobile/Library/CallHistoryDB/CallHistory.storedata</string>
</array>

这样,就能访问到通话日志文件。

使用Sqlite读取数据库


使用Sqlite直接读取数据库内容时,发现,在调用int errorCode = sqlite3_prepare_v2(database,[sqlStatement UTF8String],-1,&compiledStatement, NULL);时,出现错误,错误代码为SQLITE_AUTH,既没有权限,最后解决方案是,将3个文件内容读出,并保存到App自己的存储空间中,比如Cache目录中,再对保存到App存储目录中的数据库文件进行操作即可。

解决wal模式问题


由于通话日志的数据库采用了新的事务设计wal(write-ahead logging),数据库修改后,首先会把修改内容写入日志(-wal),为了提高性能创建了一个内存索引(-shm),映射每一个page是否dirty,读取时先看需要的page是否在wal日志中,然后再读取,当达到一定条件后Sqlite会自动将数据flush到数据库文件,当然也可以手动flush,在打开数据库之后,调用sqlite3_wal_checkpoint(database, NULL); 即可手动flush

结束


解决这几个问题后,就能成功读取通话日志了,其他的日志,如短信等等同理都可以读取到。

前言


epollLinux下的一个多路复用API,当处理大并发的事件时,其性能远强于古老的pollselectepoll可以工作在两种模式下,LT(水平触发)、ET(边缘触发),接下来将讨论这两种模式的区别。

ET(边缘触发)和LT(水平触发)


默认情况下,epoll采用LT模式工作,该模式支持阻塞和非阻塞套接字,如果想采用ET模式,可以使用EPOLLET参数。ET模式只支持非阻塞套接字,其效率要高于LT模式,两者的区别在于,当一个新的事件到来时,LTET模式下都可以通过epoll_wait方法来获取到这个事件,但是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait方法中获取到这个事件的;而LT模式则可以再次获取到,进而再次进行处理,其只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取到这个事件,因此,如果采用ET模式进行开发时,要特别注意,如果没有彻底的将缓冲区中的数据处理完,则会导致缓冲区中的用户请求得不到响应。

Nginx架构概述


传统基于进程或线程的模型(Apache就采用这种模型)在处理并发连接时会为每一个连接建立一个单独的进程或线程,且在网络或者输入/输出操作时阻塞。这将导致内存和CPU的大量消耗,因为新起一个单独的进程或线程需要准备新的运行时环境,包括堆和栈内存的分配,以及新的执行上下文,当然,这些也会导致多余的CPU开销。最终,会由于过多的上下文切换而导致服务器性能变差。
反过来,我们来看Nginx的架构设计,总结起来,它是模块化的、基于事件驱动、异步、单线程且非阻塞。
Nginx大量使用多路复用和事件通知,Nginx会创建几个数量有限(比如worker的数量和CPU的核数相同)的worker进程,每个worker进程包含一个高效的Run-loop,来处理多个网络连接,每个worker进程每秒能处理成千的并发连接。

代码结构

worker进程负责维护Run-loop,并且在请求处理的每个阶段执行相应模块代码,Nginx包括很多模块,如事件模块,状态处理模块,协议模块,负载均衡等。Nginx并不支持动态加载模块,模块只能在Nginx编译时进行添加进来。
在接收,处理和管理网络请求时,Nginx会根据操作系统的不同,采用特定的事件模型和磁盘I/O,以达到更高的性能,如LinuxSolaris以及基于BSD的操作系统,采用epollkqueueevent ports
Nginx的架构图如下:

阅读全文 »


iOS中,Hit-Testing主要用于决定哪个视图来首先处理Touch事件,确定完后,就会依据响应者链来进行事件的处理。接下来,我们将分析Hit-Testing的工作流程。

由于不确定的原因,Hit-Testing测试会被执行多次,导致单个视图的-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法会被调用多次,由于是幂等的,所以结果不影响。

Hit-Testing使用的搜索算法为逆前序深度遍历(逆前序遍历先访问根节点,然后进行从其索引最大的子视图到最小的子视图的遍历,既从右至左进行遍历)。当touch事件的points发生在多个视图的重叠部分时,根据算法将得到最右子树中的最深视图,而该视图就是位于界面最前端的视图。

下图展示了一个视图层级树以及对应在屏幕上的显示结果,树的分支顺序安排反应了子视图数组的顺序,比如View A的索引小于View B

阅读全文 »

基础概念


  • dylib的权限由它寄生的那个App决定,同一个dylib寄生在系统App和用户App里时的授权是不同的。
  • 越狱之后,我们就可以拥有daemon的概念了,给用户提供各种“守护”。
  • Class-dump处理的对象是Mach-O格式的二进制文件,如Framework的库文件和App的可执行文件。
  • 采用arm64架构的App不兼容armv7/armv7s架构。

命令


  • 查看app文件的可执行文件(对于App Store上下载的App,经过了加密,所以需要先砸壳才能获取到):plutil -p Info.plist | grep CFBundleExecutable

前言


在iOS开发中,很多都使用到SDWebImage库,该库使用了外观模式,提供简洁的API,UIButtonUIImageView等类可以直接调用API进行网络图片的下载,接下来,就来分析一下SDWebImage的整个工作流程。

整体结构


查看SDWebImage的整个项目结构,除去一些辅助的类,其结构大致如下图所示,UIView+WebCacheOperation类作为UIView的类别,提供基本的方法,如设置、取消、移除Operation操作,该Operation满足<SDWebImageOperation>协议,并通过关联引用来进行存储。
UIImageView+WebCacheSDWebImageManager等类提供对外的接口。
SDWebImageManager类将作为Manager来进行下载和缓存的管理,而真正负责下载的类为SDWebImageDownloader类,SDWebImageDownloaderOperation负责封装下载对象;管理缓存的类为SDImageCache
好了,大致结构就是这样,接下来将通过一次调用来分析其内部的具体流程。

阅读全文 »

在iOS越狱设备上添加Entitlements授权

院里的一款用于网络测试的APP,在iOS8.3之前,可以通过引入CoreTelephony框架,调用其私有函数来获取信号强度,MCC、MNC等相关参数,但在iOS8.3之后,无法通过私有API进行获取,APP开发团队一直没解决,Boss找到我,让我研究一下,看有没有解决方案,遂开始研究。
首先,经过多方的调研,发现在iOS8.3 above的系统是有解决方案的,参见Stackoverflow,大概思路是需要添加Entitlements授权,既在entitlements文件中添加如下的key,但是由于Apple 的CodeSign的机制,导致无法添加私有的Entitlements授权,所以需要绕过Apple的签名机制。

1
2
3
4
<key>com.apple.CommCenter.fine-grained</key>
<array>
<string>spi</string>
</array>

绕过签名机制

一开始,使用ldid来进行签名,签名之后会导致app闪退,遂使用了另外一种方法,禁用codesign,采用伪证书的方式,接下来将详细列出步骤。

阅读全文 »

前言

在做知乎日报的项目时,在其设置界面中有一个选项,当开启时,可以在移动网络状况下不下载图片。这个需求的解决方案有两种,一种是在每一个使用网络图片的地方,在下载前进行判断,如果当前网络为移动蜂窝网络,且开启了在移动网络状况下不下载图片的选项时,放弃图片的下载;还有一种方案就是直接在NSURLProtocol中进行截获,在+ (BOOL)canInitWithRequest:(NSURLRequest *)request中进行判断,当request.URL请求为图片,并开启不下载图片选项,且当前为移动网络状况下,截获该请求。在知乎日报项目中,我使用的第二种,既子类NSURLProtocol来实现请求拦截。

NSURLProtocol实现图片下载拦截

一开始,觉得思路很简单,直接创建PictureBlockURLProtocol类,其是NSURLProtocol的子类,在Implement实现中定义如下:

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
//获取当前网络状态
+ (BOOL)isBlockPictureDownload{
Reachability *reachability = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).reachability;

NetworkStatus netStatus = [reachability currentReachabilityStatus];

BOOL isBlock = netStatus == ReachableViaWWAN?YES : NO;

return isBlock;
}

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
if ([NSURLProtocol propertyForKey:HybridResourceProtocolKey inRequest:request]) {
return NO;
}

//满足条件时直接截获请求
if ([[UserConfig sharedInstance] isBlockPicture] && ([request.URL.pathExtension caseInsensitiveCompare:@"jpg"] == NSOrderedSame || [request.URL.pathExtension caseInsensitiveCompare:@"png"] == NSOrderedSame) && [self isBlockPictureDownload]) {
return YES;
}
return NO;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}

//没有实现内容,request会以请求超时的方式结束
- (void)startLoading{}

- (void)stopLoading{}

阅读全文 »

前言

俗话说,谷歌大法好,WebP是由谷歌公司推出的图片文件格式,支持有损、无损压缩。从官方文档,可以看到如下图所示的对比,大意就是压缩率比PNG、JPEG等高。腾讯也对WebP做了测试,测试链接请戳这里。现在,很多公司都已经开始使用WebP格式,国内如腾讯、淘宝、今日头条等等。

iOS WebP 示例

iOS原生肯定是不支持WebP格式的,接下来,将介绍三种方法来展示在iOS端如何支持WebP格式:

编译源代码

从官网下载WebP库的源代码,下载地址请戳这里,下载如下图所示的部分,下载后是一个tar文件,名字类似于

libwebp-0.5.1.tar,创建一个build文件夹,将该压缩文件放到build目录,然后再创建一个build.sh的shell文件,该文件用来生成通用的framework,代码如下:

阅读全文 »