Menu

tlanyan

十里平湖霜满天,寸寸青丝愁华年

记一次C++程序优化历程

近期用到了一位师兄写的C++程序,总体功能良好。使用不同的数据测试,发现了一个明显的缺点:大数据量下,预处理过程耗时很长。中科院的某计算集群,普通队列中的程序运行时间不能超过6个小时。而手上这套程序,大数据量下预处理就花了不止六个小时,结果当然是还没开始就被结束了。

和天河二号的工作人员联系,确认没有执行时间限制。于是开通了天河二号的账号,把程序扔上去跑。执行大数据量时,程序莫名被kill。询问技术支持,得知是内存耗尽,建议每个节点的进程数少一点。如此折腾了两次,大数据量的例子没跑通,大部分时间都费在预处理上,然后程序崩了,又要调整参数重新再来。

耗时长,最多是多花点机时,问题不大。但是没跑通的情况下每次要等五六个小时,然后才知道能否运行,测试然后反馈的过程太低效。忍无可忍,就开始进行优化吧!

第一步,找出耗时的点。原来的程序输出日志用的cout,没有附带时间,不能通过日志发现耗时的点。为了找出性能关键点,第一步是改进log,在输出中加上时间。写了一个Log类,替换掉cout,程序的输出中就带上时间了:

通过查看Log,定位到了耗时长的过程。

第二步,目测程序源代码,找出问题所在。该段代码比较好理解,主要是进行数据初始化和打标签。程序中规中矩,都是操控内存中的数组,没有磁盘、网络、进程通信等耗时调用。审查代码中发现第一个问题:内存重分配。程序声明了vector,没有指定大小,后续代码中使用push_back对数组的每一项进行赋值。内存分配和数据拷贝的代价是很大的,这应该是一个性能点。修改代码,声明时指定数组大小。编译并运行程序,结果表明省下了30%的耗时。

第二步,统计代码的工作量。耗时过程的初始化数据量,大概是整个数据量的10%,就算其中内嵌了两层循环,也不应该耗时如此多。为了查看是否有额外工作量,加入了计数器。运行结果显示,该段函数的计算量不大,耗时长应该有其他的原因。

第三步,根据经验判断是缓存失效导致。第一反应是用valgrind查看缓存命中,但valgrind模拟运行的效率太差,几个小时后kill掉放弃了。目测程序源码,发现很多数据都是从全局内存读取,没有充分利用缓存。修改代码,使用局部变量缓存全局数据,接下来代码中的数据使用缓存数据。经过测试,效果非常明显,提升了50%的效率。

第四步,查找其他性能热点。经过几次小的调优测试,发现一些全局内存访问不可避免(随机访问,无法利用缓存),按照目前的方式难以继续优化。要大幅降低耗时需要重写算法,目前无法保证对算法和程序意图十分了解,遂暂时作罢。

优化前后的结果对比:中等数据规模下,耗时从8’43″降到3’25″;大数据量下,耗时从4h38’44″降到1h49’21″(注:使用自己的机器测试,CPU主频3.46GHz,比中科院和天河二号集群的CPU主频都要高,所以耗时短)。从数据看出,效果还是很明显的。

PHP回顾之类自动加载

PHP回顾系列目录

在autoload机制引入前,要引用其他文件中的函数和类需使用include/require把文件加载。随着SPL库中的autoload机制以及命名空间的完善,现代化PHP开发鲜少见到以include/require的方式加载类。

使用require/include加载类的方式是直接的:包含类定义所在的文件,类的定义自然就有了。在规模稍大的项目中,这种方式会导致每个PHP文件开头都是一堆include/require。如果某些文件中定义了一些全局常量,会导致不时抛出重复定义的警告。如果使用第三方类库,还需要了解库的结构,以便正确的引入文件。可以看到,这种方式加载类是低效且乏味的。

PHP5引入了__autoload函数来简化类的加载工作。如果一个函数的定义不存在,该函数总是被调用。开发人员可通过这个函数实现类文件的加载,例如:

但是该函数不被建议使用,并在PHP7.2中被废弃。被废弃的原因是PHP不允许函数重名,所以一个项目中仅能出现一个__autoload函数。自己写的代码保证只有一个__autoload函数虽然有点难但也能做到,要是第三方库也定义了__autoload,那就很头疼了。__autoload的后继者是spl_autoload_register函数,这才是PHP解决类自动加载的利器。

spl_auoload_register函数的参数是一个回调函数,调用该函数会将注册的加载函数放入autoload函数队列中。无法找到类的定义时,Zend引擎会顺序回调队列中的函数,直到类定义找到或者抛出类未定义的异常。使用spl_autoload_register,有几个值得注意的点:

  1. 理论上可以注册无限个加载函数,相比之下__autoload只能被定义一次;
  2. PHP引擎会顺序调用注册的函数。从效率考虑,最常用类的加载函数应该放在队列的头部;
  3. spl_autoload_register注册的函数,是PHP给开发人员最后的机会协助找到类,否则直接抛异常。new, static调用,class_exists的操作,都可能会触发调用注册的加载函数;
  4. spl_autoload_register常和PSR-4规范搭配使用,实现方式高效且优雅。

有了spl_autoload_register,程序中再也不用一堆include/require,取而代之的是使用use。可能有人觉得使用一堆use没见得比使用一堆require/include方便。实际上,use的好处有以下几点:

  1. 同一个名字空间下的类,不需要通过use导入;
  2. PHP7中可以以组的方式引入类,例如use app\utils{A, B, C},省行数又方便;
  3. use可以为类设置别名,避免同类名引发的错误;
  4. 使用use,无需考虑绝对路径、相对路径等问题,都交给注册的加载函数处理。

有了spl_autoload_register,让PHP的面向对象编程更为完善和优雅,也让PHP以更现代化的方式运行。

除了spl_autoload_register,spl_autoload系列函数还有spl_autoload_call/spl_autoload_extentions/spl_autoload_functions/spl_autoload_unregister。这几个函数一般程序开发中很少用到,开发类库时可能会使用的上。

解决“DNS problem: query timed out looking up CAA for xxx”的错误

昨天使用certbot续期证书,输入certbot renew,没出现预期的“Congratulations, all renewals succeeded.”,而是意外的“DNS problem: query timed out looking up CAA for tlanyan.me”。咦,怎么突然就出错了呢?

按照以往的经验,首先确认域名记录存在,并且443端口已经开启。于是登录到国内和国外的几台服务器,执行telnet tlanyan.me 443,无一例外的返回“Connection refused”,说明域名解析正常并且443端口做好了连接验证的准备。用dig命令查询域名解析,返回记录存在并且IP指向服务器。

上Google查原因和解决方案,发现不少人遇到同样的问题。在Let’s Encrypt的官方论坛和Server Fault,回帖基本上是针对个例的分析和解答,关键是我还没看出问题原因和可行的解决方案。由于之前出现过域名校验失败导致certbot返回错误,而过几小时再试就OK的例子,心想可能Let’s Encrypt服务器抽风了,明天也许就可以了。

今天登陆服务器执行命令,问题依旧,知道这次是真有问题。要解决错误,首先要是弄清楚错误提示中的CAA是什么。通过dnsimple.com上的科普文章,了解到CAA是证书颁发机构授权的缩写,即显式指出可以给这个域名颁发证书的机构。域名持有人通过设置CAA记录,让授权的证书颁发机构才能对域名签发证书,以增强网站安全性。该记录不是必须的,如果不存在,则所有的证书机构均可对域名颁发证书。要查看域名的CAA记录,可以用dig命令,指定资源类型为type257,例如:dig tlanyan.me type257

明白了CAA和其作用,再回头看certbot的报错,说的是CAA记录没找到,所以不能给域名颁发证书。域名用的dnspod解析,管理面板上不能增加CAA记录。在Google上搜索“dnspod caa”,发现有人吐槽dnspod导致Let’s Encrypt出现颁发证书报错的问题。解决办办法也很简单:换一家dns服务商。

于是把域名解析换成cloudxns,certbot报错立马消失,证书顺利续签。如果你使用dnspod,遇到了同样的问题,建议你换一家服务商,或者付费购买证书。

顺便说一下,换掉dnspod后,无需添加CAA记录也能正常查询到CAA记录。网上分析是dnspod为了安全,把一些查询给屏蔽掉了,导致CAA记录的查询超时。

最后推荐一下cloudxns这家免费的域名解析服务商,网站比dnspod好用不少,功能也更丰富强大。文中提到的CAA记录,cloudxns原生支持,还支持一键添加国内各大邮箱的解析,方便贴心!

参考

  1. https://support.dnsimple.com/articles/caa-record/
  2. https://w4ng.com/nginx-use-letsencrypt-ssl-https/

天河二号使用说明

准备工作:联系天河二号工作人员,填写申请表,审核通过后获得账号、密码、登录密钥和使用说明手册。

以下是在天河二号运行程序的一些总结,主要参考天河二号官方下发的使用说明。

连接

连接天河二号机器前,需先登录VPN。如果未安装VPN,先进入网页https://vpn3.nscc-gz.cn:4433下载Windows客户端,然后安装并运行程序。在出现的登录界面中填入以下信息:

如果信息无误,点解登录后VPN客户端会最小化到右下角托盘并显示绿色图标。

登录VPN后,才可以用SSH方式连接到天河二号的登录节点。登录节点默认的认证方式是免密私钥认证,需提供开设账号时分发的私钥作为凭证。使用OpenSSH客户端连接的示例为:

private_key在windows下默认为755权限,OpenSSH会拒绝使用安全性低的私钥,chmod保证读写权限均为私有。

运行环境

通过一些命令可以探测到天河登录系统的运行环境。

  • uname -a: 系统的内核版本是2.6.32
  • cat /proc/cpuinfo: 登录节点使用了intel芯片,含4个物理CPU,每个CPU8核心16线程
  • free -g: 登录节点配备了128G内存
  • df -h: 大约5P的硬盘存储
  • gcc -v: gcc版本为4.4.7
  • icc -v: 系统默认的icc版本为14.0.2,其他版本需使用module命令加载
  • mpicc -v: mpi版本为3.1.3,使用icc 14.0.2编译
  • make -v: make版本为3.81
  • cat /etc/hosts|grep ln: 系统有几十个登录节点,这也解释为了登录的时候会经常提示指纹不匹配
  • module avail: 系统支持模块,高版本的GCC/CUDA/FFT等均可以模块方式加载,无需自行编译
  • ping cn.bing.com/ping 114.114.114.114: 提示找不到主机,表明机器无法访问外网

通过查看yhinfo等命令的帮助,可以看到任务管理系统使用的是slurm。把slurm命令中的s替换成yh,则是yh系列的各个命令。

计算节点无法登录,可以通过yhrun执行程序探测。以下是一些计算节点的信息:

CPU: Xeon(R) CPU E5-2692 v2 @ 2.20GHz(未启用超线程)
内存: 64G
操作系统:天河自编译的Linux系统,内核版本2.6.32

程序运行

按照官方指南,程序的源码和编译可在主目录下,运行时的存储应放置在BIGDATA目录中。程序运行的命令主要是yhrun和yhbatch,yhrun是交互式提交作业,yhbatch是批量提交作业。实际使用中建议用yhbatch提交作业。

yhinfo/yhi

通过yhinfo或者yhi可查看用户可用的节点状态。命令的输出中,需要关注的是PARTITION和STATE两列。PARTITION指示任务可提交到的分区,在yhrun中会使用;STATE表示节点状态,只有idle的节点才是可直接使用的。

要查看所有分区/队列信息,可加上-a选项。

yhrun

yhrun用来交互式提交作业。基本用法是: yhrun [options] program [program-options]。options中,常见指定如下选项:

  • -n: 任务数。在MPI作业中,即进程数
  • -p: 任务运行分区。可用分区通过yhi命令查看
  • -N: 节点数。希望作业运行在几个节点上
  • -c: 任务使用的核心数。该值默认为1,非openMP程序一般用不到,指定了也不影响运行。

天河二号的每个计算节点配置24核,并且是独占式的。这意味着节点在同一时间只能被一个用户所使用,为了节省结算计时,尽量让N=[n/24],或者不指定-N参数。

yhrun可以替代mpirun使用,而不是简单的在各个主机上执行相同程序。例如在多个进程上运行非MPI程序:yhrun -n20 -p free hostname,结果将返回进程所在节点的主机名,结果类似于pssh执行同一条命令。通过yhrun运行的程序能否让MPI程序的各个进程正常进行通信和交互,刚开始尚有疑惑。

为了检测yhrun和mpirun有相同效果,写了一个小demo进行校验。以下的测试代码:

编译和运行:

从结果上看,yhrun的执行效果和mpirun一致,说明yhrun确实可以替代mpirun执行有通信的多进程程序。

yhbatch

mpirun是交互式的提交作业,这意味着会一直占据终端,直到程序开始计算并将结果返回回会释放终端。大部分的程序运行时间周期都较长,在等待期间,如果用yhrun,用户只能等待结果或另开连接,否则无法进行其他操作。实际使用中,应使用yhbatch提交作业,yhrun执行具体任务。作业系统在yhbatch提交时申请的资源满足后,调度执行任务并将结果存放到用户的目录下。

yhbatch命令选项和mpirun命令相差不大,猜测的主要不同是yhbatch是向调度中心进行资源申请,只有满足这些条件才运行用户的程序,否则继续等待。

使用yhbatch首先建立一个脚本文件,里面放入程序执行命令:

然后使用yhbatch提交:chmod u+x ./batch.sh; yhbatch -n10 -pfree ./batch.sh。yhbatch中的-N等参数应该不小于yhrun中的参数,否则运行时的进程数和节点可能并非所想,甚至出现资源错误提示。

yhqueue/yhq

yhbatch提交作业后,使用yhqueue/yhq可查看正在运行的任务状况。

yhcancal

提交到任务队列后,yhq可查看提交或者正在运行的任务id。如果想终止任务,使用yhcancel。

其他事项,请参考官方说明手册。

Gmsh网格文件格式说明

本文中的网格文件指的是Gmsh的ASCII文本文件,内容主要参考Gmsh的官方文档:http://gmsh.info/doc/texinfo/gmsh.html#MSH-ASCII-file-format

文件结构

Gmsh网格文件由一个或多个块(section)构成。块定义以$Name开始,$EndName结尾,例如$Nodes和$EndNodes共同定义一个块。$MeshFormat块是必须的(第一个出现的块),$Nodes, $Elements等块可选。如果定义了$Elements块,则必须给出$Node块。目前$Nodes和$Elements块在一个文件中只允许出现一次(未来版本可能会更改),其他块可重复多次。如果遇到了未定义的块,Gmsh将跳过该块。利用此特性可以为Gmsh网格文件增加注释,例如放在$Comment和$EndComment包裹的块中。

块格式

以下对常见的各个块格式做说明。

MeshFormat

$MeshFormat块给出三部分信息:

  1. 文件格式版本号,目前是固定值2.2;
  2. 文件类型,在ASCII文件中值为0;
  3. 数据长度,即单精度浮点数所占字节数,通常为8。

一个典型的MeshFormat块:

Nodes

Nodes块第一行为顶点总数,接下来每一行按照“顶点编号 X坐标 Y坐标 Z坐标”的格式定义一个顶点。Gmsh是三维网格生成器,二维顶点只需把Z坐标设置为常数即可。

一个典型的Nodes块:

Elements

注意:Gmsh默认Nodes块出现在Elements之前。

Elements块是网格中最关键的块,也是结构定义相对复杂的块。Elements块的结构如下:

网格单元的顶点数由单元类型决定,因此定义行中未出现顶点数字段。

理论上,网格的顶点数和tag数可以是不小于0的任意整数,所以定义网格单元的行长度不固定,单元可能有不同数量的tag以及顶点。

Gmsh支持的主要网格类型编号如下:

其他高阶网格定义请参考官方文档

常规情形下,每个网格单元都包含如下tag:一个指示网格所属的物理实体的tag,个个指示网格所归属的几何实体的tag,一个指示网格单元所在的分区编号的tag。Gmsh和大多数代码要求单元至少包含前两个tag。

顶点列表给出组成网格单元的顶点编号,其中编号是出现在$Nodes块中的顶点编号。对于高阶网格单元,Gmsh按照如下顺序对顶点进行编号:

  1. 单元的基本顶点
  2. 每个边上的顶点
  3. 每个面上的顶点
  4. 体顶点

总体原则即:高阶网格顶点出现在低阶网格顶点之后。不能处理高阶网格的程序,只需要读取低阶网格顶点即可。

其他块的字段含义请参考官方文档。

效率提示

Gmsh没有要求元素的编号是连续的。但出于效率考虑,请尽量使用连续、紧凑的编号。例如两个顶点的编号,不用0和1,而是1, 10000,会导致程序性能下降:Gmsh不得不使用map而非数组来存储和访问元素,后处理程序中会分配10000个单元(大部分是无用的空值)的数组,而这些代价都是可避免的。

crontab中的ss

使用crontab对服务器上的运行情况进行信息收集和监控,检测端口是否正常监听用到ss命令:

在crontab的标准输出日志中,发现无论是否正常监听端口,ret的值总是空。手动执行脚本,ret的信息就正常。奇了怪哉。

在脚本中加上调试信息: set -x,同时将错误信息也输出到log中:* * * * * ~/foo.sh > ~/foo.log 2>&1。等新log生成,发现问题:“ss: command not found”。OK,cron执行任务时找不到ss命令。

继续跟踪为何ss命令不能在crontab中使用:which ss查看ss的路径,定位ss在/usr/sbin目录内;接着在crontab中打印PATH信息,输出为:“/usr/bin:/bin”。于是真相大白:ss确实不在crontab执行时的路径下。

问题的修复也很简单:export PATH=/usr/sbin:$PATH,将这个命令放置在ss的命令之前。

顺带一提,在/etc/crontab文件,/usr/sbin目录已经被包含在执行时的路径内:PATH=/sbin:/bin:/usr/sbin:/usr/bin。

文本文件、二进制文件、字符编码

最近被文件搞迷糊了,又和字符编码搞混,不明其中原理导致心烦意乱。昨晚梦到了字符转码等操作,感觉快要走火入魔。今早起床后抓紧看了几篇文章,细细体会,终于消掉文件和编码的疑惑。

文本文件和二进制

最初的疑惑是:文本文件和二进制文件有什么区别?为什么一个能显示内容,另一个的内容经常无法正常显示?

马里兰大学的这篇培训笔记,把两者的区别讲得清楚:文本文件是二进制文件的一种,底层存储也是0和1,与二进制文件没有区别;文本文件可读性和移植性好,但表现字符有限;二进制文件数据存储紧凑,无字符编码限制。在文本文件中,只能存放数字、文字、标点等有限内容;二进制没有字符约束,可随意存储图像、音视频等数据。友好和易容程度上,文本文件大幅胜出。能看懂0和1组合的很少,编辑16进制的数据也是少数人的特殊技巧。

二进制文件的内容,一般无法通过常规编辑器查看,原因是文本编辑器只能显示可见字符(可见字符只有几万个),而二进制中的数据大多是不可显示的。例如一个64位的整数,二进制表示(只)需要八个字节。若以可见字符表示,则需要1-20个字符(从个位数到20位)。常规的文本编辑器,不能分辨整数、浮点数、指针等,只会解析字节。如果字节无对应字符,只好以乱码相待。

编码

说到字符展现,绕不过字符编码。常见的127个ASCII字符,没啥编码好说的,反正几乎所有的编码方式都兼容它。双字节、多字节字符,编码方式和字节序,才是困扰程序员的问题。一个汉字,GBK编码需要两个字节,还要考虑本机的大小端,才能确定存放的最终形式。进行网络通行时,要转换成网络字节序(大端序),接收方才能正常解析。许多开发人员对字符编码不熟悉,通信时遇到乱码问题,调试就很困难。

USC(Universal Multiple Octet Coded Character Set)标准的制定,让开发人员远离混乱的双字节字符集。USC标准里,所有的字符都有了唯一的码点,根据码点就可查到对应字符。USC要求所有的字符都用两个字节(USC-4标准是4个字节),即使是ASCII字符。由于使用了两个字节,可容纳2^16-1(6w+)字符,足够装下各国所有的字符。USC只是一个标准,不对应具体的编码方式。

USC解决了字符集的问题,但是对纯ASCII文件,浪费了50%的空间。接着UTF-8登场,这是一种变长的字符编码方案,既兼容了ASCII码(不浪费ASCII文件的空间,但是浪费汉字文件的空间,哭!),顺便还消除字节序的困:。UTF-8是单字节流,不存在字节序问题,也不需要BOM。目前UTF-8是web通行标准。

对应关系

USC-2与UTF-8的对应关系如下:

十六进制二进制
0000 0000-0000 007F0xxxxxxx
0000 0080-0000 07FF110xxxxx 10xxxxxx
0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

从编码可以看出,与二进制相比,浪费了很多空间。不过这也没办法:谁让人类的脑袋只能理解可以显示的字符呢?

UTF-8转换规则为: 1. 如果某字节第一位是 0 ,那么判定为 ASCII 字节,除了 0 外余下的 7 位是 ASCII 码,所以 UTF-8 是兼容 ASCII 码的; 2. 如果第一个字节是 1 ,那么连续的几个 “1” 代表从这个字符开始,后面连续的几个字节其实是一个字位,且后面的字节都要以10开头。

了解如上规则,我们的程序便可轻松的处理UTF-8编码的字节流。例如要找出“中”的UTF-8编码,则可以这样处理(注意文件是UTF-8编码):

也可以写出针对UTF-8编码的strlen函数:

了解其原理,和其他语言对接,同样也不是烦恼了。

参考

  1. https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/asciiBin.html
  2. http://www.unicode.org/faq/utf_bom.html#BOM
  3. https://my.oschina.net/goal/blog/195749
  4. http://mp.weixin.qq.com/s/2H6LZME03pUFNWBLNl0izw

PHP回顾之加解密

PHP回顾系列目录

经过CSDN的密码泄露、各大平台和论坛被拖库等安全事件,但凡有一点安全意识的开发人员都知道不能存放明文密码(当然单纯md5同样会被鄙视);信用卡及CVV等非常敏感的客户数据,需要经过多层加密后才存放到数据库中。

数据的安全处理包含加密和解密两个过程。加密后结果有两种:无法还原明文(不包括穷举和暴力破解)和可还原成明文。第一种是单向加密模式,一般用于校验;第二种加密有与之对应的解密过程,根据算法和密钥能得到加密前的结果。两种加密模式在实践中都有广泛应用场景。例如,用户的密码,只需要进行安全的hash即可,即使数据泄露,获取明文密码的可能性也大大降低;用户的手机号则需要加密存储,发短信通知用户时,将密文还原成手机号再使用,避免被拖库后泄露用户信息。

加密算法

PHP和加解密相关的拓展主要有: Hash,Mcrypt、OpenSSL以及在PHP 5.5中引入的密码hash API。以下分别做介绍。

Hash

Hash拓展如其名,提供了各类常见的hash算法,如md5,、SHA系列、hmac、pbkdf2等。计算字符串或者文件的hash值,该拓展内的函数基本能满足要求。拓展内常用的函数包括:

  • hash_algos: 列出拓展支持的hash算法
  • hash/hash_file: 根据给定的算法和数据/文件,计算hash值
  • hash_hmac/hash_hmac_file/hash_pbkdf2: 计算给定数据/文件的hash值,需要提供密钥或者盐等额外数据

常用md5和sha1函数,可用拓展内的函数替代:md5(‘xxxxx’)等同于hash(‘md5’, ‘xxxxx’),sha1同理。

Mcrypt

Mcrypt主要提供双向操作的各类算法,例如DES、3DES、AES等算法。Mcrypt依赖于libmcrypt项目,但是该项目自2007起不再维护,这导致Mcrypt拓展在PHP 7.1中被废弃,在PHP 7.2中被移除(项目迁移到PECL中)。在新项目中不建议使用Mcrypt拓展 ,官方推荐使用OpenSSL拓展替代。

和hash类函数相比,mcrypt中的加解密函数使用门槛更高。要熟练使用mcrypt,需要理清楚几个概念:加密算法(cipher)、分组模式(encrypt mode)、密钥(key)和初始向量(initialization vector, IV)。常用的加密算法包括DES/3DES/AES-128/AES-192等,分组模式有CBC/CFB/ECF/OFB等,密钥是用户用来加解密的字符串,某些算法和分组模式组合需要初始向量来驱动。

根据算法和分组模式,加密时操作的数据块大小(block size)会有差异。不是数据块大小整数倍的数据,需要进行填充后才可使用。遗憾的是,mcrypt对数据填充没有支持。同理,密钥和iv的长度必须与算法和模式要求的相符。

所以,要使用mcrypt加解密,需要上述四个要素:算法、分组模式、密钥和初始向量(非必须,某些算法和分组模式组合有要求)。mcrypt中的函数,除了真正执行加解密的函数(mcrypt_encrypt/mcrypt_decrypt、mcrypt_generic/mdecrypt_generic),其余函数可以归类为辅助获取上述四个要素中的一项。例如,使用mcrypt_list_algorithms函数获取支持的加密算法;mcrypt_get_iv_size和mcrypt_create_iv用来获取初始向量。四项要素准备齐全后,输入数据即可进行加解密(注意:数据可能需要填充,填充长度依赖于算法和分组模式)。

OpenSSL

OpenSSL拓展既包含hash类的算法,也提供双向操作的DES/AES等算法,还支持非对称的RSA算法、安全的随机数生成等功能。由于OpenSSL拓展的能力依赖于openssl库,其功能将会不断完善,建议熟悉和使用。

openssl_get_md_methods函数可获取所有支持的hash算法,使用openssl_digest函数可计算hash值。对于pbkdf2算法,可直接使用openssl_pbkdf2函数。

openssl_get_cipher_methods函数获取所有的加密算法,然后可使用openssl_encrypt/openssl_decrypt函数进行加解密。

openssl库提供了openssl_random_pseudo_bytes函数来生成随机的字符串。如果程序中需要随机数,建议使用该函数而非自行写算法生成。

此外,openssl提供了对证书的支持。可以从证书中读取证书、使用公钥和私钥对数据进行签名和校验。相关的函数包括:openssl_get_publickey/openssl_get_privatekey、openssl_private/public_encrypt/decrypt、openssl_sign/openssl_verify等函数。详情可参考官方的openssl拓展文档

OpenSSL依赖于openssl库,建议系统中安装新版本的openssl库以保障安全性。

密码hash API

(猜测)密码的加密和验证对许多人是个难题(或者用弱鸡的行为来实现),无奈之下PHP官方在5.5版本中内置了密码加密和验证的原生支持。密码hash API主要是针对密码加密和验证:passwod_hash加密,password_verify验证密码,就是这么简单!

如果翻阅文档,发现此拓展总共四个函数。除去加密和验证,其余两个分别是password_get_info和password_neets_rehash。第一个函数返回加密数据的信息,第二个函数则校验加密数据是否与指定的算法相匹配。可见官方为了人民群众科学的使用密码而专门搞这么一套API,也是用心良苦了。

password_hash使用了号称后现代主义的加密算法:bcrypt(默认,目前也只有该算法,后续可能会加入其它算法)。使用该函数,能极大的提高密码的安全性,强烈建议使用。

具体实践

具体业务开发中,约定好加密方式,调用已有的库函数即可解决绝大部分数据加解密问题。如非必要,千万不要自己实现一套加密算法,除非你是密码学专家。例如hash类算法的一致性和雪崩性,加密类算法的安全性依赖于密钥而非算法原则,非算法专业的很难设计出,即使实现也不一定能经得住生产环境的考验。

如果约定的协议不是某个具体的加密算法,还包含了一系列前处理和后处理,就要视情况而定。PHP没有字节流的概念(使用字节流需掌握pack/unpack函数),string或者char才是最直观的,md5或者hash_hmac中的row_output参数都很少用(不一定懂含义),二进制或者字节流就更难为人了。如果对方先获取数据的字节流,做了一系列流处理和加密操作,最后base64了一下,PHP这边可能除了base64能看懂,其他的就懵逼了。这种情况下,建议找有经验的老司机写好算法后直接调用,课后自己多补补密码学的东西加深理解。

密码知识是业务中用到的一个工具,相信大部分人可以完全无视之(因为有现成API可调用)。如果能理解,就是最吼的,毕竟提高安全意识是值得称赞的。

一加一加

警告:前方多图,流量党慎入!

本文的主角是一加小傻狗:

而不是一加手机的一加:

虽然一加手机的价格还比一加小傻狗还贵那么一点,但是一加小傻狗给生活带来的影响远不是一加手机能比的。

关于名字

一直不是很喜欢泰迪这类的小狗,觉得这类宠物狗是人们为了自身喜好而刻意培育出的品种,丧失了狗的本性。理想中养的狗应该威风活泼,对主人忠心耿耿,在内能镇家,在外能助人威风的狗。养狗之前稍微了解了一下,决定了阿拉斯加是我的菜:颜值方面看,幼年的阿拉斯加可爱呆萌,成年的体格强壮;性格上说,成年阿拉斯加对忠厚善良,踏实可靠。 于是就这么愉快的决定了买阿拉斯加。

一加的名字,把它抱出来的时候就决定了:一加!理由很简单:这是我养的第一只狗,品种是阿拉斯加,所以叫一加;另外一加手机时我很喜欢的手机品牌,叫一加上口贴切。

缘起

刚买回家的小一加饥肠辘辘的样子,对什么都很好奇:

偶尔一加也有高冷的样子:

晚上溜一加,在办公室的一加:

可惜的是花好不常在,长大后的一加就长残了,再也回不到小时候纯真可爱的样子。放一张长大后的样子,对比感受一下:

换新家

一加来了之后,就要照顾一加。之前工作日住城里,周末到郊区的生活不存在了,因为周末也要喂一加!好在一加来了后两周,就搬到郊区去住了,不用来回跑。不好的地方是离上班的地方远,中午不能回家喂一加了。

搬家的路上:

一加似乎对新家也比较满意,很快适应了新家,没有出现晚上狼嚎。

玩具

一加对一般的玩具没太大兴趣,倒是对奶瓶和拖鞋情有独钟。因为一加喜欢玩奶瓶,那段时间每天喝爽歪歪,想想都是泪啊!!

有天出去溜一加,一加对一位老爷爷锯下来的木头挺有兴趣,就问老爷爷要了一个带回家玩。玩了两天后食欲不振,去宠物店打针吃药,担心木头有病菌,就扔了。

作为必要的一环,一加貌似对我的脚也挺感兴趣,不过就是闻完之后心情不大好。比如闻了之后想自杀:

又比如被我当脚垫后,激动不已:

生活

由于平时都要去上班,只能把一加自己锁在家里,晚上回来喂食。尽管这样,一加还是傻乎乎的,感觉非常的开心。

如果不是天气不好或者太忙,晚上一般都会出去溜的:

偶尔周末天气好,也会出去放风:

阿拉撕家

一加从来不算省心的狗,一直保持了阿拉撕家的传统。刚买回来的第二天,就给我生动形象的上了一课:

经常早上起来发现一片狼藉:

啃拖鞋,啃凳子,啃沙发当然也是一把好手!

饮食

刚开始听说阿拉斯加肠胃不好,都不敢乱喂东西。换狗粮都小心翼翼的,生怕一不小心把一加养死了。后来发现一加啥都吃,胃口棒棒的,也就放心了。一加最喜欢吃西瓜了,每次吃西瓜都眼巴巴的看着我,所以每次都是一边吃一边喂,喂完再把瓜皮给一加啃。

一加吃樱桃和蓝莓,香蕉桃子等来者不拒,相比较而言钟情蓝莓。

洗澡

把一加拿过来的时候,一加正好一个月。太小了不能洗澡,所以身上有股味道。买了干洗粉、空气清新剂等,只能稍微缓解一下,体味还是很重的。熬到三个月,也把预防针和狂犬病打了,终于可以给一加水洗了,不过一加好像不是很开心。

可惜的是,洗澡也不能去除狗味,挺麻烦的。

青蛙趴

估计是阿拉斯加的通病,一加很喜欢像青蛙一样的趴在地上,现在回想起来还挺可爱的。

剪毛

从北京回来后,第一件事就是把一加的毛剃了,顿时感觉没那么可爱了。伤感的是,剃毛后的一加是最后的印象。

别离

当初养狗的一个心理预期是:如果我不想养狗了(比如说有小孩),可以把狗送给我妈养。今年暑假我妈说要来这里玩,我还挺高兴的。一加这么可爱,我妈看到了肯定也喜欢。万万没想到的是,我妈进门看到一加说的第一句话是:我最讨厌狗了!听得我心碎了…

由于一加在家里,所以家里有狗味和狗毛。外加没怎么训练一加,一加的尿比较随意,经常要拖地。我自己倒是觉得还好,每天拖地四五次当锻炼身体了。我妈和小侄子来了之后,对狗味和狗毛比较反感,更因为这不能在家吃饭而生怨言。在我妈在的那些天,明显感觉到一加心情低落了不少。除了我之外,其他人都不待见一加,还总是嫌弃它,骂它臭狗。据说我不在家的时候,一加就自己乖乖躺一旁,我回来了后就对我特别亲热,总是粘着我。

刚开始几天还好,过了几天我妈实在是忍不了狗毛狗味还有捡狗屎(我不在家我妈只好自己捡),要我把狗卖掉。我嘴上答应着,心里并没有想着要卖掉。一加在的这两个月,虽然给我带来了不少麻烦,但卖掉或者送走也是很难接受的。每次我妈问我有人要没,我都随口说在联系了。

我妈终于还是受够了,于是对我说白送还没人要吗?我心想一加这小傻狗,估计自己还不知道要到人生的转折点吧。我妈倒还好,反正不是常住,让不让一加走要看后续的日子。于是问我老婆,想不想养一加。从她脸上勉强的表情上来看,一加的命运真的要发生重大转折了。

最后还是决定把一加送走。希望让一加找个对它好的新主人,所以也没想着卖钱。送走一加时,从电梯口看到一加不明所以的眼神望向我,这是对一加最后的印象,想想都伤感。

送走一加后,我妈和我老婆花了好几个小时把家里都重新打扫了一遍。焕然一新的家里,只是没有了一加。

终曲

一加走后,翻看一加的照片和视频,才发现不知不觉中一加已经长大了好多。越看就越对一加思念,也对自己没照顾好一加而自责。没顶住压力把一加送走,着实把肠子都悔青了,但是又不好开口要回来。完美的结局当然是对方说不想养了,然后我把一加接回来,皆大欢喜,可惜这只是幻想。

在微博上不时看到狗的照片,常常萌生出了再养狗的想法。冷静下来又觉得自己有病,把一加送走了又要养狗!一加不在的时间越长,就对一加的想念越深。刚开始被一加牙齿划出血,担心受怕的去医院打狂犬疫苗;最初舍不得打一加,导致裤子总被一加咬出洞,后来揍了几顿就老实了;一加身上的狗味,外出随性拉屎要处理,等等这些一加在时的烦恼,现在都觉得没什么,也快忘记了。现在能想起的是一加傻乎乎的模样:蜷在脚边啃椅子桌腿、眼巴巴瞅着我吃东西希望我喂一口、回房间后总是过来敲门、晚上睡觉就趴在门口睡、晚上独自像青蛙一样趴在阳台边上凉快,等等平时习以为常的模样,却让我思念不已。虽然我也经常说一加傻,但是傻乎乎表情和眼光中透露的忠厚、善良、纯真和无辜,每每想起都心生怜惜和愧疚,于是对一加的评分多一分,心中的懊悔增加一份。

曾经一加难为狗。

最后来一张一加早上六点多敲房门,把我吵醒后,开门发现一加一脸无辜的照片:

解决docker容器中” Could not resolve host”的报错

为了测试容器互联,在机器上开了两个不同的容器。启动后发现作为客户端的容器没有telnet等常用网络工具,于是执行 apt-get update & apt-get install telnet命令安装。命令意外地出现了如下的错误输出:

看错误信息是解析不了软件包仓库的域名,导致无法获取软件包列表。尝试ping一下www.baidu.com,输出:“ping: unknown host”。接着cat容器的/etc/resolv.conf,和宿主机的resolv.conf对比,发现内容一致。尝试ping 114.114.114.114,没有回应。这几个现象表明容器网络出了问题,导致无法ping通外网,也无法解析域名。

通过google,找到了栈爆网上的一个帖子,顺利解决了问题。解决步骤如下:

  1. 找出宿主机的dns: cat /etc/resolv.conf。一般是两个,例如: 10.0.0.2, 10.0.0.3;
  2. 编辑/etc/docker/daemon.json文件(该文件不存在,需新建),输入内容:

  1. 重启docker服务: systemctl restart docker。此条命令将会关掉所有的容器。

通过以上三个步骤,即可在容器内正常进行dns解析并顺利访问外网。可以通过启动alpine容器验证:

docker run -it alpine ping www.baidu.com

输出如下:

参考

  1. https://stackoverflow.com/questions/24991136/docker-build-could-not-resolve-archive-ubuntu-com-apt-get-fails-to-install-a
Scroll Up