手头有个项目的磁盘占用空间日益见涨，已经逼近 90% 的警戒线。登上去排查了下，一半都是被图片给占用了。既然是图片，我琢磨着是不是可以把这些文件以及相关功能都迁移到云存储上去。服务器是阿里云的，所以这是一份 OSS 的折腾记录。选择同区域 OSS阿里云的 OSS 收费项分的很细，存储是存储的，流量是流量的。存储费按使用的容量来计算，用多少收多少，这个很直观，也比较好掌控。流量费就不好说了，图片一不小心被盗链，或是其他原因流量突然暴增，会带来一些预料之外的开支。好在 OSS 提供了一个比较良心的选择：内网流量免费。通过这个规则，我只需要让访问 OSS 的流量都走内网，就不用担心流量方面的开支了。那么问题来了：如何打通 OSS 的内网访问流量？两个前提：阿里云服务器 1 个。同区域的 OSS 1 个。注意我加粗了的「同区域」。举个例子，如果服务器在杭州，OSS 买到了深圳，按照 OSS 的收费规则，这是跨区域，存在流量费用。如果非要这么安排，但又想减省一点流量费，那就只有在 OSS 所在区域再买一台服务器，然后再通过公网流量连接另一台服务器。这还是会有点得不偿失，因为阿里云加的服务器也不便宜。Nginx 代理配置准备好了同区域的服务器和 OSS 后，接下来就是找个软件，让服务器转发一下访问 OSS 文件的请求。这里我用的是 Nginx。在 Nginx 上要实现这个功能只需要在 server 区域中添加下面几行代理配置：location /upload/ { proxy_pass http://test.oss-cn-hangzhou-internal.aliyuncs.com/; proxy_hide_header Content-Disposition; }注意上面配置中的 test 要替换为自己在 OSS 创建的 Bucket 名称。通过浏览器访问 OSS 上的图片文件时，默认是下载的形式，我希望直接在浏览器显示，proxy_hide_header Content-Disposition; 就是这个用途。完成以上配置后重启 Nginx，通过服务器的地址 + /upload/ + OSS 上存在的文件名，就能走内网方式访问 OSS 的文件了。这一步可能会碰到 OSS 输出 AccessDenied 类似的错误，如果错误消息中有类似 ACL 之类的字眼，可以检查一下创建的 Bucket 是不是「私有」级别权限。这个权限安全级别较高，需要请求中附带一些认证字段才能正常访问。那相应的也需要特殊版本的 Nginx 配合相关插件才能搞定。我不想这么麻烦，所以需要把 Bucket 的 ACL 权限设置为「公共读」。禁止 OSS 直访在使用了「公共读」的权限后，就违背了折腾的初衷。因为这意味着 OSS 上的文件都有了一个公网可访问的地址，这是一个会导致 OSS 流量费的隐患，所以需要堵住这个入口。OSS 支持通过 Referer 方式设置防盗链功能。通过此功能设置一个 IP 白名单，内容就是服务器的访问地址，然后完善一下刚才的 Nginx 代码：location /upload/ { proxy_pass http://test.oss-cn-hangzhou-internal.aliyuncs.com/; proxy_hide_header Content-Disposition; proxy_set_header referer $host; }这样就只能通过服务器的请求来访问 OSS 的文件了。使用 OSS 缩略图功能OSS 自带了缩略图功能，但使用的方式有点特别。是通过在文件地址后面添加处理参数的方式来操作的。类似于这样：/upload/1.jpg?x-oss-process=image/resize,m_fill,w_100,h_100这会输出长宽都是 100 像素的小图。不可否认，这种方式用起来是很方便，但就像我在开头所说的，OSS 的收费项很细，这个图片处理也会产生费用。如果让这个功能直接暴露出来，一样是一个费用隐患。所以我想把这个功能包装起来，让用法看起来如下：/upload/1-100.jpg这要用到 Nginx 的 Rewrite 功能。调整后的 Nginx 配置如下：location /upload/ { rewrite /(.*)-100.(.*) /$1.$2?x-oss-process=image/resize,m_fill,w_100,h_100 break; proxy_pass http://test.oss-cn-hangzhou-internal.aliyuncs.com/; proxy_hide_header Content-Disposition; proxy_set_header referer $host; }这样缩略图的处理就在自己掌控之中了。文章引用于：https://www.zzxworld.com/posts/config-nginx-proxy-to-access-aliyun-internal-oss-bucket
今天，我读到一篇好文章。它用图片通俗易懂地解释了，"数字签名"（digital signature）和"数字证书"（digital certificate）到底是什么。我对这些问题的理解，一直是模模糊糊的，很多细节搞不清楚。读完这篇文章后，发现思路一下子就理清了。为了加深记忆，我把文字和图片都翻译出来了。文中涉及的密码学基本知识，可以参见我以前的笔记。====================================================数字签名是什么？作者：David Youd翻译：阮一峰原文网址：http://www.youdzone.com/signature.html1.鲍勃有两把钥匙，一把是公钥，另一把是私钥。2.鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。3.苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密，就可以达到保密的效果。4.鲍勃收信后，用私钥解密，就看到了信件内容。这里要强调的是，只要鲍勃的私钥不泄露，这封信就是安全的，即使落在别人手里，也无法解密。5.鲍勃给苏珊回信，决定采用"数字签名"。他写完后先用Hash函数，生成信件的摘要（digest）。6.然后，鲍勃使用私钥，对这个摘要加密，生成"数字签名"（signature）。7.鲍勃将这个签名，附在信件下面，一起发给苏珊。8.苏珊收信后，取下数字签名，用鲍勃的公钥解密，得到信件的摘要。由此证明，这封信确实是鲍勃发出的。9.苏珊再对信件本身使用Hash函数，将得到的结果，与上一步得到的摘要进行对比。如果两者一致，就证明这封信未被修改过。10.复杂的情况出现了。道格想欺骗苏珊，他偷偷使用了苏珊的电脑，用自己的公钥换走了鲍勃的公钥。此时，苏珊实际拥有的是道格的公钥，但是还以为这是鲍勃的公钥。因此，道格就可以冒充鲍勃，用自己的私钥做成"数字签名"，写信给苏珊，让苏珊用假的鲍勃公钥进行解密。11.后来，苏珊感觉不对劲，发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法，要求鲍勃去找"证书中心"（certificate authority，简称CA），为公钥做认证。证书中心用自己的私钥，对鲍勃的公钥和一些相关信息一起加密，生成"数字证书"（Digital Certificate）。12.鲍勃拿到数字证书以后，就可以放心了。以后再给苏珊写信，只要在签名的同时，再附上数字证书就行了。13.苏珊收信后，用CA的公钥解开数字证书，就可以拿到鲍勃真实的公钥了，然后就能证明"数字签名"是否真的是鲍勃签的。14.下面，我们看一个应用"数字证书"的实例：https协议。这个协议主要用于网页加密。15.首先，客户端向服务器发出加密请求。16.服务器用自己的私钥加密网页以后，连同本身的数字证书，一起发送给客户端。17.客户端（浏览器）的"证书管理器"，有"受信任的根证书颁发机构"列表。客户端会根据这张列表，查看解开数字证书的公钥是否在列表之内。18.如果数字证书记载的网址，与你正在浏览的网址不一致，就说明这张证书可能被冒用，浏览器会发出警告。19.如果这张数字证书不是由受信任的机构颁发的，浏览器会发出另一种警告。20.如果数字证书是可靠的，客户端就可以使用证书中的服务器公钥，对信息进行加密，然后与服务器交换加密信息。（完）
git 的教程可以查看廖雪峰老师的官网：https://www.liaoxuefeng.com/wiki/896043488029600，讲解的非常详细也非常好，基本可以满足正常的工作需求使用。基本流程就是：1、新建本地开发分支（比如 yangzhenhua 分支），提交也是提交的开发分支。2、本地测试无问题之后合并到 master 分支。3、然后 git push origin master 分支到 git 服务器。4、服务器钩子自动 git pull origin master 分支到项目目录。这样我们就在 yangzhenhua 分支工作开发，然后提交也可以提交到 yangzhenhua 分支上，因为服务器钩子虽然是本地提交才会触发，但是钩子是更新的 master 分支，所以提交开发分支并不会影响生产环境项目目录，什么时候想上线了，直接本地分支合并到 master 分支然后 git push origin master 分支就可以了。准备工作1、服务器端安装 git[root@localhost ~]# yum install git [root@localhost ~]# git version2、客户端安装 git我是 windows 环境直接在https://git-scm.com/downloads下载客户端安装，然后使用 Git Bash 作为命令行客户端。安装完成后可以查看下 git 版本。$ git version git version 2.9.2.windows.1搭建 git 服务器1、服务器端创建 git 用户来管理 git 服务[root@localhost ~]# id git id: git: no such user [root@localhost ~]# useradd git [root@localhost ~]# passwd git New password: BAD PASSWORD: The password contains the user name in some form Retype new password: passwd: all authentication tokens updated successfully.2、创建证书登录[root@localhost ~]# cd /home/git/ [root@localhost git]# sudo -u git mkdir .ssh [root@localhost git]# chmod 700 .ssh/ [root@localhost git]# sudo -u git touch .ssh/authorized_keys [root@localhost git]# chmod 600 .ssh/authorized_keys3、服务器端创建 git 服务器[root@localhost ~]# mkdir -p /home/data/git/gittest.git [root@localhost ~]# git init --bare /home/data/git/gittest.git/ Initialized empty Git repository in /home/data/git/gittest.git/ [root@localhost home]# cd /home/data/git/ [root@localhost git]# ll 总用量 4 drwxr-xr-x 7 root root 4096 5月 9 01:17 gittest.git [root@localhost git]# chown -R git:git gittest.git/ [root@localhost git]# ll 总用量 4 drwxr-xr-x 7 git git 4096 5月 9 01:17 gittest.git我们默认设置 /home/data/git/gittest.git 为 git 仓库，当我们执行完 git init --bare /home/data/git/gittest.git/ 命令时，git 就会创建一个裸仓库，裸仓库没有工作区，因为服务器上的 git 仓库只是为了共享，所以不让用户直接登录到服务器上去改工作区，并且服务器上的 git 仓库通常都以 .git 结尾，然后把权限改为 git客户端 clone 远程仓库1、客户端开发目录下打开你的 Git Bash 终端克隆远程仓库$ git clone git@192.168.160.130:/home/data/git/gittest.git www.yanghaihua.com Cloning into 'gittest'... The authenticity of host '192.168.160.130 (192.168.160.130)' can't be established. ECDSA key fingerprint is SHA256:r1PfMrvkpc+X/Cqef9As7RvLix8kXZ2dyAfYmkDsDds. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.160.130' (ECDSA) to the list of known hosts. git@192.168.160.130's password: warning: You appear to have cloned an empty repository. Checking connectivity... done.当链接第一次链接到 git 服务器时会有一个提示 ...(yes/no) 选择 yes，此时在你的 .ssh 多出来一个 known_hosts 文件2、客户端创建 ssh 公钥和私钥$ ssh-keygen -t rsa -C 'xxxxxx'-t：指定要创建的密钥类型，可以使用："rsa1"(SSH-1) "rsa"(SSH-2) "dsa"(SSH-2)-C：一个注释然后敲击两次回车就可以了，因为我这里已经有了，所以就创建了。执行完成后会在你的用户家目录 .ssh 文件夹中出现 id_rsa (私钥) id_rsa.pub (公钥)。这样一个空的仓库就完成了，然后我们把项目迁移到这个空仓库中。（如果已经有就不要创建了，直接使用就好，如果没有就创建）3、客户端测试 git 是否配置成功$ ll total 1 -rw-r--r-- 1 Administrator 197121 11 五月 8 18:34 readme.txt $ cat readme.txt version.1.0这里为了测试，我在仓库目录中只新建了一个 readme.txt 文件，然后内容是 version.1.04、客户端提交项目$ git add . $ git status $ git commit -m 'version.1.0' $ git push origin master git@192.168.160.130's password: Counting objects: 3, done. Writing objects: 100% (3/3), 224 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@192.168.160.130:/home/data/git/gittest.git * [new branch] master -> master这里就是平时开发时候的正常提交流程。输入密码，然后提交成功。这时候是需要输入密码的，因为我们没有将本地的公钥导入到 /home/git/.ssh/authorized_keys 这个文件中。5、客户端导入公钥到服务器端实现免密提交$ cat readme.txt version.1.0 version.1.1 $ git add . $ git commit -m 'version.1.1' $ git push origin master Counting objects: 3, done. Writing objects: 100% (3/3), 260 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To git@192.168.160.130:/home/data/git/gittest.git 8c49f3d..5501923 master -> master将客户端中的公钥导入 /home/git/.ssh/authorized_keys 文件中，公钥就在我们家目录中的 .ssh/id_rsa.pub 文件中。然后我们再次修改 readme.txt 文件，增加一行 version1.1 字符串，然后再次提交测试。（这里就不做测试了，配置好后就可以实现免密使用 git 了）服务器端 clone 仓库（基本流程和客户端差不多）1、服务器端克隆仓库[root@localhost ~]# cd /home/wwwroot/ [root@localhost wwwroot]# sudo -u www git clone git@127.0.0.1:/home/data/git/gittest.git/ www.yanghaihua.com 正克隆到 'www.yanghaihua.com'... The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. ECDSA key fingerprint is e4:5a:53:73:8a:03:6b:5f:f7:2f:67:38:3c:a1:b3:b3. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '127.0.0.1' (ECDSA) to the list of known hosts. git@127.0.0.1's password: remote: Counting objects: 10, done. remote: Compressing objects: 100% (4/4), done. remote: Total 10 (delta 1), reused 0 (delta 0) 接收对象中: 100% (10/10), done. 处理 delta 中: 100% (1/1), done.假设我们的项目都放在 /home/wwwroot/ 目录下，这里我们用 www 权限来 clone 版本库，因为我们的项目运行权限都是 www 运行权限。2、服务器端生成 ssh 公钥和私钥[root@localhost .git]# sudo -u www ssh-keygen -t rsa -C "server@163.com" Generating public/private rsa key pair. Enter file in which to save the key (/home/www/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/www/.ssh/id_rsa. Your public key has been saved in /home/www/.ssh/id_rsa.pub. The key fingerprint is: 50:14:cd:b2:ff:0b:48:f3:a4:f0:b7:a8:6d:03:e4:af server@163.com The key's randomart image is: +--[ RSA 2048]----+ | .++ | | .. o | | . o | | ... | | o. S.. | | o+ *. | | o+ +. | | .+o o. | | E+o.. .. | +-----------------+3、服务器端测试 git 是否正常[root@localhost www.yanghaihua.com]# sudo -u www git pull git@127.0.0.1's password: Already up-to-date.正常使用，需要输入密码，导入公钥到服务器端 /home/git/.ssh/authorized_keys 文件中。4、服务器端导入公钥到 /home/git/.ssh/authorized_keys 文件中实现免密[root@localhost www.yanghaihua.com]# sudo -u www git pull Already up-to-date.到这里服务器仓库就配置好了，因为这个仓库是项目目录不需要提交和修改，只需要等下配置好钩子之后 git pull 就可以了。服务器端配置钩子先看看官网怎么定义：和其它版本控制系统一样，git 能在特定的重要动作发生时触发自定义脚本。有两组这样的钩子：客户端的和服务器端的。客户端钩子由诸如提交和合并这样的操作所调用，而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。 你可以随心所欲地运用这些钩子。https://www.git-scm.com/book/zh/v2/自定义-Git-Git-钩子1、配置钩子[root@localhost www.yanghaihua.com]# cd /home/data/git/gittest.git/hooks [root@localhost hooks]# sudo -u git vim post-receive #!/bin/sh cd /home/wwwroot/www.yanghaihua.com unset GIT_DIR git config --list sudo -u www git pull origin master钩子其实就是一个 shell 脚本，在客户端提交或者推送的时候调用它，上面的配置大概就是进入项目目录，然后使用 www 用户更新项目到项目目录，就是这么简单的一个配置，客户端修改代码，服务端只做更新不做任何修改。2、修改钩子权限[root@localhost hooks]# chmod u+x post-receive3、测试钩子是否可以正常运行[root@localhost hooks]# sudo -u git ./post-receive core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true remote.origin.url=git@127.0.0.1:/home/data/git/gittest.git/ remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.master.remote=origin branch.master.merge=refs/heads/master [sudo] password for git: git 不在 sudoers 文件中。此事将被报告。因为这个钩子是 git 用户在运行，所以我们要用 git 用户身份来测试，测试过程中发生错误，因为在 sudoers 文件中 git 用户没有这个权限，我们 visudo 编辑下这个文件4、增加 git sudo 权限，在 root 这一行下面增加root ALL=(ALL) ALL git ALL=(ALL) NOPASSWD:ALL #NOPASSWD 执行时免密5、再次测试[root@localhost hooks]# sudo -u git ./post-receive core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true remote.origin.url=git@127.0.0.1:/home/data/git/gittest.git/ remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.master.remote=origin branch.master.merge=refs/heads/master 来自 127.0.0.1:/home/data/git/gittest * branch master -> FETCH_HEAD Already up-to-date.钩子配置成功，在上面钩子文件中，我们使用了 www 用户权限执行了 git push origin master 命令，是因为我们的项目是用的 www 权限执行的，但是 git 的钩子操作是用的 git 权限来操作的，如果不用 www 权限，那么钩子更新下来的文件权限就是 git 权限，所以为了保证 nginx 下项目目录运行权限是 www 用户，我们加上了 sudo -u www xxxxxx 来操作。这里基本就完成了，我们可以测试下整体流程操作。测试1、客户端提交代码$ cat readme.txt version.1.0 version.1.1 version.1.2 $ git add . $ git commit -m 'version.1.2' $ git push Counting objects: 3, done. Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) remote: core.repositoryformatversion=0 remote: core.filemode=true remote: core.bare=false remote: core.logallrefupdates=true remote: remote.origin.url=git@127.0.0.1:/home/data/git/gittest.git/ remote: remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* remote: branch.master.remote=origin remote: branch.master.merge=refs/heads/master remote: sudo：抱歉，您必须拥有一个终端来执行 sudo To git@192.168.160.130:/home/data/git/gittest.git 13b9970..990b6ee master -> master再次修改 readme.txt 文件，增加一行 version.1.2 字符串，然后提交的后看 git 的反馈，说明提交已经成功，但是调用钩子的时候报错了，需要一个终端来执行 sudo，解决办法就是编辑 /etc/sudoers 文件：2、修改 /etc/sudoers 文件[root@localhost ~]# visudo . . . Defaults requiretty 改为： #Defaults requiretty #表示不需要控制终端 Defaults requiretty 改为： Defaults:git !requiretty #表示仅git用户不需要控制终端（我使用的这个）3、再次提交测试$ cat readme.txt version.1.0 version.1.1 version.1.2 version.1.3 $ git add . $ git commit -m 'version.1.3' $ git push Counting objects: 3, done. Writing objects: 100% (3/3), 265 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) remote: core.repositoryformatversion=0 remote: core.filemode=true remote: core.bare=false remote: core.logallrefupdates=true remote: remote.origin.url=git@127.0.0.1:/home/data/git/gittest.git/ remote: remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* remote: branch.master.remote=origin remote: branch.master.merge=refs/heads/master remote: 来自 127.0.0.1:/home/data/git/gittest remote: * branch master -> FETCH_HEAD remote: 更新 8d17b41..ab39eb6 remote: Fast-forward remote: readme.txt | 3 ++- remote: 1 file changed, 2 insertions(+), 1 deletion(-) remote: --钩子拉取项目成功-- To git@192.168.160.130:/home/data/git/gittest.git 8d17b41..ab39eb6 master -> master再次修改 readme.txt 文件，增加一行 version.1.3 字符串，然后再次提交。钩子中的命令都已经执行。接下来看下服务器端项目目录是否正常。3、查看服务端 readme.txt 文件[root@localhost www.yanghaihua.com]# less readme.txt version.1.0 version.1.1 version.1.2 version.1.34、禁用服务端 git 用户 shell 登录[root@localhost ~]# vi /etc/passwd . . . git:x:1001:1001::/home/git:/usr/bin/git-shell这样 git 用户就可以正常通过 ssh 使用 shell，但是不可以登录 shell。到这里使用 git 提交，然后服务器端钩子更新代码到项目目录就基本上行完成了。基本大致流程就是客户端 push 到服务器，然后服务器再用钩子 pull 到项目目录，服务器端只做更新而不做任何的修改和提交。主要就是权限的分配和分支的开发。开发一定要新建一个分支，然后没有问题之后再合并到主分支，然后再 push 到服务器上，否则如果直接提交的 master 分支就会直接到被钩子更新到项目目录了。这只是一个简单的部署流程，中间还有很多细节问题需要我们去配置和探索。转载于：https://www.yanghaihua.com/content/2019-04-09/1033.shtml
看到一篇对epoll讲解很到位的文章，转载收藏一下。从事服务端开发，少不了要接触网络编程。epoll 作为 Linux 下高性能网络服务器的必备技术至关重要，nginx、Redis、Skynet 和大部分游戏服务器都使用到这一多路复用技术。epoll 很重要，但是 epoll 与 select 的区别是什么呢？epoll 高效的原因是什么？网上虽然也有不少讲解 epoll 的文章，但要么是过于浅显，或者陷入源码解析，很少能有通俗易懂的。笔者于是决定编写此文，让缺乏专业背景知识的读者也能够明白 epoll 的原理。文章核心思想是：要让读者清晰明白 epoll 为什么性能好。本文会从网卡接收数据的流程讲起，串联起 CPU 中断、操作系统进程调度等知识；再一步步分析阻塞接收数据、select 到 epoll 的进化过程；最后探究 epoll 的实现细节。下边是一个典型的计算机结构图，计算机由 CPU、存储器（内存）与网络接口等部件组成，了解 epoll 本质的第一步，要从硬件的角度看计算机怎样接收网络数据。计算机结构图（图片来源：Linux内核完全注释之微型计算机组成结构）下图展示了网卡接收数据的过程。在 ① 阶段，网卡收到网线传来的数据；经过 ② 阶段的硬件电路的传输；最终 ③ 阶段将数据写入到内存中的某个地址上。这个过程涉及到 DMA 传输、IO 通路选择等硬件有关的知识，但我们只需知道：网卡会把接收到的数据写入内存。网卡接收数据的过程通过硬件传输，网卡接收的数据存放到内存中，操作系统就可以去读取它们。了解 epoll 本质的第二步，要从 CPU 的角度来看数据接收。理解这个问题，要先了解一个概念——中断。计算机执行程序时，会有优先级的需求。比如，当计算机收到断电信号时，它应立即去保存数据，保存数据的程序具有较高的优先级（电容可以保存少许电量，供 CPU 运行很短的一小段时间）。一般而言，由硬件产生的信号需要 CPU 立马做出回应，不然数据可能就丢失了，所以它的优先级很高。CPU 理应中断掉正在执行的程序，去做出响应；当 CPU 完成对硬件的响应后，再重新执行用户程序。中断的过程如下图，它和函数调用差不多，只不过函数调用是事先定好位置，而中断的位置由“信号”决定。中断程序调用以键盘为例，当用户按下键盘某个按键时，键盘会给 CPU 的中断引脚发出一个高电平，CPU 能够捕获这个信号，然后执行键盘中断程序。下图展示了各种硬件通过中断与 CPU 交互的过程。CPU 中断（图片来源：net.pku.edu.cn）现在可以回答“如何知道接收了数据？”这个问题了：当网卡把数据写入到内存后，网卡向 CPU 发出一个中断信号，操作系统便能得知有新数据到来，再通过网卡中断程序去处理数据。了解 epoll 本质的第三步，要从操作系统进程调度的角度来看数据接收。阻塞是进程调度的关键一环，指的是进程在等待某事件（如接收到网络数据）发生之前的等待状态，recv、select 和 epoll 都是阻塞方法。下边分析一下进程阻塞为什么不占用 CPU 资源？为简单起见，我们从普通的 recv 接收开始分析，先看看下面代码：//创建socket int s = socket(AF_INET, SOCK_STREAM, 0); //绑定 bind(s, ...) //监听 listen(s, ...) //接受客户端连接 int c = accept(s, ...) //接收客户端数据 recv(c, ...); //将数据打印出来 printf(...)这是一段最基础的网络编程代码，先新建 socket 对象，依次调用 bind、listen 与 accept，最后调用 recv 接收数据。recv 是个阻塞方法，当程序运行到 recv 时，它会一直等待，直到接收到数据才往下执行。那么阻塞的原理是什么？工作队列操作系统为了支持多任务，实现了进程调度的功能，会把进程分为“运行”和“等待”等几种状态。运行状态是进程获得 CPU 使用权，正在执行代码的状态；等待状态是阻塞状态，比如上述程序运行到 recv 时，程序会从运行状态变为等待状态，接收到数据后又变回运行状态。操作系统会分时执行各个运行状态的进程，由于速度很快，看上去就像是同时执行多个任务。下图的计算机中运行着 A、B 与 C 三个进程，其中进程 A 执行着上述基础网络程序，一开始，这 3 个进程都被操作系统的工作队列所引用，处于运行状态，会分时执行。工作队列中有 A、B 和 C 三个进程等待队列当进程 A 执行到创建 socket 的语句时，操作系统会创建一个由文件系统管理的 socket 对象（如下图）。这个 socket 对象包含了发送缓冲区、接收缓冲区与等待队列等成员。等待队列是个非常重要的结构，它指向所有需要等待该 socket 事件的进程。创建 socket当程序执行到 recv 时，操作系统会将进程 A 从工作队列移动到该 socket 的等待队列中（如下图）。由于工作队列只剩下了进程 B 和 C，依据进程调度，CPU 会轮流执行这两个进程的程序，不会执行进程 A 的程序。所以进程 A 被阻塞，不会往下执行代码，也不会占用 CPU 资源。socket 的等待队列注：操作系统添加等待队列只是添加了对这个“等待中”进程的引用，以便在接收到数据时获取进程对象、将其唤醒，而非直接将进程管理纳入自己之下。上图为了方便说明，直接将进程挂到等待队列之下。唤醒进程当 socket 接收到数据后，操作系统将该 socket 等待队列上的进程重新放回到工作队列，该进程变成运行状态，继续执行代码。同时由于 socket 的接收缓冲区已经有了数据，recv 可以返回接收到的数据。这一步，贯穿网卡、中断与进程调度的知识，叙述阻塞 recv 下，内核接收数据的全过程。如下图所示，进程在 recv 阻塞期间，计算机收到了对端传送的数据（步骤①），数据经由网卡传送到内存（步骤②），然后网卡通过中断信号通知 CPU 有数据到达，CPU 执行中断程序（步骤③）。此处的中断程序主要有两项功能，先将网络数据写入到对应 socket 的接收缓冲区里面（步骤④），再唤醒进程 A（步骤⑤），重新将进程 A 放入工作队列中。内核接收数据全过程唤醒进程的过程如下图所示：唤醒进程以上是内核接收数据全过程，这里我们可能会思考两个问题：其一，操作系统如何知道网络数据对应于哪个 socket？其二，如何同时监视多个 socket 的数据？第一个问题：因为一个 socket 对应着一个端口号，而网络数据包中包含了 ip 和端口的信息，内核可以通过端口号找到对应的 socket。当然，为了提高处理速度，操作系统会维护端口号到 socket 的索引结构，以快速读取。第二个问题是多路复用的重中之重，也正是本文后半部分的重点。服务端需要管理多个客户端连接，而 recv 只能监视单个 socket，这种矛盾下，人们开始寻找监视多个 socket 的方法。epoll 的要义就是高效地监视多个 socket。从历史发展角度看，必然先出现一种不太高效的方法，人们再加以改进，正如 select 之于 epoll。先理解不太高效的 select，才能够更好地理解 epoll 的本质。假如能够预先传入一个 socket 列表，如果列表中的 socket 都没有数据，挂起进程，直到有一个 socket 收到数据，唤醒进程。这种方法很直接，也是 select 的设计思想。为方便理解，我们先复习 select 的用法。在下边的代码中，先准备一个数组 fds，让 fds 存放着所有需要监视的 socket。然后调用 select，如果 fds 中的所有 socket 都没有数据，select 会阻塞，直到有一个 socket 接收到数据，select 返回，唤醒进程。用户可以遍历 fds，通过 FD_ISSET 判断具体哪个 socket 收到数据，然后做出处理。int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...); listen(s, ...); int fds[] = 存放需要监听的socket; while(1){ int n = select(..., fds, ...) for(int i=0; i < fds.count; i++){ if(FD_ISSET(fds[i], ...)){ //fds[i]的数据处理 } }}select 的流程select 的实现思路很直接，假如程序同时监视如下图的 sock1、sock2 和 sock3 三个 socket，那么在调用 select 之后，操作系统把进程 A 分别加入这三个 socket 的等待队列中。操作系统把进程 A 分别加入这三个 socket 的等待队列中当任何一个 socket 收到数据后，中断程序将唤起进程。下图展示了 sock2 接收到了数据的处理流程：注：recv 和 select 的中断回调可以设置成不同的内容。sock2 接收到了数据，中断程序唤起进程 A所谓唤起进程，就是将进程从所有的等待队列中移除，加入到工作队列里面，如下图所示：将进程 A 从所有等待队列中移除，再加入到工作队列里面经由这些步骤，当进程 A 被唤醒后，它知道至少有一个 socket 接收了数据。程序只需遍历一遍 socket 列表，就可以得到就绪的 socket。这种简单方式行之有效，在几乎所有操作系统都有对应的实现。但是简单的方法往往有缺点，主要是：其一，每次调用 select 都需要将进程加入到所有监视 socket 的等待队列，每次唤醒都需要从每个队列中移除。这里涉及了两次遍历，而且每次都要将整个 fds 列表传递给内核，有一定的开销。正是因为遍历操作开销大，出于效率的考量，才会规定 select 的最大监视数量，默认只能监视 1024 个 socket。其二，进程被唤醒后，程序并不知道哪些 socket 收到数据，还需要遍历一次。那么，有没有减少遍历的方法？有没有保存就绪 socket 的方法？这两个问题便是 epoll 技术要解决的。补充说明： 本节只解释了 select 的一种情形。当程序调用 select 时，内核会先遍历一遍 socket，如果有一个以上的 socket 接收缓冲区有数据，那么 select 直接返回，不会阻塞。这也是为什么 select 的返回值有可能大于 1 的原因之一。如果没有 socket 有数据，进程才会阻塞。epoll 是在 select 出现 N 多年后才被发明的，是 select 和 poll（poll 和 select 基本一样，有少量改进）的增强版本。epoll 通过以下一些措施来改进效率：措施一：功能分离select 低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。如下图所示，每次调用 select 都需要这两步操作，然而大多数应用场景中，需要监视的 socket 相对固定，并不需要每次都修改。epoll 将这两个操作分开，先用 epoll_ctl 维护等待队列，再调用 epoll_wait 阻塞进程。显而易见地，效率就能得到提升。相比 select，epoll 拆分了功能为方便理解后续的内容，我们先了解一下 epoll 的用法。如下的代码中，先用 epoll_create 创建一个 epoll 对象 epfd，再通过 epoll_ctl 将需要监视的 socket 添加到 epfd 中，最后调用 epoll_wait 等待数据：int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...) listen(s, ...) int epfd = epoll_create(...); epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中 while(1){ int n = epoll_wait(...) for(接收到数据的socket){ //处理 } }功能分离，使得 epoll 有了优化的可能。措施二：就绪列表select 低效的另一个原因在于程序不知道哪些 socket 收到数据，只能一个个遍历。如果内核维护一个“就绪列表”，引用收到数据的 socket，就能避免遍历。如下图所示，计算机共有三个 socket，收到数据的 sock2 和 sock3 被就绪列表 rdlist 所引用。当进程被唤醒后，只要获取 rdlist 的内容，就能够知道哪些 socket 收到数据。就绪列表示意图本节会以示例和图表来讲解 epoll 的原理和工作流程。创建 epoll 对象如下图所示，当某个进程调用 epoll_create 方法时，内核会创建一个 eventpoll 对象（也就是程序中 epfd 所代表的对象）。eventpoll 对象也是文件系统中的一员，和 socket 一样，它也会有等待队列。内核创建 eventpoll 对象创建一个代表该 epoll 的 eventpoll 对象是必须的，因为内核要维护“就绪列表”等数据，“就绪列表”可以作为 eventpoll 的成员。维护监视列表创建 epoll 对象后，可以用 epoll_ctl 添加或删除所要监听的 socket。以添加 socket 为例，如下图，如果通过 epoll_ctl 添加 sock1、sock2 和 sock3 的监视，内核会将 eventpoll 添加到这三个 socket 的等待队列中。添加所要监听的 socket当 socket 收到数据后，中断程序会操作 eventpoll 对象，而不是直接操作进程。接收数据当 socket 收到数据后，中断程序会给 eventpoll 的“就绪列表”添加 socket 引用。如下图展示的是 sock2 和 sock3 收到数据后，中断程序让 rdlist 引用这两个 socket。给就绪列表添加引用eventpoll 对象相当于 socket 和进程之间的中介，socket 的数据接收并不直接影响进程，而是通过改变 eventpoll 的就绪列表来改变进程状态。当程序执行到 epoll_wait 时，如果 rdlist 已经引用了 socket，那么 epoll_wait 直接返回，如果 rdlist 为空，阻塞进程。阻塞和唤醒进程假设计算机中正在运行进程 A 和进程 B，在某时刻进程 A 运行到了 epoll_wait 语句。如下图所示，内核会将进程 A 放入 eventpoll 的等待队列中，阻塞进程。epoll_wait 阻塞进程当 socket 接收到数据，中断程序一方面修改 rdlist，另一方面唤醒 eventpoll 等待队列中的进程，进程 A 再次进入运行状态（如下图）。也因为 rdlist 的存在，进程 A 可以知道哪些 socket 发生了变化。epoll 唤醒进程至此，相信读者对 epoll 的本质已经有一定的了解。但我们还需要知道 eventpoll 的数据结构是什么样子？此外，就绪队列应该应使用什么数据结构？eventpoll 应使用什么数据结构来管理通过 epoll_ctl 添加或删除的 socket？如下图所示，eventpoll 包含了 lock、mtx、wq（等待队列）与 rdlist 等成员，其中 rdlist 和 rbr 是我们所关心的。epoll 原理示意图，图片来源：《深入理解Nginx：模块开发与架构解析(第二版)》，陶辉就绪列表的数据结构就绪列表引用着就绪的 socket，所以它应能够快速的插入数据。程序可能随时调用 epoll_ctl 添加监视 socket，也可能随时删除。当删除时，若该 socket 已经存放在就绪列表中，它也应该被移除。所以就绪列表应是一种能够快速插入和删除的数据结构。双向链表就是这样一种数据结构，epoll 使用双向链表来实现就绪队列（对应上图的 rdllist）。索引结构既然 epoll 将“维护监视队列”和“进程阻塞”分离，也意味着需要有个数据结构来保存监视的 socket，至少要方便地添加和移除，还要便于搜索，以避免重复添加。红黑树是一种自平衡二叉查找树，搜索、插入和删除时间复杂度都是O(log(N))，效率较好，epoll 使用了红黑树作为索引结构（对应上图的 rbr）。注：因为操作系统要兼顾多种功能，以及由更多需要保存的数据，rdlist 并非直接引用 socket，而是通过 epitem 间接引用，红黑树的节点也是 epitem 对象。同样，文件系统也并非直接引用着 socket。为方便理解，本文中省略了一些间接结构。epoll 在 select 和 poll 的基础上引入了 eventpoll 作为中间层，使用了先进的数据结构，是一种高效的多路复用技术。这里也以表格形式简单对比一下 select、poll 与 epoll，结束此文。希望读者能有所收获。 转载于: https://my.oschina.net/editorial-story/blog/3052308原作者: https://zhuanlan.zhihu.com/p/63179839
Go 语言也提供了接口类型，使得我们可以面向接口编程，将实现和接口分离。在我看来，软件的抽象之美也应该以此来表达，和 Java 语言不同的是 Go 并不是那么 “强制”，它使用了一种 鸭子类型 的方式让动态类型成为可能。Duck Typing在 Go 中没有 implements 和 extends 这种关键字，这对我们而言反倒轻松了一些，它认为 Go 的接口就像鸭子测试里的描述：当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子，那么这只鸟就可以被称为鸭子。在鸭子类型中，关注点在于对象的行为，能作什么；而不是关注对象所属的类型。例如，在不使用鸭子类型的语言中，我们可以编写一个函数，它接受一个类型为 “鸭子” 的对象，并调用它的 “走” 和 “叫” 方法。在使用鸭子类型的语言中，这样的一个函数可以接受一个任意类型的对象，并调用它的 “走” 和 “叫” 方法。如果这些需要被调用的方法不存在，那么将引发一个运行时错误。任何拥有这样的正确的 “走” 和 “叫” 方法的对象都可被函数接受的这种行为引出了以上表述，这种决定类型的方式因此得名。举个例子我们用 Go 语言来实现一个鸭子类型：type Duck interface { Swim() // 游泳 Feathers() // 羽毛 }这里使用 Go 提供的 interface 关键字定义了一个鸭子接口类型，这个接口中提供了鸭子的两种行为：游泳和羽毛是什么样的，但是没有提供实现。我们见过的鸭子类型可多了去了，下面是你可能见过的：组合接口现在我们给鸭子再添加一种嘎嘎叫的能力，一种方式是在原有的接口上添加 “嘎嘎叫” 方法，这样做的话就表示所有的鸭子都应该拥有此能力，假设我们的玩具鸭并不能开口嘎嘎叫，所以它没有这种能力。这时候我们可以将会嘎嘎叫的鸭子单独定义一种类型，在 Go 可以使用组合的方式来实现：type QuackDuck interface { Quack() // 嘎嘎叫 Duck // 嵌入接口 }这样 QuackDuck 类型就拥有了之前 Duck 提供的两种抽象能力，同时还应该拥有嘎嘎叫的能力。接口实现前面我们只给出了鸭子的能力定义，还没有任何实现，由于 Go 中没有继承和实现的关键字，想成为上述接口的实现非常简单，只要实现它们定义的方法就可以了。// RealDuck - 真正的鸭子 type RealDuck struct { } func (RealDuck) Swim() { fmt.Println("用鸭璞向后划水") } func (RealDuck) Feathers() { fmt.Println("遇到水也不会湿的羽毛") } func (RealDuck) Quack() { fmt.Println("嘎~ 嘎~ 嘎~") } // ToyDuck - 玩具鸭 type ToyDuck struct { } func (ToyDuck) Swim() { fmt.Println("以固定的速度向前移动") } func (ToyDuck) Feathers() { fmt.Println("白色的固定的塑料羽毛") }可以看到我们定义了两种鸭子类型，一种是真正的鸭子，它还多实现了一种嘎嘎叫方法，另一个玩具鸭子只有游泳和羽毛这两种行为。这个编程方式和我们写普通的结构体方法没什么区别，只是对应的方法签名相同，其实这种方式在 Go 语言的标准库中有特别多的应用，比如：io.Reader、io.Writer 和 io.Closer。接口使用接下来我们可以使用一下这个类型了：var duck Duck duck = ToyDuck{} duck.Swim() duck.Feathers()输出以固定的速度向前移动 白色的固定的塑料羽毛由于玩具鸭没有嘎嘎叫的能力，所以如果你这么写编译无法通过我们也可以用一种工厂的方式来进行调用：func Factory(name string) Duck { switch name { case "toy": return &ToyDuck{} case "real": return &RealDuck{} default: panic("No such duck") } } func main() { duck := Factory("toy") duck.Swim() duck.Feathers() }小结其实这就是 Go 中的多态体现，仔细体会其中的味道，在阅读其他源码的时候你会更加熟练。但是也有一个问题当代码工程庞大的时候你很难知道一个接口体到底实现了哪些接口，不过有幸的是我们生在最智能的 IDE 时代，在 GoLand 中可以帮你提示 :D转载于https://blog.biezhi.me/2019/01/learn-golang-interfaces.html
斐讯K3 固件降级刷官改固件详细图文教程
简介这篇文章是对原作者文章的一个转载，以防止文章丢失。对应的更新了文中的图片链接，其余内容均未修改。正文在购买了斐讯K3后，第一件事就是刷机，以下是具体操作步骤，建议全程处于 chrome 浏览器的“无痕窗口”下进行。第一部分官方固件降级 58 固件，降级 246 固件输入路由器地址 p.to 或者 192.168.2.1 进入路由器后台页面，找到“功能设置”，点击“系统状态”然后查看“硬件版本”和“软件版本”我这边的“硬件版本”是“A2”，“软件版本”是 V21.6.12.6我们需要将软件版本“V21.6.12.6”先降级为“V21.6.11.58”，在“功能设置”选项中往下滑，找到“手动升级”选项选择固件“K3_V21.6.11.58p.bin”，然后点击“上传升级”然后会提示“正在升级”，K3的屏幕上面会显示“系统升级中”升级完成会自动刷新网页，重新进入“手动升级”页面。，可以看到当前版本为“V21.6.11.58”，然后我们还需要再次降级，降级为“V21.5.37.246”我们再选择“升级文件”，选择“K3_V21.5.37.246.bin”，然后点击“上传升级”，然后会再次进入如上图一样的“正在升级”页面。升级成功后，登录K3，我们打开“功能设置”中的“系统状态”，可以看到固件版本已经降级为了“V21.5.37.246”，然后执行一次重启路由器操作，确保接下来的操作不出问题。第二部分激活 telnet 端口，备份K3激活 telnet 工具地址：http://www.right.com.cn/forum/thread-261028-1-1.html打开 RoutAckProV1B2.exe，“路由器 IP”默认为 192.168.2.1，斐讯默认的路由器地址，不需要更改，然后先点击“打开 Telnet”，然后点击“设置永久标志”，输出信息如下“收到应答信号，打开 Telnet 完成，收到应答信号，设置永久标志完成”，最后关掉软件即可插入 U 盘，然后前往“存储管理”中查看 U 盘是否正常识别打开软件“PuTTY”连接 K3 的 Telnet，“主机名称（或 IP 地址）”处填写路由器地址 192.168.2.1，“连接类型”选择”Telnet”，然后点击“打开”输入命令“mount”查看 U 盘是否挂在成功，成功的话会看到 sda 开头的设备，我这边是显示 sda1输入如下命令进行备份操作cd /tmp/share/sda1 (进入 U 盘,注意 sda1) mkdir backup (建立备份文件夹) cd backup (进入 backup 目录) cat /dev/mtdblock0 > mtdblock0.bin (备份 CFE) cat /dev/mtdblock5 > mtdblock5.bin (备份设备信息) cat /dev/mtdblock6 > mtdblock6.bin (备份固件) ls 备份完成后，进入“存储管理”中点击“安全移除”，如果无法移除，那就直接拔 U 盘，然后将备份的文件拷贝到电脑或者网盘进行二次备份。第三部分刷入官改固件打开”Tftpd64.exe”,”Current Diectory”为路由器固件所在的文件夹，点击“Browes”可修改，一般默认既为 Tftpd64 所在文件夹的路径，“Server interfaces”为连接路由所获取到的本机电脑的 ip 地址，手动选择 ip 地址为“192.168.2.x”，x 为路由自动获取到的地址，可能不一样，我这边的 ip 地址为 192.168.2.100再次打开“PuTTY”，如上次步骤输入 ip 地址为 192.168.2.1，连接类型为“Telnet”，然后单击“开始”进行连接，然后输入如下命令刷入。蓝色字体为固件名称，我这边名称为“k3_v15.bin”；红色字体为本机电脑 ip 地址，我这边 ip 地址为“192.168.2.100”cd /tmp; tftp -g -r k3_v15.bin 192.168.2.100上传成功后输入刷入固件命令cat k3_v15.bin > /dev/mtdblock6 && reboot稍等片刻，会出现如下错误，rmmod dhd failed: No such file or directory，说明路由器已经断开了 PuTTY 的连接，K3 的屏幕上也显示为“系统升级中”，这时候等了 2,3 分钟进入 192.168.2.1 即可。成功后打开 192.168.2.1 登录后台，查看“系统信息”，可以看到已经已经刷入成功，至此刷入完成链接: https://cloud.189.cn/web/share?code=byMVZnriUzEb（访问码：1kvd）文章转载于 https://hlynford.com/202.html
有时，最新版本的安装包可能无法按预期工作。你的程序可能与更新的软件包不兼容，并且仅支持特定的旧版软件包。在这种情况下，你可以立即将有问题的软件包降级到其早期的工作版本。请参阅我们的旧指南，在这了解如何降级 Ubuntu 及其衍生版中的软件包以及在这了解如何降级 Arch Linux 及其衍生版中的软件包。但是，你无需降级某些软件包。我们可以同时使用多个版本。例如，假设你在测试部署在 Ubuntu 18.04 LTS 中的LAMP 栈的 PHP 程序。过了一段时间，你发现应用程序在 PHP 5.6 中工作正常，但在 PHP 7.2 中不正常（Ubuntu 18.04 LTS 默认安装 PHP 7.x）。你打算重新安装 PHP 或整个 LAMP 栈吗？但是没有必要。你甚至不必将 PHP 降级到其早期版本。在这个简短的教程中，我将向你展示如何在 Ubuntu 18.04 LTS 中切换多个 PHP 版本。它没你想的那么难。请继续阅读。在多个 PHP 版本之间切换要查看 PHP 的默认安装版本，请运行：$ php -v PHP 7.2.7-0ubuntu0.18.04.2 (cli) (built: Jul 4 2018 16:55:24) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.7-0ubuntu0.18.04.2, Copyright (c) 1999-2018, by Zend Technologies如你所见，已安装的 PHP 的版本为 7.2.7。在测试你的程序几天后，你会发现你的程序不支持 PHP7.2。在这种情况下，同时使用 PHP5.x 和 PHP7.x 是个不错的主意，这样你就可以随时轻松地在任何支持的版本之间切换。你不必删除 PHP7.x 或重新安装 LAMP 栈。你可以同时使用 PHP5.x 和 7.x 版本。我假设你还没有在你的系统中卸载 PHP 5.6。万一你已将其删除，你可以使用下面的 PPA 再次安装它。你可以从 PPA 中安装 PHP 5.6：$ sudo add-apt-repository -y ppa:ondrej/php $ sudo apt update $ sudo apt install php5.6从 PHP 7.x 切换到 PHP 5.x.首先使用命令禁用 PHP 7.2 模块：$ sudo a2dismod php7.2 Module php7.2 disabled. To activate the new configuration, you need to run: systemctl restart apache2接下来，启用 PHP 5.6 模块：$ sudo a2enmod php5.6将 PHP 5.6 设置为默认版本：$ sudo update-alternatives --set php /usr/bin/php5.6或者，你可以运行以下命令来设置默认情况下要使用的全局 PHP 版本。$ sudo update-alternatives --config php输入选择的号码将其设置为默认版本，或者只需按回车键保持当前选择。如果你已安装其他 PHP 扩展，请将它们设置为默认值。$ sudo update-alternatives --set phar /usr/bin/phar5.6最后，重启 Apache Web 服务器：$ sudo systemctl restart apache2现在，检查 PHP 5.6 是否是默认版本：$ php -v PHP 5.6.37-1+ubuntu18.04.1+deb.sury.org+1 (cli) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies从 PHP 5.x 切换到 PHP 7.x.同样，你可以从 PHP 5.x 切换到 PHP 7.x 版本，如下所示。$ sudo a2enmod php7.2 $ sudo a2dismod php5.6 $ sudo update-alternatives --set php /usr/bin/php7.2 $ sudo systemctl restart apache2提醒一句：最终稳定版 PHP 5.6 于 2017 年 1 月 19 日达到活跃支持截止。但是，直到 2018 年 12 月 31 日，PHP 5.6 将继续获得对关键安全问题的支持。所以，建议尽快升级所有 PHP 程序并与 PHP 7.x 兼容。如果你希望防止 PHP 将来自动升级，请参阅以下指南。就是这些了。希望这有帮助。还有更多的好东西。敬请关注！干杯!转载于https://linux.cn/article-9951-1.html译者： geekpi
一直不能很好的理解这4者之间的关系和区别，下面是在知乎上看到严肃的一个回答，感觉说的比较不错。而且比较容易理解，所以转载过来记录一下。阻塞与非阻塞与同步与异步 不能简单的从字面理解，提供一个从分布式系统角度的回答。同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步，就是在发出一个调用时，在没有得到结果之前，该调用就不返回。但是一旦调用返回，就得到返回值了。换句话说，就是由调用者主动等待这个调用的结果。而异步则是相反，调用在发出之后，这个调用就直接返回了，所以没有返回结果。换句话说，当一个异步过程调用发出后，调用者不会立刻得到结果。而是在调用发出后，被调用者通过状态、通知来通知调用者，或通过回调函数处理这个调用。典型的异步编程模型比如Node.js举个通俗的例子：你打电话问书店老板有没有《分布式系统》这本书，如果是同步通信机制，书店老板会说，你稍等，”我查一下"，然后开始查啊查，等查好了（可能是5秒，也可能是一天）告诉你结果（返回结果）。而异步通信机制，书店老板直接告诉你我查一下啊，查好了打电话给你，然后直接挂电话了（不返回结果）。然后查好了，他会主动打电话给你。在这里老板通过“回电”这种方式来回调。阻塞与非阻塞阻塞和非阻塞关注的是程序在等待调用结果（消息，返回值）时的状态.阻塞调用是指调用结果返回之前，当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前，该调用不会阻塞当前线程。还是上面的例子，你打电话问书店老板有没有《分布式系统》这本书，你如果是阻塞式调用，你会一直把自己“挂起”，直到得到这本书有没有的结果，如果是非阻塞式调用，你不管老板有没有告诉你，你自己先一边去玩了， 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。如果是关心blocking IO/ asynchronous IO, 参考 Unix Network Programming View Book作者：严肃链接：https://www.zhihu.com/question/19732473/answer/20851256来源：知乎注解二老张爱喝茶，废话不说，煮开水。出场人物：老张，水壶两把（普通水壶，简称水壶；会响的水壶，简称响水壶）。老张把水壶放到火上，立等水开。（同步阻塞）老张觉得自己有点傻老张把水壶放到火上，去客厅看电视，时不时去厨房看看水开没有。（同步非阻塞）老张还是觉得自己有点傻，于是变高端了，买了把会响笛的那种水壶。水开之后，能大声发出嘀~~~~的噪音。老张把响水壶放到火上，立等水开。（异步阻塞）老张觉得这样傻等意义不大老张把响水壶放到火上，去客厅看电视，水壶响之前不再去看它了，响了再去拿壶。（异步非阻塞）老张觉得自己聪明了。所谓同步异步，只是对于水壶而言。普通水壶，同步；响水壶，异步。虽然都能干活，但响水壶可以在自己完工之后，提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己（情况2中），造成老张效率的低下。所谓阻塞非阻塞，仅仅对于老张而言。立等的老张，阻塞；看电视的老张，非阻塞。情况1和情况3中老张就是阻塞的，媳妇喊他都不知道。虽然3中响水壶是异步的，可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的，这样才能发挥异步的效用。作者：愚抄链接：https://www.zhihu.com/question/19732473/answer/23434554来源：知乎
Nginx是俄国人最早开发的Webserver，现在已经风靡全球，相信大家并不陌生。PHP也通过二十多年的发展来到了7系列版本，更加关注性能。这对搭档在最近这些年，叱咤风云，基本上LNMP成了当下的标配。可是，你用了这么多年的Nginx+PHP的搭配，你真正知道他们之间是怎么交互怎么通信的么？作为一道常常用来面试的考题，从过往经验看，情况并不乐观。更多的同学是知道PHP-FPM、知道FastCGI，但不晓得Nginx、PHP这对老搭档具体的交互细节。那么，今天我们就来一起学习一下，做一回认真的PHP工程师。前菜为了讲解的有理有据，我们先来准备一个纯净精简的Nginx+PHP环境，这里我们使用Docker拉取Centos最新版本环境，来快速通过编译安装方式搭建一个Nginx+PHP环境。有了Linux环境，我们来源码编译安装Nginx、PHP，这个过程网络里有很多的教程，我们就不细说了。当然你也可以安装lnmp一键安装包来快速搭建。通过安装nginx、php，我们的Linux环境里就有了今天的这两位主角了。我们稍加配置，让Nginx可以接收请求并转发给PHP-FPM，我们目标是输出一个phpinfo()的信息。我们通过对Nginx新增Server配置实现了nginx与PHP的一次通信，配置文件非常简单，如下图有了上面的一个sample示例，我们开始深入Nginx与FastCGI协议。主食从上图的Nginx配置中可以注意到 fastcgi 开头的一些配置，以及引入的 fastcgi.conf 文件。其实在fastcgi.conf中，也是一堆fastcgi的配置项，只是这些配置项相对不常变，通常单独文件保管可以在多处引用。可以看到在fastcgi.conf中，有很多的fastcgi_param配置，结合nginx server配置中的fastcgi_pass、fastcgi_index，通常我们的同学已经能够想到Nginx与PHP之间打交道就是用的FastCGI，但再深问FastCGI是什么？它起到衔接Nginx、PHP的什么作用？等等深入的问题的时候，很多同学就卡壳了。那么，我们就来一探究竟。CGI是通用网关协议，FastCGI则是一种常住进程的CGI模式程序。我们所熟知的PHP-FPM的全称是PHP FastCGI Process Manager，即PHP-FPM会通过用户配置来管理一批FastCGI进程，例如在PHP-FPM管理下的某个FastCGI进程挂了，PHP-FPM会根据用户配置来看是否要重启补全，PHP-FPM更像是管理器，而真正衔接Nginx与PHP的则是FastCGI进程。如上图所示，FastCGI的下游，是CGI-APP，在我们的LNMP架构里，这个CGI-APP就是PHP程序。而FastCGI的上游是Nginx，他们之间有一个通信载体，即图中的socket。在我们上文图3的配置文件中，fastcgi_pass所配置的内容，便是告诉Nginx你接收到用户请求以后，你该往哪里转发，在我们图3中是转发到本机的一个socket文件，这里fastcgi_pass也常配置为一个http接口地址（这个可以在php-fpm.conf中配置）。而上图5中的Pre-fork，则对应着我们PHP-FPM的启动，也就是在我们启动PHP-FPM时便会根据用户配置启动诸多FastCGI触发器（FastCGI Wrapper）。对FastCGI在Nginx+PHP的模式中的定位有了一定了解后，我们再来了解下Nginx中为何能写很多fastcgi_*的配置项。这是因为Nginx的一个默认内置module实现了FastCGI的Client。关于Module ngx_http_fastcgi_module的详细文档可以查看这里: http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html 。我们关心一下我们图4中的这些fastcgi_param都是些什么吧，详细描述见下图。从图6中可以看到，fastcgi_param所声明的内容，将会被传递给“FastCGI server”，那这里指的就是fastcgi_pass所指向的server，也就是我们Nginx+PHP模式下的PHP-FPM所管理的FastCGI进程，或者说是那个socket文件载体。这时，有的同学会问：“为什么PHP-FPM管理的那些FastCGI进程要关心这些参数呢？”，好问题，我们一起想想我们做PHP应用开发时候有没有用到 $_SERVER 这个全局变量，它里面包含了很多服务器的信息，比如包含了用户的IP地址。同学们不想想我们的PHP身处socket文件之后，为什么能得到远端用户的IP呢？聪明的同学应该注意到图4中的一个fastcgi_param配置 REMOTE_ADDR ，这不正是我们在PHP中用 $_SERVER[‘REMOTE_ADDR’] 取到的用户IP么。的确，Nginx这个模块里fastcgi_param参数，就是考虑后端程序有时需要获取Webserver外部的变量以及服务器情况，那么ngx_http_fastcgi_module就帮我们做了这件事。真的是太感谢它啦！那么我们已经说清了FastCGI是个什么东东，并且它在Nginx+PHP中的定位。我们回到前面提出的问题，“它起到衔接Nginx、PHP的什么作用？”。对PHP有一定了解的同学，应该会知道PHP提供SAPI面向Webserver来提供扩展编程。但是这样的方式意味着你要是自主研发一套Webserver，你就需要学习SAPI，并且在你的Webserver程序中实现它。这意味着你的Webserver与PHP产生了耦合。在互联网的大趋势下，一般大家都不喜欢看到耦合。譬如Nginx在最初研发时候也不是为了和PHP组成黄金搭档而研发的，相信早些年的Nginx后端程序可能是其他语言开发。那么解决耦合的办法，比较好的方式是有一套通用的规范，上下游都兼容它。那么CGI协议便成了Nginx、PHP都愿意接受的一种方式，而FastCGI常住进程的模式又让上下游程序有了高并发的可能。那么，FastCGI的作用是Nginx、PHP的接口载体，就像插座与插销，让流行的WebServer与“世界上最好的语言”有了合作的可能。有了这些基础背景知识与他们的缘由，我们就可以举一反三的做更多有意思的事情。譬如我在前年曾实现了Java程序中按照FastCGI Client的方式（替代Nginx）与PHP-FPM通信，实现Java项目+PHP的一种组合搭配，解决的问题是Java程序一般来说在代码调整后需要编译过程，而PHP可以随时调整代码随时生效，那么让Java作为项目外壳，一些易变的代码由PHP实现，在需要的时候Java程序通过FastCGI与PHP打交道就好。这套想法也是基于对Nginx+PHP交互模式的理解之上想到的。网络中也有一些借助FastCGI的尝试与实践，譬如《Writing Hello World in FCGI with C++》这篇文章，用C++实现一个FastCGI的程序，外部依然是某款Webserver来处理HTTP请求，但具体功能则有C++来实现，他们的中间交互同样适用的FastCGI。同学们有兴趣了也可以做些Geek尝试。甜品通过本文的讲解，我们希望让大家看到，Nginx+PHP的工程模式下，两位主角分工明确，Nginx负责承载HTTP请求的响应与返回，以及超时控制记录日志等HTTP相关的功能，而PHP则负责处理具体请求要做的业务逻辑，它们俩的这种合作模式也是常见的分层架构设计中的一种，在它们各有专注面的同时，FastCGI又很好的将两块衔接，保障上下游通信交互，这种通过某种协议或规范来衔接好上下游的模式，在我们日常的PHP应用开发中也有这样的思想落地，譬如我们所开发的高性能API，具体的Client到底是PC、APP还是某个其他程序，我们不关心，而这些PC、APP、第三方程序也不关心我们的PHP代码实现，他们按照API的规范来请求做处理即可。同学们是不是发现技术思想是可以在各个环节融会贯通的，是不是很兴奋？很刺激？哈，同学们开心就好，祝大家在工作学习过程中，能挖掘到更多的好知识，提升自己的同时造福身边小伙伴！作者： Pangee 链接：https://www.imooc.com/article/19278来源：慕课网本文原创发布于慕课网 ，转载请注明出处，谢谢合作