Nginx 从入门到入坟(三)- Nginx 核心配置文件分析
1. Nginx目录结构分析
在使用 Nginx 之前,我们先对安装好的 Nginx 目录文件进行一个分析,在这块给大家介绍一个工具 tree,通过 tree 我们可以很方面的去查看 centos 系统上的文件目录结构,当然,如果想使用 tree 工具,就得先通过yum install -y tree
来进行安装,安装成功后,可以通过执行tree /usr/local/nginx
(tree 后面跟的是 Nginx 的安装目录),获取的结果如下:
- conf: Nginx 所有配置文件目录;
- fastcgi.conf: fastcgi 相关配置文件;
- fastcgi.conf.default: fastcgi.conf 的备份文件;
- fastcgi_params: fastcgi 的参数文件;
- fastcgi_params.default: fastcgi 的参数备份文件;
- scgi_params: scgi 的参数文件;
- scgi_params.default: scgi 的参数备份文件;
- uwsgi_params: uwsgi 的参数文件;
- uwsgi_params.default: uwsgi 的参数备份文件;
- mime.types: 记录的是 HTTP 协议中的 Content-Type 的值和文件后缀名的对应关系;
- mime.types.default: mime.types 的备份文件;
- nginx.conf: 这个是 Nginx 的核心配置文件,这个文件非常重要,也是我们后面要讲的重点;
- nginx.conf.default: nginx.conf 的备份文件;
- koi-utf、koi-win、win-utf 这三个文件都是与编码转换映射相关的配置文件,用来将一种编码转换成另一种编码;
- html: 存放 Nginx 自带的两个静态的html页面;
- 50x.html: 访问失败后的失败页面;
- index.html: 成功访问的默认首页;
- logs: 记录入门的文件,当 Nginx 服务器启动后,这里面会有 access.log、error.log 和 nginx.pid 三个文件出现;
- sbin: 是存放执行程序文件 nginx;
- nginx: 是用来控制 Nginx 的启动和停止等相关的命令。
2. Nginx核心配置文件结构
前面已经说过,Nginx 的核心配置文件默认是放在/usr/local/nginx/conf/nginx.conf
,下面我们就来说一下nginx.conf的内容和基本配置方法。
读取 Nginx 自带的 Nginx 配置文件,将其中的注释部分删除掉后,就剩下下面内容:
1 | worker_processes 1; |
1 | 指令名 指令值; # 全局块,主要设置Nginx服务器整体运行的配置指令 |
nginx.conf 配置文件中默认有三大块:全局块
、events块
、http块
。
http 块中可以配置多个 server 块,每个 server 块又可以配置多个 location 块。
2.1 全局块
2.1.1 user指令
user: 用于配置运行 Nginx 服务器的 worker 进程的用户和用户组。
语法 | user user [group] |
---|---|
默认值 | nobody |
位置 | 全局块 |
该属性也可以在编译的时候指定,语法如下./configure --user=user--group=group
,如果两个地方都进行了设置,最终生效的是配置文件中的配置。
使用 user 指令可以指定启动运行工作进程的用户及用户组,这样对于系统的权限访问控制的更加精细,也更加安全。
2.1.2 work process指令
master_process: 用来指定是否开启工作进程。
语法 | master_process on|off |
---|---|
默认值 | master_process on |
位置 | 全局块 |
worker_processes: 用于配置 Nginx 生成工作进程的数量,这个是 Nginx 服务器实现并发处理服务的关键所在。理论上来说 workder process 的值越大,可以支持的并发处理量也越多,但事实上这个值的设定是需要受到来自服务器自身的限制,建议将该值和服务器 CPU 的内核数保存一致。
语法 | worker_processes num/auto |
---|---|
默认值 | 1 |
位置 | 全局块 |
如果将 worker_processes 设置成 2,则会看到如下内容:
2.1.3 其他指令
daemon: 设定 Nginx 是否以守护进程的方式启动。
守护式进程是 linux 后台执行的一种服务进程,特点是独立于控制终端,不会随着终端关闭而停止。
语法 | daemon on|off |
---|---|
默认值 | daemon on |
位置 | 全局块 |
pid: 用来配置 Nginx 当前 master 进程的进程号 ID 存储的文件路径。
语法 | pid file |
---|---|
默认值 | 默认为:/usr/local/nginx/logs/nginx.pid |
位置 | 全局块 |
该属性可以通过./configure --pid-path=PATH
来指定。
error_log: 用来配置 Nginx 的错误日志存放路径。
语法 | error_log file [日志级别] |
---|---|
默认值 | error_log logs/error.log error |
位置 | 全局块、http、server、location |
该属性可以通过./configure --error-log-path=PATH
来指定。
其中日志级别的值有:debug|info|notice|warn|error|crit|alert|emerg
,翻译过来为调试|信息|通知|警告|错误|临界|警报|紧急
,这块建议大家设置的时候不要设置成 info 以下的等级,因为会带来大量的磁盘 I/O 消耗,影响 Nginx 的性能。
include: 用来引入其他配置文件,使 Nginx 的配置更加灵活。
语法 | include file |
---|---|
默认值 | 无 |
位置 | any |
2.2 events块
accept_mutex: 用来设置 Nginx 网络连接序列化。
语法 | accept_mutex on|off |
---|---|
默认值 | accept_mutex on |
位置 | events |
这个配置主要可以用来解决常说的 “惊群” 问题。大致意思是在某一个时刻,客户端发来一个请求连接,Nginx 后台是以多进程的工作模式,也就是说有多个 worker 进程会被同时唤醒,但是最终只会有一个进程可以获取到连接,如果每次唤醒的进程数目太多,就会影响 Nginx 的整体性能。如果将上述值设置为 on(开启状态),将会对多个 Nginx 进程接收连接进行序列号,一个个来唤醒接收,就防止了多个进程对连接的争抢。
multi_accept: 用来设置是否允许同时接收多个网络连接。
语法 | multi_accept on|off |
---|---|
默认值 | multi_accept off |
位置 | events |
如果 multi_accept 被禁止了,Nginx 一个工作进程只能同时接受一个新的连接。否则,一个工作进程可以同时接受所有的新连接。
worker_connections: 用来配置单个 worker 进程最大的连接数。
语法 | worker_connections number |
---|---|
默认值 | worker_commections 512 |
位置 | events |
这里的连接数不仅仅包括和前端用户建立的连接数,而是包括所有可能的连接数。另外,number 值不能大于操作系统支持打开的最大文件句柄数量。
use: 用来设置 Nginx 服务器选择哪种事件驱动来处理网络消息。
语法 | use method |
---|---|
默认值 | 根据操作系统定 |
位置 | events |
注意:此处所选择事件处理模型是 Nginx 优化部分的一个重要内容,method 的可选值有select/poll/epoll/kqueue
等,之前在准备 centos 环境的时候,我们强调过要使用 linux 内核在 2.6 以上,就是为了能使用 epoll 函数来优化 Nginx。
另外这些值的选择,我们也可以在编译的时候使用--with-select_module
、--without-select_module
、--with-poll_module、--without-poll_module
来设置是否需要将对应的事件驱动模块编译到 Nginx 的内核。
2.2.1 events指令配置实例
打开 Nginx 的配置文件 nginx.conf,添加如下配置:
1 | events{ |
启动测试:
1 | ./nginx -t |
2.3 http块
2.3.1 定义MIME-Type
我们都知道浏览器中可以显示的内容有 HTML、XML、GIF 等种类繁多的文件、媒体等资源,浏览器为了区分这些资源,就需要使用 MIME Type。所以说 MIME Type 是网络资源的媒体类型。Nginx 作为 web 服务器,也需要能够识别前端请求的资源类型。
在 Nginx 的配置文件中,默认有两行配置:
1 | include mime.types; |
default_type: 用来配置 Nginx 响应前端请求默认的 MIME 类型。
语法 | default_type mime-type |
---|---|
默认值 | default_type text/plain |
位置 | http、server、location |
在 default_type 之前还有一句include mime.types
,include 之前我们已经介绍过,相当于把 mime.types 文件中 MIMT 类型与相关类型文件的文件后缀名的对应关系加入到当前的配置文件中。
举例来说明:
有些时候请求某些接口的时候需要返回指定的文本字符串或者 json 字符串,如果逻辑非常简单或者干脆是固定的字符串,那么可以使用 Nginx 快速实现,这样就不用编写程序响应请求了,可以减少服务器资源占用并且响应性能非常快。
如何实现:
1 | location /get_text { |
2.3.2 自定义服务日志
Nginx 中日志的类型分 access.log、error.log。
access.log
:用来记录用户所有的访问请求。
error.log
:记录 Nginx 本身运行时的错误信息,不会记录用户的访问请求。
Nginx 服务器支持对服务日志的格式、大小、输出等进行设置,需要使用到两个指令,分别是 access_log 和 log_format 指令。
access_log: 用来设置用户访问日志的相关属性。
语法 | access_log path[format[buffer=size]] |
---|---|
默认值 | access_log logs/access.log combined; |
位置 | http , server , location |
log_format: 用来指定日志的输出格式。
语法 | log_format name [escape=default|json|none] string… |
---|---|
默认值 | log_format combined “…” |
位置 | http |
2.3.3 其他配置指令
sendfile: 用来设置 Nginx 服务器是否使用 sendfile() 传输文件,该属性可以大大提高 Nginx 处理静态资源的性能。
语法 | sendfile on|off |
---|---|
默认值 | sendfile off |
位置 | http、server、location |
keepalive_timeout: 用来设置长连接的超时时间。
语法 | keepalive_timeout time |
---|---|
默认值 | keepalive_timeout 75s |
位置 | http、server、location |
为什么要使用keepalive?
我们都知道 HTTP 是一种无状态协议,客户端向服务端发送一个 TCP 请求,服务端响应完毕后断开连接。
如何客户端向服务端发送多个请求,每个请求都需要重新创建一次连接,效率相对来说比较多,使用 keepalive 模式,可以告诉服务器端在处理完一个请求后保持这个 TCP 连接的打开状态,若接收到来自这个客户端的其他请求,服务端就会利用这个未被关闭的连接,而不需要重新创建一个新连接,提升效率,但是这个连接也不能一直保持,这样的话,连接如果过多,也会是服务端的性能下降,这个时候就需要我们进行设置其的超时时间。
keepalive_requests: 用来设置一个 keep-alive 连接使用的次数。
语法 | keepalive_requests number |
---|---|
默认值 | keepalive_requests 100 |
位置 | http、server、location |
2.4 server块和location块
server 块和 location 块都是比较重要的内容,因为后面会对 Nginx 的功能进行详细讲解,所以这块内容就放到静态资源部署的地方说明一下。
1 | server { |
2.4.1 server块的匹配逻辑
Nginx 在决定请求由哪个 server 块执行时,主要关注的是 server 块中的listen
和server_name
两个字段。
2.4.1.1 listen指令
listen 字段定义 server 响应的 ip 和端口,如果没有明确配置 listen 字段,默认监听0.0.0.0:80
(root 用户运行)或者0.0.0.0:8080
(非 root 用户运行)。
listen 可以被配置为:
- 一个 ip 和端口的组合;
- 一个单独的 ip,默认监听 80 端口;
- 一个单独的端口,默认监听所有的 ip 接口;
- 一个 Unix socket 路径;
其中最后一项通常只用于在不同的 server 之间传递请求。
选择要使用的 server 的规则如下:
- Nginx 首先将所有 “不完整” 的 listen 指令进行转换,比如没有 listen 字段的转换为
listen 0.0.0.0:80
,listen 1.1.1.1
转换为listen 1.1.1.1:80
等; - Nginx 根据请求的 ip 和端口创建一个与请求最匹配的 server 块列表,优先匹配指定了特定 ip 的 server 块,其次才会选择
listen 0.0.0.0
的这种 server 块,但是无论是哪种情况,端口必须是完全匹配的; - 如果只有一个最佳匹配,那么将使用匹配的 server 块响应请求,如果有多个 server 块具有相同级别的特异性匹配,Nginx 就开始评估每一个 server 块的
server_name
指令; - 注意:只有当 listen 指令无法找到最佳匹配时才会考虑评估 server_name 指令;
重要的是要理解,Nginx 仅 server_name 在需要区分与 listen 指令中特定级别的匹配的 server 块时才评估指令。比如下面的例子,如果 example.com 托管在 192.168.1.10 的端口 80 上,example.com 则在本示例中,尽管 server_name 第二个块中有指令,但对第一个块的请求始终会得到满足。
1 | server { |
如果多个 server 块以相同的特异性匹配,则下一步是检查 server_name 指令。
2.4.1.2 server_name指令
如果根据 listen 指令无法得到最佳匹配,将会开始解析 server_name 指令。Nginx 会检查请求中的 “Host” 头,这个值包含了客户端实际试图请求的域名或者 ip 地址,Nginx 会根据这个值去匹配 server_name 指令,匹配规则如下:
- Nginx 会尝试寻找一个和 sever_name 和 Host 值完全匹配的 server 块,如果找到多个精确匹配,则会使用第一个匹配的 server 块;
- 如果没有找到精确匹配的 server 块,则 nginx 尝试找到 server_name 带有
*
开头的 server 块,如果找到多个,则选择最长匹配的 server 块; - 如果没有找到使用
*
开头的 server 块,则会寻找以*
结尾的 server 块,同样,如果有多个匹配,选择最长匹配; - 如果没有找到使用
*
匹配的 server 块,则会寻找使用正则表达式(以~开头)定义 server_name 的 server 块,如果找到多个匹配,会使用第一个匹配; - 如果没有找到正则表达式匹配的 server 块,则 Nginx 将会为该 IP 地址和端口选择默认的 server 块。每一个 ip 和端口组合都可以配置一个且只能配置一个默认的 default_server 块,如果没有的话,则会选择可用列表中的第一个 server(此时的选择是随机的,顺序不固定);
示例如下:
- 准确的 server_name 匹配:
1
2
3
4
5server {
listen 80;
server_name www.domain.com;
. . .
} - 以
*
通配符开始的字符串:1
2
3
4
5server {
listen 80;
server_name *.domain.com;
. . .
} - 以
*
通配符结束的字符串:1
2
3
4
5server {
listen 80;
server_name www.*;
. . .
} - 匹配正则表达式:
1
2
3
4
5server {
listen 80;
server_name ~^(?.+).domain.com$;
. . .
} - 如果以上都没有匹配,则使用 default_server。如果没有指定 default_server,则会选择第一个可用的 server,我们可以指定对于没有匹配的 host 值时,返回错误到客户端。可以用来防止别人把垃圾流量转到你的网站。 通过返回 444 这个 Nginx 的非标准错误码让 Nginx 断开与浏览器的连接。
1
2
3
4
5server {
listen 80 default_server;
server_name _;
return 444;
}
2.4.1.3 实例示范
如果存在 server_name 与 “Host” 头值完全匹配的定义,则选择该服务器块来处理请求。
在此示例中,如果请求的 “Host” 头设置为host1.example.com
,则将选择第二台服务器:
1 | server { |
如果找不到完全匹配的内容,则 Nginx 然后检查是否存在 server_name 带有合适的起始通配符。以通配符开头的最长匹配将被选择来满足请求。
在此示例中,如果请求的 “Host” 头为www.example.org
,则将选择第二个服务器块:
1 | server { |
如果找不到与起始通配符匹配的内容,则 Nginx 将在表达式末尾使用通配符查看是否存在匹配项。此时,将选择以通配符结尾的最长匹配来满足请求。
例如,如果请求的 “Host” 标头设置为www.example.com
,则将选择第三个服务器块:
1 | server { |
如果找不到通配符匹配项,则 Nginx 将继续尝试匹配 server_name 使用正则表达式的指令。所述第一匹配正则表达式将被选择,以响应该请求。
例如,如果请求的 “Host” 标头设置为www.example.com
,则将选择第二个服务器块以满足请求:
1 | server { |
如果以上步骤均不能满足请求,则该请求将被传递到默认服务器以获取匹配的 IP 地址和端口。
2.4.2 location块的匹配逻辑
2.4.2.1 location块语法
location 块位于server 块(或其他 location 块)中,用于决定如何处理请求 URI(请求的一部分,位于域名或 IP 地址/端口之后)。
位置块通常采用以下形式:
1 | location optional_modifier location_match { |
在上面的 location_match 定义了 Nginx 应该针对什么样的请求 URI 检查。上例中修饰符的存在或不存在会影响 Nginx 尝试匹配 location 块的方式。
不同修饰符对 location 块的影响解释如下:
无
:如果不存在修饰符,则该位置将解释为前缀匹配。这意味着给定的 location 将与请求 URI 的开头进行匹配以确定匹配;=
:精确匹配,如果使用等号,则如果请求 URI 完全匹配给定的位置,则此块将被视为匹配;~
:如果存在波浪号修饰符,则此位置将被解释为区分大小写的正则表达式匹配(大小写敏感正则匹配);~*
:如果使用波浪号和星号修饰符,则位置块将被解释为不区分大小写的正则表达式匹配;^~
:如果存在 carat 和 tilde 修饰符,并且如果此块被选为最佳非正则表达式匹配,则不继续匹配后面的(前缀匹配)。
2.4.2.2 location块语法的示例
作为前缀匹配的一个例子,以下 location 块可以被选择为响应于请求的 URI 的样子/site
,/site/page1/index.html
或/site/index.html
:
1 | location /site { |
为了演示精确的请求 URI 匹配,此块将始终用于响应类似于/page1
。它不会用于响应/page1/index.html
请求 URI。请记住,如果选择此块并使用索引页完成请求,则内部重定向将发生到另一个位置,该位置将成为请求的实际处理程序:
1 | location = /page1 { |
作为应区分大小写的正则表达式的 location 示例,此块可用于处理对/tortoise.jpg
的请求,但不能用于处理/FLOWER.PNG
:
1 | location ~ .(jpe?g|png|gif|ico)$ { |
下面是一个类似于上面的不区分大小写的匹配块。在这里,无论是/tortoise.jpg
和/FLOWER.PNG
都可以通过此块处理:
1 | location ~* .(jpe?g|png|gif|ico)$ { |
最后,如果确定为最佳非正则表达式匹配,则此块将防止正则表达式匹配发生。它可以处理以下请求/costumes/ninja.html
:
1 | location ^~ /costumes { |
如上所述,修饰符对 location 块的不同影响。但是,这并没有告诉我们 Nginx 用于确定将请求发送到哪个 location 块的算法。
类似于 Nginx 用来选择处理请求的服务器块的过程,Nginx 也有一个既定的算法来决定服务器中的哪个 location 块用于处理请求。
2.4.2.3 Nginx如何选择用于处理请求的location
Nginx 选择将用于为请求提供 server 的 location,其方式与它选择 server 块的方式类似。它通过一个过程来确定任何给定请求的最佳 location 块。了解这个过程是能够可靠、准确地配置 Nginx 的关键要求。
考虑到我们上面描述的 location 声明的类型,Nginx 通过将请求 URI 与每个 location 进行比较来评估可能的 location 上下文。它使用以下算法执行此操作:
- Nginx 首先检查所有基于前缀的 location 匹配(所有不涉及正则表达式的 location 类型),它根据完整的请求 URI 检查每个 location;
- 首先,Nginx 寻找完全匹配。如果发现使用
=
修饰符的 location 块与请求 URI 完全匹配,则立即选择该位置块来处理请求; - 如果没有找到精确的(使用
=
修饰符)位置块匹配,Nginx 然后继续评估非精确前缀。它发现给定请求 URI 的最长匹配前缀位置,然后按如下方式评估:- 如果最长的匹配前缀 location 具有
^~
修饰符,则 Nginx 将立即结束其搜索并选择此 location 来满足请求; - 如果最长的匹配前缀 location 不使用
^~
修饰符,则该匹配项暂时由 Nginx 存储,以便可以移动搜索的焦点;
- 如果最长的匹配前缀 location 具有
- 确定并存储了最长的匹配前缀 location 后,Nginx 继续评估正则表达式 location(区分大小写和不区分大小写)。如果有任何的正则表达式的 location 包括 在最长前缀匹配的 location 里,Nginx 会将这些移动到它的正则表达式的 location 列表的顶部进行检查。然后,Nginx 尝试顺序匹配正则表达式 location。第一个匹配上请求 URI 的正则表达式的 location 被立即选择来服务该请求;
- 如果未找到与请求 URI 匹配的正则表达式的 location,则选择先前存储的前缀 location 来处理请求;
这里要理解的是,默认情况下,Nginx 将优先于前缀匹配提供正则表达式匹配。但是,它首先评估前缀位置,允许管理员通过使用=
和^~
修饰符指定位置来覆盖这种趋势。
同样要注意的是,尽管前缀 location 通常是根据最长,最具体的匹配选择的,但是当找到第一个匹配的 location 时,正则表达式评估就会停止。这意味着配置中的定位对于正则表达式 location 具有广泛的意义。
最后,重要的是要理解,当 Nginx 评估正则表达式 location 时,最长前缀匹配内的正则表达式匹配将 “插队”。在考虑其他任何正则表达式匹配项之前,将按顺序评估这些值。
2.4.2.4 location中的反斜线
1 | location /test { |
- 不带
/
:当访问 ip 地址 /test 时, Nginx 先找是否有 test 目录,如果有则找 test 目录下的 index.html;如果没有 test 目录, nginx 则会找是否有 test 文件; - 带
/
:当访问 ip 地址 /test 时, Nginx 先找是否有 test 目录,如果有则找 test 目录下的 index.html,如果没有它也不会去找是否存在 test 文件。
2.4.2.5 设置请求资源的目录
root: 设置请求的根目录。
语法 | root path |
---|---|
默认值 | root html |
位置 | http、server、location |
path 为 Nginx 服务器接收到请求以后查找资源的根目录路径。
alias: 用来更改 location 的 URI。
语法 | alias path |
---|---|
默认值 | - |
位置 | location |
path 为修改后的根路径。
以上两个指令都可以来指定访问资源的路径,那么这两者之间的区别是什么?
举例说明:
- 在
/usr/local/nginx/html
目录下创建一个images
目录,并在目录下放入一张图片mv.png
图片:访问图片的路径为:1
2
3location /images {
root /usr/local/nginx/html;
}1
http://localhost/images/mv.png
- 如果把 root 改为 alias: 再次访问上述地址,页面会出现 404 的错误,查看错误日志会发现是因为地址不对,所以验证了:
1
2
3location /images {
alias /usr/local/nginx/html;
}需要在 alias 后面路径改为:1
2
3
4root的处理结果是: root路径+location路径
/usr/local/nginx/html/images/mv.png
alias的处理结果是:使用alias路径替换location路径
/usr/local/nginx/html/images1
2
3location /images {
alias /usr/local/nginx/html/images;
} - 如果 location 路径是以
/
结尾,则 alias 也必须是以/
结尾,root 没有要求,将上述配置修改为:访问就会出问题,查看错误日志还是路径不对,所以需要把 alias 后面加上1
2
3location /images/ {
alias /usr/local/nginx/html/images;
}/
。
总结:
1 | root的处理结果是: root路径+location路径 |
2.4.2.6 index指令和error_page指令
index: 设置网站的默认首页。
语法 | index file … |
---|---|
默认值 | index index.html |
位置 | http、server、location |
index 后面可以跟多个设置,如果访问的时候没有指定具体访问的资源,则会依次进行查找,找到第一个为止。
举例说明:
1 | location / { |
访问该 location 的时候,可以通过http://ip:port/
,地址后面如果不添加任何内容,则默认依次访问index.html
和index.htm
,找到第一个来进行返回。
error_page: 设置网站的错误页面。
语法 | error_page code … [=[response]] uri |
---|---|
默认值 | - |
位置 | http、server、location… |
当出现对应的响应code后,如何来处理。
举例说明:
- 可以指定具体跳转的地址:
1
2
3server {
error_page 404 http://www.itcast.cn;
} - 可以指定重定向地址:
1
2
3
4
5
6
7server{
error_page 404 /50x.html;
error_page 500 502 503 504 /50x.html;
location =/50x.html{
root html;
}
} - 使用 location 的 @ 符合完成错误信息展示: 可选项
1
2
3
4
5
6
7server{
error_page 404 @jump_to_error;
location @jump_to_error {
default_type text/plain;
return 404 'Not Found Page...';
}
}=[response]
的作用是用来将相应代码更改为另外一个这样的话,当返回 404 找不到对应的资源的时候,在浏览器上可以看到,最终返回的状态码是 200,这块需要注意下,编写 error_page 后面的内容,404 后面需要加空格,200 前面不能加空格。1
2
3
4
5
6server{
error_page 404 =200 /50x.html;
location =/50x.html{
root html;
}
}
参考文献
【1】https://www.bilibili.com/video/BV1yS4y1N76R?spm_id_from=333.337.search-card.all.click
【2】https://www.bilibili.com/video/BV1ov41187bq?spm_id_from=333.337.search-card.all.click
【3】https://www.bnskd.com/2813.html
【4】http://www.lmonkey.com/t/2zLAPYMyW
【5】https://my.oschina.net/python8/blog/5373448
【6】https://blog.csdn.net/qq_22783587/article/details/118891858
【7】https://blog.csdn.net/cr7258/article/details/117486459