【Nginx11】Nginx学习:HTTP核心模块(八)文件处理
Nginx学习:HTTP核心模块(八)文件处理
继续我们的 HTTP 核心模块之旅。今天主要是文件相关的一些处理操作,包括 DirectIO、文件缓存以及 sendfile 相关的配置。这三个配置中,大家应该会见过 sendfile ,但是另外两个就比较少见了。包括我之前也从来没见过,不过还好,DirectIO 并不是一个完全的陌生人,文件缓存优化也与操作系统基础知识有关,而 sendfile 一般默认就是开启的,所以大家也不要有太大的压力哦。
directio
是不是看着很眼熟,没错,早前我们在 PHP 的小课堂文章中学习过。没印象或者想不起来的小伙伴可以移步 PHP中DirectIO直操作文件扩展的使用https://mp.weixin.qq.com/s/fS6X2IlrnrBrBZwwqRF3vA (去博客搜索也可以哦) 去回忆一下哦。别的不多说了,直接来看看相关的配置。由于不知道要怎么测试,所以就简单地介绍一些这些配置就好了,如果有小伙伴了解这一块要怎么测试的,可以评论区留言哦。
directio
当读入长度大于等于指定 size 的文件时,开启 DirectIO 功能。
directio size | off;
具体的做法是, 在 FreeBSD 或 Linux 系统开启使用 O_DIRECT 标志, 在 Mac OS X 系统开启使用 F_NOCACHE 标志, 在 Solaris 系统开启使用 directio() 功能。这条指令自动关闭 sendfile(0.7.15版) 。它在处理大文件时 directio 4m; 或者在 Linux 系统使用 aio 时比较有用。默认 off 。
directio_alignment
为 DirectIO 设置文件偏移量对齐。
directio_alignment size;
大多数情况下,按512字节对齐足矣, 但在 Linux 系统下使用 XFS ,需要将值扩大到 4K 。
文件优化缓存
这个缓存是个什么东西呢?它可以用于减少 Nginx 的系统调用,缓存文件句柄、大小和修改时间等。具体作用我们在最后会看到。
open_file_cache
用于配置文件缓存。
open_file_cache off;
open_file_cache max=N [inactive=time];
默认是 off ,也就是关闭状态的。可配置的值包括:
max=N,设置缓存中元素的最大数量,当缓存溢出时,使用 LRU(最近最少使用) 算法删除缓存中的元素
inactive=time,在这段时间内缓存元素如果没有被访问,将从缓存中删除。默认超时是60秒
它可以缓存的内容包括:
打开文件的描述符,大小和修改时间
目录查找结果
文件查找时的错误结果,诸如“file not found”(文件不存在)、“no read permission”(无读权限) 等等
在使用文件缓存的时候,最好 open_file_cache_errors 也开启,这个命令我们后面马上会说。
open_file_cache_errors
开启或者关闭缓存文件查找的错误结果
open_file_cache_errors on | off;
默认值是 off ,如果确定要使用文件缓存的话,最好把它也打开。
open_file_cache_min_uses
设置在由 open_file_cache 指令的 inactive 参数配置的超时时间内, 文件应该被访问的最小 number(次数) 。
open_file_cache_min_uses number;
如果访问次数大于等于此值,文件描述符会保留在缓存中,否则从缓存中删除。
open_file_cache_valid
设置检查 open_file_cache 缓存的元素的时间间隔。
open_file_cache_valid time;
默认 60s 检查一次。
配置测试
首先使用 strace 命令追踪一下当前 Nginx 的 Worker 进程,为了方便测试,咱们可以把 worker_processes 设置为 1 ,只启动一个工作进程。然后随便请求一个 URL 就会出现下面的内容。
[root@localhost html]# strace -p 2365
strace: Process 2365 attached
epoll_wait(15, [{EPOLLIN, {u32=2915635216, u64=140495590854672}}], 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(54245), sin_addr=inet_addr("192.168.56.1")}, [112->16], SOCK_NONBLOCK) = 5
epoll_ctl(15, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2915635912, u64=140495590855368}}) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 60000) = 1
recvfrom(5, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa", O_RDONLY|O_NONBLOCK) = 6
fstat(6, {st_mode=S_IFDIR|0755, st_size=24, ...}) = 0
close(6) = 0
// =======注意这里=======
writev(5, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(7, "192.168.56.1 - - [07/Aug/2022:22"..., 102) = 102
setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(5, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
stat("/usr/local/nginx/html/aaa/index.html", {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa/index.html", O_RDONLY|O_NONBLOCK) = 6
fstat(6, {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
writev(5, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(5, 6, [0] => [10], 10) = 10
write(7, "192.168.56.1 - - [07/Aug/2022:22"..., 125) = 125
close(6) = 0
epoll_wait(15,
主要需要看的就是上面注释中的部分,有三个操作,分别是 openat、fstat 和 close 操作,分别对应着打开文件句柄、获取文件统计信息以及关闭文件句柄这三个操作。目前的情况下,多次访问,还是一样的结果,每次都会有这三个步骤。
接下来,我们就简单配置下文件缓存,直接使用官方文档中提供的示例配置。这几个命令可以配置在 http、server、location 中,我们就简单地在 server 中进行配置吧。
server {...open_file_cache max=1000 inactive=20s;open_file_cache_valid 30s;open_file_cache_min_uses 2;open_file_cache_errors on;...
}
然后再次访问,第一次还是会正常出现 openat、fstat 和 close 这三个系统函数的调用。但是之后再次访问,就会发现这三个系统调用不见了。
[root@localhost html]# strace -p 2423
strace: Process 2423 attached
epoll_wait(15, [{EPOLLIN, {u32=2915635216, u64=140495590854672}}], 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(65255), sin_addr=inet_addr("192.168.56.1")}, [112->16], SOCK_NONBLOCK) = 7
epoll_ctl(15, EPOLL_CTL_ADD, 7, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2915635912, u64=140495590855368}}) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 60000) = 1
recvfrom(7, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa", O_RDONLY|O_NONBLOCK) = 12
fstat(12, {st_mode=S_IFDIR|0755, st_size=24, ...}) = 0
close(12) = 0
// =======注意这里=======
writev(7, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 102) = 102
setsockopt(7, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
// =======注意这里=======
openat(AT_FDCWD, "/usr/local/nginx/html/aaa/index.html", O_RDONLY|O_NONBLOCK) = 12
fstat(12, {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
// =======注意这里=======
writev(7, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(7, 12, [0] => [10], 10) = 10
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 125) = 125
// 这里开始是后续访问
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa HTTP/1.1\r\nTEST_UNDERLIN"..., 1024, 0, NULL, NULL) = 224
writev(7, [{iov_base="HTTP/1.1 301 Moved Permanently\r\n"..., iov_len=200}, {iov_base="<html>\r\n<head><title>301 Moved P"..., iov_len=116}, {iov_base="<hr><center>nginx/1.23.0</center"..., iov_len=53}], 3) = 369
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 102) = 102
epoll_wait(15, [{EPOLLIN, {u32=2915635912, u64=140495590855368}}], 512, 65000) = 1
recvfrom(7, "GET /aaa/ HTTP/1.1\r\nTEST_UNDERLI"..., 1024, 0, NULL, NULL) = 260
writev(7, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=235}], 1) = 235
sendfile(7, 12, [0] => [10], 10) = 10
write(5, "192.168.56.1 - - [07/Aug/2022:23"..., 125) = 125
epoll_wait(15,
很明显,这就是文件缓存在起作用。减少了文件相关的系统调用读取的次数。为什么我们上面访问的内容会有两遍请求呢?我访问的是 /aaa 目录,直接访问目录会找这个目录下面的 index.html 文件,因此有一次 301 跳转。你也可以直接访问 /aaa/index.html 就会看得更清楚一些了。
这一套文件缓存不会缓存文件的具体内容,而只是操作符句柄及文件的一些统计信息,Nginx 虽然已经对静态内容做过优化。但在高流量网站的情况下,仍然可以使用 open_file_cache 进一步提高性能,减少系统调用。通过扩大这个缓存的容量可以提高线上的实际命中率。但是缓存容量并不是越大越好,比如当达到 20000 个元素的容量时,共享内存的锁就成了瓶颈。所以,可以在确定访问频次非常高的静态文件 location 或者 server 上开启这一套文件缓存,数量也不用太多,可以让性能有更进一步的提升。
PHP-FPM 或者反向代理之类的和这个文件缓存就没啥关系了,PHP-FPM 走的是 socket 句柄,通过连接 PHP-FPM 进行操作,而打开 php 文件的是 PHP-FPM ,不是 Nginx 。
sendfile
这一块又要牵涉到操作系统了。在操作系统编程中,sendfile() 是系统调用,而 read() 这种是函数调用,因此,使用 sendfile() 对于文件读取来说会带来性能的提升。
读取发送文件的时候,使用了 sendfile() 那么 Nginx 就会直接向系统内核发送指令,然后发送文件也是系统内核直接完成,只有一次复制操作,实现了异步网络 IO 的形式。而传统情况则是从磁盘中以流的形式加载文件,然后再将文件流复制到系统内核中,内核再发送。区别就在这里。其实就是说,传统方式在读写文件时,会从硬盘到系统空间,再到用户空间这样走一圈,而使用 sendfile() 则只在系统空间,不用走到用户空间,从而实现零拷贝,减少空间切换的消耗。
当然,sendfile 只对静态网站有用,也就是确实需要进行文件读写发送的。如果是动态网站,比如 FastCGI 或反向代理的,对接的实际上是 socket 接口,真正的文件处理是在动态语言中进行的,比如 PHP 的模板文件加载等。因此,它只对静态页面或文件有性能提升的效果。
sendfile
开启或关闭使用 sendfile()
调用。
sendfile on | off;
现在默认就是打开的,从 nginx 0.8.12 和 FreeBSD 5.2.1 开始,可以使用 aio 预加载 sendfile的数据,Linux 没有哦。
send_lowat
如果设置成非 0 值,Nginx 将尝试最小化向客户端发送数据的次数。
send_lowat size;
默认值是 0 ,它是通过将 kqueue 方法的 NOTE_LOWAT 标志, 或者将套接字的 SO_SNDLOWA T属性设置成指定的 size 实现的。这条指令在Linux、Solaris和Windows操作系统无效。
sendfile_max_chunk
设置为非0值时,可以限制在一次 sendfile() 调用时传输的数据量。
sendfile_max_chunk size;
最新的版本默认 2m,如果不进行限制,一个快速的连接可能会霸占整个worker进程的所有资源。在版本1.21.4之前,默认情况下是 0 ,没有限制。
总结
好吧好吧,今天的内容有点水,因为我确实不知道 DirectIO 和 sendfile 在 Nginx 中要怎么测试。如果有了解过的小伙伴记得评论留言哦。不过通常来说,我们会在刚安装好的 Nginx 配置文件中看到 sendfile 是 on 的状态,一般来说,不懂的就别瞎配了,咱惹不起还躲不起嘛,其它的配置让它们就走默认好了。还是那句话,留个印象,将来如果有用到的时候,能够想起来并且查资料能有个方向就够了。毕竟,现在的水平只到这里,谁知道将来我们能不能成为大神呢,要是真成了,再回来好好补上呗!
参考文档:
http://nginx.org/en/docs/http/ngx_http_core_module.html
相关文章:
【Nginx11】Nginx学习:HTTP核心模块(八)文件处理
Nginx学习:HTTP核心模块(八)文件处理 继续我们的 HTTP 核心模块之旅。今天主要是文件相关的一些处理操作,包括 DirectIO、文件缓存以及 sendfile 相关的配置。这三个配置中,大家应该会见过 sendfile ,但是另…...

STM32MP157驱动开发——按键驱动(休眠与唤醒)
文章目录 “休眠-唤醒”机制:APP执行过程内核函数休眠函数唤醒函数 休眠与唤醒方式的按键驱动程序(stm32mp157)驱动程序框架button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “休眠-唤醒”机制: 当应用程序必须等待某个事件发生,…...
全面解析 SOCKS5 代理与 HTTP 代理的对比与应用
一、 SOCKS5 代理与 HTTP 代理的基本原理 SOCKS5 代理:SOCKS5 是一种网络协议,它可以在传输层(Transport Layer)代理 TCP 和 UDP 请求。SOCKS5 代理不解析请求内容,而是直接将数据中转至目标服务器,支持更广…...
STM32 HEX文件和BIN文件格式区别keil中的配置与生成
一、区别 HEX 文件: 是包括地址信息的,在烧写或下载HEX文件的时候,一般都不需要用户指定地址,因为HEX文件内部的信息已经包括了地址。HEX文件是用ASCII来表示二进制的数值。例如一般8-BIT的二进制数值0x3F,用ASCII来表示就需要分别表示字符3和字符F,每个字符需要一个BYTE…...
RabbitMQ优先级队列的使用
RabbitMQ优先级队列的使用 生产者 public class PriorityQueue {public static void Send(){string path AppDomain.CurrentDomain.BaseDirectory;string tag path.Split(/, \\).Last(s > !string.IsNullOrEmpty(s));Console.WriteLine($"这里是 {tag} 启动了。。&…...

MAC 推送证书不受信任
配置推送证书的时候,一打开就变成不受信任,搜了很多解决版本。 由于苹果修改相关规定,推送证书 打开Apple PKI - Apple 下载AppleWWDRCA文件,选择G4,双击安装之后,证书已经变为受信任。 AppleWWDRCA(Apple Worldwid…...
Gitee创建分支
在使用Gitee进行代码托管时,分支是一个非常重要的概念。它可以让我们在不同的开发阶段、不同的团队成员之间协作开发,提高团队工作效率。因此,下面将介绍如何在Gitee仓库中建立分支。 一、在Gitee上创建新的分支 在讲解如何在Gitee上创建新…...

集群间ssh配置免密登录
ssh免密配置,可以将ssh生成的密钥分发给目标主机,之后再用ssh访问目标主机时就无需输入密码 下面我们来配置用centos71免密登录centos72主机 使用下面指令生成一个密钥 ssh-keygen其中会提示,是否输入密码短语,这里不输入&#…...

YOLOV8改进:CVPR 2023 | SCConv: 即插即用的空间和通道重建卷积
1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:添加 SCConv,经过测试,有效涨点。…...

人员定位安全管控系统:提升安全管理水平的智能解决方案
在当今社会,人员安全管理成为各行各业关注的焦点。为了保障人员的安全和提高管理效率,人员定位安全管控系统应运而生。 人员定位安全管控系统采用多种定位技术来实现对人员位置的准确定位,如GPS(全球定位系统)、Wi-Fi…...

数据结构(二)
目录 Trie树 并查集 堆 Trie树 作用:用来高效地存储和查找字符串集合的数据结构 基本形式: 模板代码如下: #include<iostream> using namespace std;const int N 100010;//idx代表当前用到哪个下标 //既是根节点,又是空节点 //cnt存储的是以当前点结尾的…...
logback 自定义log字段(MDC)推送到logstash(spring boot + logback+ logstash)
直接上代码: 1.创建FIlter,往 MDC 里面追加内容 WebFilter Component public class LogBackFilter implements Filter {Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic void doFilter(ServletRequest…...
1253. 重构 2 行二进制矩阵
1253. 重构 2 行二进制矩阵 给你一个 2 行 n 列的二进制数组: 矩阵是一个二进制矩阵,这意味着矩阵中的每个元素不是 0 就是 1。第 0 行的元素之和为 upper。第 1 行的元素之和为 lower。第 i 列(从 0 开始编号)的元素之和为 cols…...

安全—01day
文章目录 1. 编码1.1 ASCLL编码1.2 URL编码1.3 Unicode编码1.4 HTML编码1.5 Base64编码 2. form表单2.1 php接收form表单2.2 python接收form表单 1. 编码 1.1 ASCLL编码 ASCII 是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的…...
【Git】Please commit your changes or stash them before you merge的解决方法
背景 我从远程库中clone了一个项目进行开发,修改了一部分代码后,远程库有更新,我想将远程更新拉取下来,并且保留自己的更改,使用git pull origin master命令,有报错: error: Your local chang…...

网卡收发包系统结构收发包流程,tcp/ip协议,socket套接字缓冲区,滑动窗口,mtu/mss
MTU和MSS的区别 MTU和MSS的区别 TCP 的 MTU & MSS MTU是在那一层?MSS在那一层? MTU是在数据链路层的载荷大小也就是传给网络层的大小,mss是在传输层的载荷大小也就是传给应用层的大小 mss是根据mtu得到的 1、MTU: Maximu…...
VUE之axios使用,跨域问题,拦截器添加Token
参考资料: 参考视频 视频资料及个人demo Axios中文文档 VUE之基本部署及VScode常用插件 VUE之基本组成和使用 VUE之Bootstrap和Element-UI的使用 准备工作: 关于SpringBoot和SpringCloud的搭建,以及mybatis-plus的整合见本人之前的CSDN博客,下面编写get请求和post请求…...
阿里云函数计算签名认证(iOS实现细节备注)
1、使用第三方库 AFNetworking进行网络请求。 2、阿里云函数计算签名认证文档 3、文档中添加 CanonicalizedFCHeaders 可以不用添加,CanonicalizedResource如何没有设置Path,在末尾加入“/”就可以了。 4、主要还是 hmac-sha256 签名认证,在实…...

成都爱尔蔡裕:泡在“糖”里的脆弱血管,暴露在眼睛深处
糖尿病是一组由多病因引起的以慢性高血糖为特征的终身性代谢性疾病。长期血糖增高,大血管、微血管受损并危及心、脑、肾、周围神经、眼睛、足等。医生临床数据显示,糖尿病发病后10年左右,将有30%~40%的患者至少会发生一种并发症&a…...
神经网络小记-过拟合与欠拟合
过拟合 过拟合(Overfitting)是机器学习和深度学习中常见的问题,指模型在训练数据上表现得非常好,但在新数据上表现较差,即模型过度拟合了训练数据的特征,导致泛化能力不足。 解决过拟合的方式包括以下几种…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...

Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...

【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...