ubuntu24.04 php7.4.33编译安装扩展openssl.so

0.ubuntu24.04 下默认安装的是 openssl3.0,php7.4.33只支持openssl1.1

1.下载OpenSSL 1.1.1w源码,编译安装,本人安装在/usr/local/ssl

2.进入到php-7.4.33/ext/openssl下,执行 phpize
如果出现Cannot find config.m4;可以把config0.m4改为config.m4
export PKG_CONFIG_PATH=/usr/local/ssl/lib/pkgconfig
export CPPFLAGS=-I/usr/local/ssl/include
export LDFLAGS=-L/usr/local/ssl/lib
export OPENSSL_CONF=/usr/local/ssl/openssl.cnf

./configure --with-openssl=/usr/local/ssl
make -j && make install

3.配置扩展证书:openssl.capath=/usr/lib/ssl/certs

macos 性能测试 内核优化配置

解决mac 在糟糕网络下 tcp/http 等连接较高概率超时失败问题

经过定位,应该是系统的tcp 和 socket连接数太小的问题,导致开大量应用后会出现该问题

1.增加 max files:

解决mac 在糟糕网络下 tcp/http 等连接较高概率超时失败问题

经过定位,应该是系统的tcp 和 socket连接数太小的问题,导致开大量应用后会出现该问题

1.增加 max files:

sysctl -a | grep files
kern.maxfiles = 12288
kern.maxfilesperproc = 10240

#设置系统最大连接数从12288到1048600.
sudo sysctl -w kern.maxfiles=1048600

#设置进程连接数限制,进程的最大连接数要小于等于全局连接数
sudo sysctl -w kern.maxfilesperproc=1048576

2.增加max sockets:

sysctl -a | grep somax
kern.ipc.somaxconn: 256
sudo sysctl -w kern.ipc.somaxconn=4096

3.设置动态端口范围portrange

sysctl net.inet.ip.portrange
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535

说明:Linux动态端口号默认范围是32768-65535,也就是说,作为客户端连接同一个IP和同一个端口号,最多只能建立30000多个连接,而Mac默认只能建立16000个左右的连接。
sudo sysctl -w net.inet.ip.portrange.first=32768

4.tcp最大分段寿命
sysctl net.inet.tcp.msl
net.inet.tcp.msl: 30000

说明:最大段寿命*是一个 [TCP] [分段]可以存在于互联网系统中的最大时间。它被任意地定义为两分钟长。最大段寿命的值用来确定 TIME_WAIT 周期(最大段寿命的两倍)
sudo sysctl -w net.inet.tcp.msl=5000

说明:设置tcp失败后回收时间,由默认的30s修改为5秒,一般方便性能测试。

5.设置ulimit
ulimit -n 
说明:命令显示当前shell能打开的最大文件数,默认值:256,该值总是小于kern.maxfilesperproc的值,因为一个shell就是一个进程。
ulimit -n 1048576

6.确保重启后参数生效(永久设置)
6.1按以上的方式设置参数有个问题,当系统重启后,这些参数又恢复成了默认值,解决办法就是把参数写到/etc/sysctl.conf文件中,但是,默认macos这个文件是不存在的,所以首先就要创建它:
sudo touch /etc/sysctl.conf
然后把参数写到文件里
kern.maxfiles=1048600
kern.maxfilesperproc=1048576
kern.ipc.somaxconn=4096
net.inet.tcp.msl=5000
net.inet.ip.portrange.first=32768
#net.inet.ip.portrange.last=65535

至于ulimit-n的值,可以把ulimit-n1048576 写到.bashrc中实现自动修改。

查看批量查看结果

ulimit -n 

sysctl kern.maxfiles kern.maxfilesperproc kern.ipc.somaxconn net.inet.tcp.msl net.inet.ip.portrange.first net.inet.ip.portrange.last

最后修改参数总结:(临时设置)
sudo sysctl -w kern.maxfiles=1048600
sudo sysctl -w kern.maxfilesperproc=1048576
sudo sysctl -w kern.ipc.somaxconn=4096
sudo sysctl -w net.inet.tcp.msl=5000
sudo  sysctl -w net.inet.ip.portrange.first=32768
ulimit -n 1048576

Common Error Codes for Active Directory Authentication

vScope supports both Discovery of and integration with the Active Directory. If something goes wrong you will be prompted with an error message that can give you a hint of the cause to the issue.

The error messages might look something like this:

INVALID_CREDENTIALS: 80090308: LdapErr: DSID-0C09042F, comment: AcceptSecurityContext error, data 52e, v2580 

INVALID_CREDENTIALS: 80090308: LdapErr: DSID-0C090400, comment: AcceptSecurityContext error, data 775, v1db1 

Here is a list of common error codes that might show up:

Error codeErrorDescription
525User not foundReturned when an invalid username is supplied.
52eInvalid credentialsReturned when a valid username is supplied but an invalid password/credential is supplied. If this error is received, it will prevent most other errors from being displayed.
530Not permitted to logon at this timeReturned when a valid username and password/credential are supplied during times when login is restricted.
531Not permitted to logon from this workstationReturned when a valid username and password/credential are supplied, but the user is restriced from using the workstation where the login was attempted.
532Password expiredReturned when a valid username is supplied, and the supplied password is valid but expired.
533Account disabledReturned when a valid username and password/credential are supplied but the account has been disabled.
701Account expiredReturned when a valid username and password/credential are supplied but the account has expired.
773User must reset passwordReturned when a valid username and password/credential are supplied, but the user must change their password immediately (before logging in for the first time, or after the password was reset by an administrator).
775Account locked outReturned when a valid username is supplied, but the account is locked out. Note that this error will be returned regardless of whether or not the password is invalid.

Easy Connect 网络监视进程关闭方法

由于工作中,有时需要在使用外部网络的情况下,访问公司内网,因此使用了Easy Connect这个工具,用于连接到公司的虚拟专用网中。装了之后,也只偶尔用用,大多数时间这个软件是关闭的。

但是最近处理网络异常断线时排查问题发现,EasyMonitor等3个进程一直在运行,而Easy Connect已经几个月没有使用过了,一直源源不断的上传下载数据包。不能强制退出,即使我在终端使用kill命令结束了,也会立刻重启,然后拦截网络流量.这是一个顽固的开机启动程序,类似病毒的.所以决定动手干掉.

检查plist文件

分别在以下6个目录中检查是否有与EasyConnect相关的plist文件

  1. ~/Library/Preferences/ – (当前用户设置的进程)
  2. ~/Library/LaunchAgents/ – (当前用户的守护进程)
  3. /Library/LaunchAgents/ – (管理员设置的用户进程)
  4. /Library/LaunchDaemons/ – (管理员提供的系统守护进程)
  5. /System/Library/LaunchAgents/ – (Mac操作系统提供的用户进程)
  6. /System/Library/LaunchDaemons/ – (Mac操作系统提供的系统守护进程)

删除所有com.sangfor.EasyMonitor.plist,和以com.sangfor.开头的plist

检查守护进程
launchctl list | grep Easy
可以看到有3个进程是杀不掉的会自动重启

sudo launchctl unload com.sangfor.ECAgentProxy

sudo launchctl unload /Applications/EasyConnect.app/Contents/Resources/LaunchDaemons/com.sangfor.EasyMonitor.plist

sudo launchctl unload /Applications/EasyConnect.app/Contents/Resources/LaunchAgents/com.sangfor.ECAgentProxy.plist

然后重启,网络终于正常了,一个晚上加一整天都没有断线(以前一天要断好几次地),收工.

netty http protocol

netty http协议实现的抽象,对http实现是基于非servlet的异步 IO 实现。

请求request的抽象描述,response对象的抽象比较类似

HttpMethod:主要是对method的封装,包含method序列化的操作
HttpVersion: 对version的封装,netty包含1.0和1.1的版本
QueryStringDecoder: 主要是对url进行封装,解析path和url上面的参数。(Tips:在tomcat中如果提交的post请求是application/x-www-form-urlencoded,则getParameter获取的是包含url后面和body里面所有的参数,而在netty中,获取的仅仅是url上面的参数)
HttpHeaders:包含对header的内容进行封装及操作
HttpContent:是对body进行封装,本质上就是一个ByteBuf。如果ByteBuf的长度是固定的,则请求的body过大,可能包含多个HttpContent,其中最后一个为LastHttpContent(空的HttpContent),用来说明body的结束。
HttpRequest:主要包含对Request Line和Header的组合
FullHttpRequest: 主要包含对HttpRequest和httpContent的组合

request的流程处理,只需要在netty的pipeLine中配置HttpRequestDecoder和HttpObjectAggregator。

原理

1:如果把解析这块理解是一个黑盒的话,则输入是ByteBuf,输出是FullHttpRequest。通过该对象便可获取到所有与http协议有关的信息。
2:HttpRequestDecoder先通过RequestLine和Header解析成HttpRequest对象,传入到HttpObjectAggregator。然后再通过body解析出httpContent对象,传入到HttpObjectAggregator。当HttpObjectAggregator发现是LastHttpContent,则代表http协议解析完成,封装FullHttpRequest。
3:对于body内容的读取涉及到Content-Length和trunked两种方式。两种方式只是在解析协议时处理的不一致,最终输出是一致的。

性能优化:
1:使用堆外内存,也就是DirectBuffer。来减少GC的次数。
2:使用buffer pool,避免频繁的申请及释放内存。一般pool有两层,ThreadLocal的pool和全局的pool。 申请buffer空间时,先看ThreadLocal是否有未使用的buffer,如果没有,再从全局的pool中获取buffer。一般的内存管理策略是pool里面的buffer大小全部一致(比如1k),但是 如果需要申请2k的空间,必须要新建2k空间的buffer。如果频繁申请大于1K空间内存,则性能比较低下。 netty为了解决该问题,使用了较为复杂的内存管理策略,具体可参考 http://blog.csdn.net/youaremoon/article/details/47910971 
3:零拷贝:前面提到拷贝数据的性能问题,采用零拷贝机制可有效解决该问题
CompositeByteBuf(组合): 比如读取request Line,申请1k的空间ByteBuf,如果没有发现边界(CRLF)。再申请1k的空间ByteBuf到JDK的io中读取数据。将老的ByteBuf和新申请的ByteBuf组合成CompositeByteBuf,更改CompositeByteBuf的读写指针来避免数据的拷贝。
slice(切分):  比如在1k的ByteBuf里面先读取requestLine,Header进行解析对象,最后读取body。由于body的数据还需要保存在内存里面供业务使用。一般的做法是新申请一块空间,将body的数据拷贝到新申请的空间上。这里通过虚拟一个ByteBuf,然后将读写的指针指向真实的ByteBuf的body区域上面,来避免数据的拷贝。

压缩实现,在HttpResponseEncoder之前加上 HttpContentCompressor 。response对象先进过HttpContentCompressor 压缩后,再经过HttpResponseEncoder进行序列化。
1:压缩主要是针对body进行压缩。http1.1不支持对header的压缩。
2:压缩后body的输出是trunked,而不是Content-length的形式。
Gzip格式,gzip压缩后主要包含三部分:
gzip头:主要存储的是gzip的压缩方式
deflate编码:内容采用的是deflate压缩算法
gzip尾:主要是采用CRC32算法对编码内容进行校验。

安全配置

参数推荐返回错误码描述
requst Line size2k414主要是限制url的长度
header size4k414避免header过长
body size60M413此处一般和业务关联,一般设置相对较大
keepalive timeout75 如果连接在设定时间内没有使用,则关闭掉连接,避免维护的连接过多

http协议解析

http协议主要使用CRLF进行分割。

标示ASCII描述字符
CR 13Carriage return (回车) \n
LF 10Line feed character(换行)\r
SP 32Horizontal space(空格) 
COLON 58COLON(冒号):

请求包,主要包含三部分:请求行(line),请求头(header),请求正文(body) 

请求行(Line):主要包含三部分:Method ,URI ,协议/版本。 各部分之间使用空格(SP)分割。整个请求头使用CRLF分割。(比如:POST /1.0.0/_health_check HTTP/1.1 CRLF)

请求头(Header): 格式为(name :value),用于客户端请求的描述信息。header之间以CRLF进行分割。最后一个header会多加一个CRLF。( 比如:Connection: keep-alive CRLF CRLF)

请求正文(body) :里面主要是Post提交的数据(可支持多种格式,格式在Content-Type定义,长度是在Content-Length里面定义)。

响应包,主要包含三部分:状态行(line),响应头(header),响应正文(body)

状态行(line):包含三部分:http版本,服务器返回状态码,描述信息。以CRLF进行分割。 ( 比如:HTTP/1.1 200 OK CRLF)

响应头(header) : 格式为(name :value),用于服务器返回的描述信息。header之间以CRLF进行分割。最后一个header会多加一个CRLF (比如:Content-Type: text/html CRLF Content-Encoding:gzip CRLF CRLF) 

响应正文(body):里面主要是返回数据(可支持多种格式,格式在Content-Type定义,长度是在Content-Length里面定义)。

truncked协议

1:主要包含三部分:chunk,last-chunk和trailer。如果分多次发送,则chunk有多份。
2:chunk主要包含大小和数据,大小表示这个这个trunck包的大小,使用16进制标示。其中trunk之间的分隔符为CRLF。
3:通过last-chunk来标识chunk发送完成。 一般读取到last-chunk(内容为0)的时候,代表chunk发送完成。
4:trailer 表示增加header等额外信息,一般情况下header是空。通过CRLF来标识整个chunked数据发送完成。

HTTP协议通常使用Content-Length来标识body的长度,在服务器端,需要先申请对应长度的buffer,然后再赋值。接收数据时,发现header中有Content-Length属性,则读取Content-Length 的值,确定需要读取body的长度。

如果需要一边生产数据一边发送数据,就需要使用”Transfer-Encoding: chunked” 来代替Content-Length,也就是对数据进行分块传输。按照truncked协议分批读取数据。

压缩类型

1:压缩需要客户端,服务器端同时支持。在chrome中,请求默认会加上Accept-Encoding: gzip, deflate,客户端默认开启数据压缩。而tomcat默认关闭压缩,如果开启需要增加配置。
2:在请求时,需要通过header的Accept-Encoding: gzip, deflate 来告诉服务器客户端支持的压缩类型。
3:在返回时,http server会在返回的header中添加Content-Encoding: gzip 来告诉客户端数据的压缩方式。
4:压缩类型主要包含如下几种:
         gzip      说明body采用GNU zip编码
         compress  说明body采用Unix的文件压缩程序
         deflate  说明body是用zlib的格式压缩的
         identity  说明没有对实体进行编码。
其中 gzip, compress, 以及deflate编码都是无损压缩算法,不会导致信息损失。 gzip效率最高,使用较为广泛。

http解决粘包拆包

1:请求行的边界是CRLF,如果读取到CRLF,则意味着请求行的信息已经读取完成。
2:Header的边界是CRLF,如果连续读取两个CRLF,则意味着header的信息读取完成。
3:body的长度是有Content-Length 来进行确定。如果没有Content-Length ,则是chunked协议(具体参考前面的trunked协议)。

技术人的职业发展智慧

我先分享职业生涯中的两个教训,希望对年轻程序员有启发。第一,不要只做 Code Review 和 Design Document,要知道它背后本质的要求,要开发新的算法,把本质的和革新的东西写下来,让别人能够反复看,这一点我工作两年后才慢慢意识到,希望刚入行的程序员早一点明白。另一个,在学校期间,你可能会看很多 Paper,里面有非常多炫酷的算法,但是在工业界真正能实现产品的算法是一种 trade-off,也就是说工程技术是折中的。

了解自己的职业导向

职业发展过程中,我们要做的第一件事情就是了解自己,如果你工作了足够长的时间,你会发展出一个比较固定的导向,这些职业导向并不存在价值判断,每种导向上都有优秀的人。

  • 第一种人最在乎某件事能否给自己带来最好的回报,带来更大的影响力、更好的资源,这个叫做进取心;
  • 另一种人追求的是一个可以预测、比较稳定的工作模式,比如很多公司时间长了以后,会形成一个稳定的中间层,这类人更在乎工作价值是否被领导、环境认可;
  • 还有一种人,在程序员里面会比较多,他们喜欢某件事情带来的挑战,更在乎牛人的评价,或者是在开原社区里面的影响力;
  • 还有一些平衡型的人,不仅关注工作、业绩,也在乎 work-life balance,要求工作和生活、人际关系、个人成长取得平衡。
  • 最后一种,90 后程序员比较多,追求个性、独立,但是一件大的项目通常需要团队配合,追求独立的人会比较吃亏。

了解技术人的职业发展阶段

技术人的职业发展阶段,大概可以分为三个阶段。

  • 技术新人。这一阶段要赢得领导和同事的信任,获得授权。比如你的职位是软件工程师、UI Design,这是很明确的授权,还有一些不是很明确的授权,比如某些事情你做的非常好,然后默认这件事情就是你的了。
  • 垂直领域专家。工作 2 到 3 年内获得独立贡献者的动手能力,这个阶段要做垂直领域的专家,在领域内获得扎实的技术能力。如果公司把你提升为小组长,你要能够指导下属,帮助他们做决策和判断。
  • 技术领导者。这个阶段需要通过他人获得成绩,你会领导别人,或者影响别人,甚至你没有正式 title,但大家也愿意跟着你做。这个阶段要关注他人,愿意花自己的时间帮助别人,要获取资源、调配资源,以及使组织走对方向。

发现自己的 0,1,无穷大

我提出一种模型,跟每一位一线工程师都有关系,我们最近也经常拿这个模型去考虑产品,怎么定位,怎么取舍需要开发的功能,我把它叫做 0,1,无穷大模型。

先说一下 0 和 1。假设我们现在需要一个开发,高性能计算平台工程师,这个岗位需要什么基本能力?需要会这门语言,懂并行计算,知道怎么并行调度,或者把一个算法部署到多核上面去,这些必须能做到的叫做 1,在 1 这个事情上不允许你有 0,这就是你面试过程中为什么会失败?因为你出现了一个 0,对不起,你进不来。

下面说一下 1 和无穷大。1 的地方你投入再多都是饱和的,要找到无穷大。比如我们有些员工沟通能力、逻辑能力很强,但是动手写代码能力不行,他在项目起步阶段,说服上下游方面很厉害,但一旦项目立项,这方面就是 1 了,就该停止了,所以一个人最重要是发现无穷大的部分。假设我是一个做系统优化的工程师,我要看到计算的核心部分,通常是一个循环,大概四到五层循环,里面可能还会有条件跳转、向量处理,那如果你在这方面能力做到无穷大,能更快确定计算的最核心部分、最耗时的部分,而且很快确定仿存的模式对计算带来什么阻碍,这就是在无穷大的事情。所谓无穷大对一项产品或者技术来说,就是投入的越多得到的越多,你要看自己无穷大的能力跟公司的要求是否匹配,假设你的无穷大对公司来讲是 1,那么你就使错了地方。

抉择时刻 — 工作中的价值观

年轻的时候,第一二份工作对一个人价值观的塑造非常重要,但当你慢慢成长起来,到三四十岁的时候,面临工作中的问题、人际关系中的问题、生活中的问题,你需要有自己的价值观,这就需要你平时多花时间去思考,尤其是在互联网企业,在一个开放的市场里面,每天都能看到对你和你的企业的不同评价,如果没有自己的价值观,你的精神会奔溃。

之前有个同学问过我价值观的问题,我给过一个答案,不完美但值得你去思考,我当时的回答是任何一份工作都不值得赔上你的三观,前提是你的三观是坚定的并且你有能力做出选择。

职业发展中隐含的变量:信任的速度

比较大的组织经常讲战略、执行,讲资源,好像战略乘以执行,资源再足够就能得到好的结果,但是这里还有一个参数是信任。一个工作为什么给 A 不给 B?为什么跟 C 合作不跟 D 合作?这些归结为一个字就是 Trust,我信任你,我愿意交给你,我想得到这份授权,我就需要获得这份信任。

一个人能否被信任由什么决定呢?我们通常认为信任是一个人的品质问题,但实际上信任涉及四个维度,一个是所谓的品质,比如正直诚实;第二个是动机,如果在有竞争性的部门里面工作,把你的动机说清楚很重要,这是合作的前提;第三个是之前项目的业绩和结果;第四个是能力。

我记得很早以前,有个手下问“在比较弱的时候,是不是可以做一些模糊的打擦边球的事情?”他可能忘了一件事情,信任包括他过往的业绩,所以说江湖大佬会说出来混总要还的,Trust 很难建立,但是破坏起来却很容易,很多企业真正的天花板在哪呢?就在 Trust,行业给它的信任、社会给它的信任已经到顶了。如果你做过坏事,你没有去更改,你还去狡辩,就很难获得信任,就跟日本一样,日本跟中国的关系、跟韩国的关系、跟东南亚被他侵略过的国家的关系,有个上限,因为日本从来没有道过歉。

拥抱变化,成为更好的自己

技术人要知道,你的年龄是会增长的,如果只是年龄变化,其他不变,你就会被替换掉。当年我决定离开摩托罗拉去因特尔的时候,我问自己一个问题:我此时的这个位置,继续用我或者招几个新人培训一下,哪个更合适?当我觉得后者更合适的时候,我就开始想做出点变化了,后面几年经历都是如此。谨记最舒服的时候就是最危险的时候。

我在面试过程中经常会遇到一种人,从简历上看他有非常辉煌的过去,但是在最近一个职位上他做了很长时间,比如三四年没有变化,或者他不曾去想换一种创新的方式去做事,像这样的人,在快速发展的企业里面就没有位置了。

当然,你在有红利的企业可以这样做,那是另一种事情了,比如石油、石化、电力等资源型的企业,再比如因特尔在处理器行业里面,已经做了 50 年了,这个行当已经到了指数增长的最上方,它可以吃技术红利,但是在高速发展、四面开花的高新技术企业里,是没有这种好地方的,只要有一个地方足够舒服,就说明有问题。

蔡京家的葱丝小姐 – 你的机缘与共业

从一个简单的故事讲起,故事的名字叫“蔡京家的葱丝小姐”。南宋有个赋闲在家的员外很有钱,他想去体验一下达官贵人家里是什么样的,正好蔡京宰相家败落了,外员就从宰相家高薪聘请了一位做菜的丫鬟,觉得宰相家的厨子能力一定很强,结果这个丫鬟根本就做不了菜,因为宰相家的佣人分工非常细,这个丫鬟的纤纤玉指是拿来剥葱的。

这能总结出什么?结论很简单,在一个企业里面有两类人,一类是 1×N,一类是 N×1,第一类人,第一年进入公司,作为新人,适应工作,之后以这个方式和能力做了 N 年,另一种人,不断挑战变化,N 年有 N 种做事的能力和方式,以及负责不同的项目。

这类似佛法讲得“因缘”,“因”是指你是颗什么样的种子,你很聪明,读书的时候有很好的科研训练,这是“因”,但是如果你去了一个很稳定的、没有挑战的工作岗位,就像种子放在水泥地上是长不出什么果子的。

人无远虑,必有近忧:时代发展的外部性

人无远虑,必有近忧,我们要看到时代发展的外部性。这里的外部性,一方面是指那些舒服的、资源型的、有路径可以依赖的单位越来越少,好日子或者说免费的午餐已经不在了,如果你现在在一个舒服的位子上,你最好趁这个阶段好好突围学习,不然将来你就没有能力适应变化了;

另一方面,时代的外部性还有整个经济的增速变缓,Job Market 对技术人员的技术复杂性要求在变化。从称呼的变化上也能看出这种变化,二十年前我们管自己叫“软件工程师”,现在大多数技术从业者管自己叫“码农”、“程序员”,这种称呼的变化意味着技术的复杂性使得分工越来越细,细到对一个技术人来说,如果你不去主动向下扎,你很容易被替代,这就是码农,不行换一个就完了。所以你不仅要会编程,你还要去了解底层,这能让你有一定的反脆弱性能力,对长期发展是有利的,这就要求技术人要回归技术本质,去经典教科书里再回炉,了解整个系统是怎么回事,还有就是找到自己的护城河,就是不管你换什么样的组或单位,你在某个方向的投入是积分的,是可以不断积累的。

bash-step-to-step

bash 跟着敲

前言:本文不单单是介绍常用的命令,还融入了bash语法,每个知识点都有浅显的例子配合,让你很容易消化并吸收,最后还有一个boss任务等你挑战,耐心学完的同学肯定会有很大的收获的。

查看原文:https://github.com/cookieY/bash-step-to-step#export

月薪3K与3W网络工程师差距

  1. 很幽默。
  2. 培训体系给我们灌输了很多内容,比如STP/VTP/RIP/OSPF/BGP/MPLS/路由重分发等,但实际项目中用得最多的往往是VLAN,很多项目OSPF都不需要,直接一条静态路由搞定。
  3. 在这个基础网络同质化的年代,卖交换机不得不谈点高大上的方案与技术,什么敏捷交付/策略随行/业务编排、什么SDN/VXLAN,至于用不用另说。(试问买顶配车型的土豪们,所有功能你们都用到了吗?)

如果你工作了好几年,还是只会划个VLAN,配条静态路由,插上console干完通了就走,又能怨谁 月薪3K呢?这种工作,找个高中生培训2个星期CCNA,他也会干,你的核心竞争力在哪儿?

网络上经常有人讨论 网络工程师与程序员,其实也不得不承认,网工平均待遇比程序员低,因为存在太多低级 “操作工” 。但高级网络工程师待遇真不一定比同等资历的程序员低,何为高级? 反正高级网工基本不敲命令行了,高级程序员也不写代码了。

当然,初入社会的小菜鸟,谁都难避免 “操作工”阶段,但你要学习,要成长呀,人往高处走,水往低处流!

谈谈高薪 网络工程师能力

1. 整体架构能力

经过几十年发展,网络早已经不只有交换机、路由器,安全也不仅仅有最初的防火墙、入侵检测。平时我们谈网络其实早已 泛网络化,包含:路由交换、安全、无线、网络优化、存储、数据中心、容灾备份、云计算等等。整体架构能力就是熟悉这些产品、技术和方案,能够结合客户场景/业务 合理规划设计,能做超大项目整体架构设计的人,其实是超越了售前/售后的另一类人。

2. 技术细节

很多人的学习是停留在表面的,包括我,就像年轻医生,一般问题都能解决,但遇到十年一遇的疑难杂症,还是得要老专家出马。听完培训课程,敲敲实验,大部分项目做起来没压力,但要想成为车小胖那样的“老专家”,有时间还是老老实实看 TCP/IP卷一、路由卷一卷二、RFC文档。

3. 排错思路

原厂售后是很稀缺的资源,主要在做 大项目售前测试和高等级故障处理。大部分项目售后实施人员都是 外包公司或集成商,一个优秀网络工程师不在于你命令敲定多666,也不在你能记住多少厂商的命令行,这都是在配置手册里面复制粘贴的事情。重点是排错、解决问题的思路。

4. 经验

刚考完CCIE的应届毕业生,技术原理/命令非常6,但待遇还差得远。

分享一个大家常见售前坑售后的故事:某项目实施阶段,交换机不支持某招标要求的功能,售后小菜鸟直接跟甲方负责人说,这个功能我们不支持,甲方非常生气,后果很严重。 如果是售后老鸟肯定不会直接跟甲方“坦白”。问题出了,现在关键是解决问题,把情况同步相关售前、销售,让他们想办法解决: 是找替代解决方案,是找研发开发新版本,还是去做客户商务关系。这本不是售后的责任,如果你不说或者直接跟客户说,这就是售后的问题了。

时间沉淀

寻找正确的“甩锅”方式,不甩烫死你,甩错了烫死别人,你也得负责。这就是经验。

5. 其他软实力

软实力非常多,诸如:沟通交流、言谈举止、演讲呈现、坚韧不拔、勤奋好学等等,你人帅人美也算,每个人的软实力是不一样的,发现它,发挥到极致,记住“弥补缺点永远比发扬优点困难”。

6. 机遇与平台

一次好的机遇,一个好的平台,能让你少奋斗十年。

人生几十年,你总不会一直点背,机遇总留给有准备的人。

当然,几乎没人能具备所有能力,也不是每个人运气都很好,但只要不断学习,提升自己,明天就会有希望,祝好!

来源:https://blog.51cto.com/weilan2222/1978735

wordpress 自动升级失败地解决方案

因多种原因导致下载失败,超时等多种问题,使用手动麻烦,容易出错

现在找出一种方案,实现半自动方式:

手动下载,需要的版本文件
https://downloads.wordpress.org/release/zh_CN/wordpress-xxx.zip
一定要选择 zip 地,要不可能解压不了

把下载地文件放到 wp-content/temp 文件夹下,并设置服务用户(如 www)可写

并更改后缀为 tmp,获取更改后文件绝对路径;/…../wp-content/temp/wordpress-xxx.tmp

修改文件:vim wp-admin/update-core.php 大约在559行,下载前位置($update = find_core_update( $version, $locale ); 后面),添加下面代码,保存;
$update->packages->full=$update->download=”$webroot/wp-content/temp/wordpress-xxx.tmp”;

从版本5.4以后更新使用了不同的包,所以下载内容变为

https://downloads.wordpress.org/release/wordpress-5.5.3-no-content.zip
同样放到一个可写的目录如:wp-content/temp/wordpress-5.5.3-no-content.zip
在find_core_update下面添加代码:$update->packages->no_content=”$webroot/wp-content/temp/wordpress-5.5.3-no-content.zip”;

修改文件(5.5以后文件变化了):vim wp-admin/update-core.php 大约在675行

5.6在799行

在界面上点自动更新,OK enjoy.

初级、中级和高级开发人员之间的差异 The Differences Between a Junior, Mid-Level, and Senior Developer

作者:Daan
翻译:Sambodhi

有一句话在开发人员圈子广为流传:“初级开发人员的标志就是需要在中级和高级开发人员的指导下完成工作。”所以,我们不要苛刻初级开发人员写的代码对错严谨,毕竟每个开发人员都有这样的一个过程。据译者观察,初级开发人员往往认为自己写的代码都是高质量的;而中级开发人员认识到了软件知识会影响代码质量;中高级开发人员则认识到硬件知识会影响到代码质量;高级开发人员反而认为自己写的部分代码质量不够高,需要恶补硬件知识和编译器等相关知识……至于骨灰级开发人员,可以写出高质量代码,并能够充分考虑各种情况。本文,后端开发者 Daan 讲述了初级、中级和高级开发人员之间的区别。

作为初级、中级或高级开发人员,并不仅仅跟多少年的编程经验相关。初级开发人员甚至可以比高级开发人员年龄要大。这一切都取决于技能。不过,这并不意味着高级开发人员必须是所有方面的专家,但可以肯定地说,高级开发人员要比初级、中级开发人员熟练得多。

既然高级开发人员与初级、中级开发人员的区别不仅在于编程技能,那么到底有什么区别呢?

知识

很显然,高级开发人员比初级、中级开发人员掌握更多的知识。了解设计模式、架构、自动化测试、性能、安全性等,这是初级开发人员缩小与中级、高级开发人员之间的知识差距的好方法。

了解软件开发中的事情应该如何进行是很重要的。但是,仅仅知道这些知识并不能让你成为高级开发人员。知识并不是开发人员之间最大的区别,它只是其中的一个因素。

编程

不管大多数人是怎么想的,编程并不是与计算机进行通信。编程是关于与人类交流指导的计算机。最终,代码被编译并转为一连串的 0 和 1。

代码必须对将来使用它的其他开发人员有意义。一个以前从未见过代码的新团队应该能够阅读代码并开始研究新功能或进行 bug 修复。这就是初级和高级开发人员之间的巨大差异所在。

在这一比较中,我将把中级开发人员排除在外,因为,在编程技能方面,中级开发人员处于一个灰色地带。显然,中级开发人员介于初级和高级开发人员之间,他们可能更倾向于高级开发人员。这主要与经验有关,因为中级开发人员可能至少经历过一次整个开发周期。他们犯过许多最简单的错误,并从中汲取了教训。

如何识别初级开发人员?

初级开发人员缺乏经验。有些人刚刚毕业,正在开始他们的第一份全职工作。初级开发人员的心态通常是能够让代码工作就万事大吉了。工作软件和良好的软件对他们来说,没什么不同。

编写简单的代码其实是一件很困难的事情,这恰恰就是初级开发人员不擅长的事情。初级开发人员倾向编写花哨的代码。你可以通过古怪的代码、过于复杂的抽象来识别出初级开发人员。这就是初级开发人员炫耀的方式,好让其他开发人员知道他们编写的代码有多牛逼。其实,他们这一做法是错误的。

初级开发人员只专注于代码的计算机方面,而忽视了人性方面。

那么,高级开发人员呢?

当你查看高级开发人员编写的代码时,你心里可能会嘀咕:就这些吗?代码的其余部分在哪里呢?高级开发人员编写的代码,看上去简洁、直接了当,甚至还可能让人觉得很愚蠢。而这正是开发人员在编程时可以拥有的最大品质之一。高级开发人员遵循的是 KISS 原则:保持简洁,愚蠢。

译注:KISS 是英文 “Keep it Simple and Stupid” 的首字母缩写,意思是 “保持简单和愚蠢”,其中 “愚蠢” 不是 “傻”,它还有 “迟钝”、“不敏感”、“乏味”、“无价值” 等综合含义。另一种对 KISS 的解释是:“Keep It Simple,Stupid” 由于中间加了逗号,意思就变成了 “保持简单,傻瓜”,是一种祈使语态,带有调侃的色彩。

好的目标不是越复杂越好,反而是越简洁越好。这就是 KISS 原则。符合 KISS 原则的目标都是关键的,而非包罗万象;目标必须确定优先顺序,而关键的目标则是资源和努力的重心。总结开发人员在编程过程中的经验,大多数应用程序的设计应保持简洁和单纯,而不掺入非必要的复杂性,这样的应用运作成效会取得最优;因此简单性应该是软件开发中的关键目标,尽量避免不必要的复杂性。

高级开发人员以不同于初级开发人员的方式思考他们的代码。高级开发人员编写的代码会考虑到可维护性和可扩展性。这与初级开发人员的思维方式完全不同:高级开发人员考虑的是必须与代码打交道的人,而初级开发人员只是考虑如何让代码在计算机上工作。

不仅仅是编程技能的问题

除了编程技能外,还有一些其他因素可以告诉你谁是哪种类型的开发人员。

一般来说,初级开发人员执行最简单的任务或影响较小的任务。他们不参与任何架构设计。中级开发人员也不负责设计解决方案,他们只是执行任务。与初级开发人员的不同之处在于,只要分配给他们相对常规的任务,他们就会执行这些任务,而无需过多的监督。而高级开发人员则完全可以自己开发应用程序。

但这并不意味着高级开发人员在开发过程中没有任何问题。实际上,每个开发人员每天都会遇到很多问题,这点是不会改变的。对于高级开发人员来说,也是概莫能外。

区别在于高级开发人员知道如何提出正确的问题,以及如何处理这些问题。当涉及到相对常规的任务时,中级开发人员可以提出正确的问题,但在更复杂的任务上就力有未逮,需要高级开发人员的帮助了。

高级开发人员永远不会迷失方向,他们知道如何以正确的行动来跟进问题。但是,这并不意味着高级开发人员不能向其他开发人员寻求帮助。有时,最好的方法就是向其他在该领域有经验的开发人员寻求帮助。

中级开发人员也应该能够提出正确的问题,只要他没有被分配到需要深入知识层面的高度复杂的任务。

你不应该指望初级开发人员能够立即提出正确的问题。由于初级开发人员缺乏经验,他们需要更有经验的开发人员的指导。初级开发人员需要获得必要的资源,或向正确的方向大力推进。

晋升下一个层次

作为开发人员,我们都想提高自己,变得更好。但是,你可以采取哪些步骤来晋升到下一个层次呢?

从初级开发人员到中级开发人员

由于初级开发人员缺乏经验,因此至少要经历几次整个开发周期,这一点是很重要的。这样一来,你会掉进很多陷阱中,并学会如何在下一次避免它们。

说到编程,你应该学习如何编写简单的代码。想想下一个将要处理这段代码的人。你还应该学习如何调试,因为这将使你能够更好地理解这一过程中发生的事情。

此外,你应该熟悉最佳实践,并了解架构、性能、安全性等。缩小到达中级水平所需的知识差距。

从中级开发人员到高级开发人员

从中级开发人员到高级开发人员可能会相当困难。有些开发人员在整个职业生涯中,始终处于中级水平。

高级开发人员知道什么可以走捷径,什么永远不可以走捷径。这些都是从过去所犯的错误中汲取的教训。

如果你想要成为高级开发人员,就必须准备好接受没有人知道如何解决的任务。但你应该知道的不仅仅是如何完成工作。

作为高级开发人员,你的工作还包括帮助经验较少的开发人员。当他们不知道如何做某些事情时,你就是他们的后备力量。

高级开发人员掌握了他们的技术栈,这可能不会让你感到惊讶。记着,成为高级开发人员可不仅仅是编程技能,还要了解你所在公司中使用的所有工具和应用程序。

结论

初级、中级和高级开发人员之间的区别并不全在于有多少年的经验。当然,可以肯定地说,高级开发人员比初级、中级开发人员更熟练,但知识并不是最重要的因素。

与初级开发人员相比,高级开发人员编写的代码更简单,并且思维模式也不同。但这不仅仅是编程技能。知道要问什么问题,以及如何跟进这些问题是至关重要的。只有那些拥有丰富经验的高级开发人员,才知道在如何在任何情况下做到这一点。

作为一个初级开发人员,你应该专注于如何编写简单的代码,并经历多个开发周期。要从中级开发人员晋升到高级开发人员,你应该专注于学习,而不仅仅是解决常规任务。你应该愿意承担最艰巨的任务,成为技术栈的大师。高级开发人员的另一个职责是为经验不足的开发人员提供后援力量。

我将引用 Martin Fowler 的一句话作为结束语:“任何傻瓜都可以写出计算机能理解的代码,而优秀程序员可以写出人能读懂的代码。”(Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

作者介绍:

Daan,荷兰的后端开发者,密码学爱好者。

原文链接:

The Differences Between a Junior, Mid-Level, and Senior Developer

PHP正则表达式preg_match_all修饰符

模式修饰符

下面列出了当前可用的 PCRE 修饰符。括号中提到的名字是 PCRE 内部这些修饰符的名称。 模式修饰符中的空格,换行符会被忽略,其他字符会导致错误。i (PCRE_CASELESS)如果设置了这个修饰符,模式中的字母会进行大小写不敏感匹配。

m (PCRE_MULTILINE)默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行), “行首”元字符 (^) 仅匹配字符串的开始位置, 而”行末”元字符 ($) 仅匹配字符串末尾, 或者最后的换行符(除非设置了 D 修饰符)。这个行为和 perl 相同。 当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后,另外, 还分别匹配目标字符串的最开始和最末尾位置。这等同于 perl 的 /m 修饰符。如果目标字符串 中没有 “\n” 字符,或者模式中没有出现 ^ 或 $,设置这个修饰符不产生任何影响。

s (PCRE_DOTALL)如果设置了这个修饰符,模式中的点号元字符匹配所有字符,包含换行符。如果没有这个 修饰符,点号不匹配换行符。这个修饰符等同于 perl 中的/s修饰符。 一个取反字符类比如 [^a] 总是匹配换行符,而不依赖于这个修饰符的设置。

x (PCRE_EXTENDED)如果设置了这个修饰符,模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略。 这个修饰符 等同于 perl 中的 /x 修饰符,使被编译模式中可以包含注释。 注意:这仅用于数据字符。 空白字符 还是不能在模式的特殊字符序列中出现,比如序列 (?( 引入了一个条件子组(译注: 这种语法定义的 特殊字符序列中如果出现空白字符会导致编译错误。 比如(?(就会导致错误)。e (PREG_REPLACE_EVAL)

Warning
This feature was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0.如果设置了这个被弃用的修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线(\)和 NULL 字符在 后向引用替换时会被用反斜线转义.

This feature was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0.如果设置了这个被弃用的修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线(\)和 NULL 字符在 后向引用替换时会被用反斜线转义.

Note:

仅 preg_replace() 使用此修饰符,其他 PCRE 函数忽略此修饰符。

A (PCRE_ANCHORED)如果设置了这个修饰符,模式被强制为”锚定”模式,也就是说约束匹配使其仅从 目标字符串的开始位置搜索。这个效果同样可以使用适当的模式构造出来,并且 这也是 perl 种实现这种模式的唯一途径。

D (PCRE_DOLLAR_ENDONLY)如果这个修饰符被设置,模式中的元字符美元符号仅仅匹配目标字符串的末尾。如果这个修饰符 没有设置,当字符串以一个换行符结尾时, 美元符号还会匹配该换行符(但不会匹配之前的任何换行符)。 如果设置了修饰符m,这个修饰符被忽略. 在 perl 中没有与此修饰符等同的修饰符。S当一个模式需要多次使用的时候,为了得到匹配速度的提升,值得花费一些时间 对其进行一些额外的分析。如果设置了这个修饰符,这个额外的分析就会执行。当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符)。

U (PCRE_UNGREEDY)这个修饰符逆转了量词的”贪婪”模式。 使量词默认为非贪婪的,通过量词后紧跟? 的方式可以使其成为贪婪的。这和 perl 是不兼容的。 它同样可以使用 模式内修饰符设置 (?U)进行设置, 或者在量词后以问号标记其非贪婪(比如.*?)。

Note:

在非贪婪模式,通常不能匹配超过 pcre.backtrack_limit 的字符。

X (PCRE_EXTRA)这个修饰符打开了 PCRE 与 perl 不兼容的附件功能。模式中的任意反斜线后就 ingen 一个 没有特殊含义的字符都会导致一个错误,以此保留这些字符以保证向后兼容性。 默认情况下,在 perl 中,反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文。 当前没有其他特性由这个修饰符控制。

J (PCRE_INFO_JCHANGED)内部选项设置(?J)修改本地的PCRE_DUPNAMES选项。允许子组重名, (译注:只能通过内部选项设置,外部的 /J 设置会产生错误。)

u (PCRE_UTF8)此修正符打开一个与 perl 不兼容的附加功能。 模式和目标字符串都被认为是 utf-8 的。 无效的目标字符串会导致 preg_* 函数什么都匹配不到; 无效的模式字符串会导致 E_WARNING 级别的错误。 PHP 5.3.4 后,5字节和6字节的 UTF-8 字符序列被考虑为无效(resp. PCRE 7.3 2007-08-28)。 以前就被认为是无效的 UTF-8。

 (PCRE_INFO_JCHANGED)内部选项设置(?J)修改本地的PCRE_DUPNAMES选项。允许子组重名, (译注:只能通过内部选项设置,外部的 /J 设置会产生错误。)u (PCRE_UTF8)此修正符打开一个与 perl 不兼容的附加功能。 模式和目标字符串都被认为是 utf-8 的。 无效的目标字符串会导致 preg_* 函数什么都匹配不到; 无效的模式字符串会导致 E_WARNING 级别的错误。 PHP 5.3.4 后,5字节和6字节的 UTF-8 字符序列被考虑为无效(resp. PCRE 7.3 2007-08-28)。 以前就被认为是无效的 UTF-8。

u (PCRE_UTF8)此修正符打开一个与 perl 不兼容的附加功能。 模式和目标字符串都被认为是 utf-8 的。 无效的目标字符串会导致 preg_* 函数什么都匹配不到; 无效的模式字符串会导致 E_WARNING 级别的错误。 PHP 5.3.4 后,5字节和6字节的 UTF-8 字符序列被考虑为无效(resp. PCRE 7.3 2007-08-28)。 以前就被认为是无效的 UTF-8。

修饰符意义

/ regexp / i
不区分大小写的匹配

/ regexp / s
使句点( . )匹配任何字符,包括换行符( )

/ regexp / x
从模式中删除空白符和注释

/ regexp / m
使 ^ 匹配换行符 ( )之后的内容,美元符号($)匹配换行符 ( )之前的内容

/ regexp / e
如果替换字符串是PHP代码,使用eval()执行该代码来得到实际的替换字符串。

PHP的Perl兼容正则表达式函数也支持在Perl中不支持的其他修饰符

/ regexp / U
颠倒子模式的贪婪性; * 和 + 尽可能少地匹配而不是尽可能多。

/ regexp / u
把模式字符串当作UTF – 8编码对待

/ regexp / X
如果一个反斜杠之后跟着没有特殊意义的字符,将产生一个错误

/ regexp / A
把锚定位在字符串的开头就像模式中有 ^ 一样

/ regexp / D
使 $字符仅匹配一行的末尾

/ regexp / S
使表达式解析器更加小心地检查模式的结构,使得第二次运行时(如在一个循环中)加快速度

STUN协议,探测NAT类型

1, STUN客户端(101:10)向STUN服务器(404:40)发送请求,要求得到自身经NAT映射后的地址(202:20):
      a,收不到服务器回复,则认为UDP被防火墙阻断,不能通信,网络类型:Blocked.
      b,收到服务器回复(地址要嘛是映射地址要嘛就是源地址),对比本地地址,如果相同(直接返回的就是源地址101:10),则认为无NAT设备(没经过NAT映射转换),进入第2步,否则认为有NAT设备,进入3步.
   2,(已确认无NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT(505:50)向客户端回复包:
      a,收不到服务器从其他IP地址的回复,认为包被前置防火墙阻断,网络类型:Symmetric UDPFirewall.(如果没有NAT的话是无论如何都能收到回复的,只有一点受到防火墙的阻断,有时候杀毒软件也阻断)

b,收到则认为客户端处在一个开放的网络上,网络类型:Opened.

   3,(已确认存在NAT设备)STUN客户端(101:10)向STUN服务器(404:40)发送请求,要求服务器从其他IP和PORT(505:50)向客户端回复包:

a,收不到服务器从其他IP地址(包括IP和Port)的回复,认为包被前置NAT设备阻断,进入第4步.(如果不是前置的就相当于全开Opened或Full ConeNat类型,无论那个IP和端口都能接收到回复)
      b,收到则认为NAT设备类型为Full Cone,即网络类型:Full Cone NAT.(此没有什么限制的基本和没有NAT一样Opened)
  4, STUN客户端(101:10)向STUN服务器(404:40)的另外一个IP地址(505:40,端口不能改变)发送请求,要求得到自身经NAT映射后的地址(202:20,如果不是对称的应该返回这个映射地址),并对比之(与第一步中返回的映射地址比对)
      a,地址不相同,则网络类型:Symmetric NAT.(如果是对称类型,则101:10在向一个不同的IP地址(505,端口不变)发送请求时会映射一个新的端口,此处相当于生成一个202:21,比对不相同)
      b,相同则认为是Restricted NAT(受限的),进入第5步,进一步确认类型.
5, (已确认RestrictedNAT设备)STUN客户端(101:10)向STUN服务器(404:40)发送请求,要求服务器从相同IP(404)的其他PORT(41)向客户端回复包:
      a,收不到服务器从其他PORT地址的回复,认为包被前置NAT设备阻断,网络类型:Port Restricted coneNAT.(端口改变了,端口受限相当于我一个人A(101)向B(404)要右手(40端口)的苹果,而B左手(41端口)有个香蕉,A只要B的右手苹果,而B给了A一个左手的香蕉,A肯定是不要的,相当于收不到回复)
      b,收到则认为网络类型: Restricted cone NAT.(IP受限,对端口没什么要求,只要是404

这个IP就行,无论用那个端口都行)

以上实现过程作为服务器只有双IP的服务器才能实现,针对我们这些电脑只有一个IP的开发者来说,我们就得通过两台电脑来模拟实现双IP了,这地方上面请求的服务器地址就得改变一下了,个人认为这里的Server1:404地址的端口必须和Server2:505地址的端口一样,才能实现,端口相同也不会影响测试效果的.

来源:http://blog.csdn.net/hack8/article/details/6593768

客户端主机所在网络可以分为以下类型:

1, Opened: 即主机拥有公网IP,并且没有防火墙,可自由与外部通信.

2, Full Cone NAT: 主机前有NAT设备,

NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,并且从任意外部地址发送到该NAT设备UDP端口B的包都会被转到主机端口A.

3, Restricted cone NAT: 主机前有NAT设备,

 NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,但只有从之前该主机发出包的目的IP发出到该NAT设备UDP端口B的包才会被转到主机端口A.

4, Port Restricted cone NAT: 主机前有NAT设备,

  NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,但只有从之前该主机发出包的目的IP/

解读Linux启动过程

转自:https://my.oschina.net/macwe/blog/1531024

解读Linux启动过程

 

1. 概述

本文解读一下从CPU加电自检到启动init进程的过程, 先通过下面这张图大致看一下Linux启动的整个过程。

本文的分析环境是GRUB 0.97 + Linux 2.6.18。

2. BIOS

CPU加电后首先工作在实模式并初始化CS:IP=FFFF:FFF0,BIOS的入口代码必须从该地址开始。BIOS完成相应的硬件检查并提供一系列中断服务例程,这些中断服务提供给系统软件访问硬件资源(比如磁盘、显示器等),最后选择一个启动盘加载第一个扇区(即:MBR,共512字节)数据到内存0x7C00处,并从这里开始执行指令(CS:IP=0000:7C00),对于笔者的电脑来说这就是GRUB的Stage1部分。

3. GRUB

GRUB的作用是bootloader,用来引导各种OS的。

3.1. Stage1

Stage1就是MBR,由BIOS把它从磁盘的0扇区加载到0x7c00处,大小固定位512字节,此时的CPU上下文如下:

eax=0011aa55 ebx=00000080 ecx=00000000 edx=00000080 esi=0000f4a0 edi=0000fff0
eip=00007c00 esp=00007800 ebp=00000000 iopl=0 nv up ei pl zr na po nc
cs=0000 ds=0000 es=0000 fs=0000 gs=0000 ss=0000 eflags=00000246
// 注: dl=启动磁盘号, 00H~7FH是软盘, 80H~FFH是硬盘。

因为只能是512字节,大小受限,它就干一件事,把Stage2的第一个512字节读取到0x8000,然后jmp到0x8000继续执行。

3.1.1. 读磁盘

磁盘扇区寻址有两种方式:

  • CHS方式:传统的方式,使用三元组(10位Cylinder, 8位Head, 6位Sector)来寻找扇区,最大只能找到(2^10) * (2^8) * (2^6) * 512 = 8GB的硬盘容量,现在的硬盘明显不够用了。
  • LBA方式:现在的方式,使用48位线性地址来寻找扇区,最大支持(2^48) * 512 = 128PB的硬盘空间。虽然机械上还是CHS的结构,不过磁盘的固件会自动完成LBA到CHS的转换。

因为CHS明显不适合现在的硬盘,所以LBA模式寻址是现在的PC的标配了吧!万一磁盘不支持LBA或者是软盘,需要我们手工转换成CHS模式。转换公式如下(就是三维空间定位一个点的问题):

磁道号C = LBA / 每磁道的扇区数SPT / 盘面总HPC
磁头号H = (LBA / 每磁道的扇区数SPT) mod HPC
扇区号S = (LBA mod SPT) + 1

判断是否支持LBA模式

/* check if LBA is supported */
movb	$0x41, %ah
movw	$0x55aa, %bx
int	$0x13

如果返回成功(CF=1)并且BX值是0xAA55表示支持LBA寻址(用Extensions方法)。

注意:3.5英寸软盘需要使用CHS方式寻址,它的CHS参数是80个柱面、2个磁头、每个磁道18个扇区,每扇区512字节,共1.44MB容量。

LBA模式读的功能号是AH=42h,DL参数是磁盘号,DS:SI参数是Disk Address Packet(DAP)结构体的内存地址,定义如下:

struct DAP {
    uint8_t sz; // 结构体大小
    uint8_t unused;
    uint16_t sector_cnt; // 需要都的扇区总数
    struct dst_addr { // 内存地址,读到这里
        uint16_t offset;
        uint16_t segment;
    };
    uint64_t lba_addr;  // 磁盘的LBA地址
};

参考:

  • https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
  • https://en.wikipedia.org/wiki/INT_13H

3.2. Stage2

Stage2就是GRUB剩下的全部的代码了,包括BIOS中断服务的封装给C代码使用、键盘驱动、文件系统驱动、串口、网络驱动等等,它提供了一个小型的命令行环境,可以解析用户输入命令并执行对OS的启动。

3.2.1. start.S

首先Stage2的头512字节(start.S)被加载到0x8000,并在这里开始执行,此时的CPU上下文如下:

eax=00000000 ebx=00007000 ecx=00646165 edx=00000080 esi=00007c05 edi=0000fff0
eip=00008000 esp=00001ffe ebp=00000000 iopl=0 nv up ei pl zr na po nc
cs=0000 ds=0000 es=0800 fs=0000 gs=0000 ss=0000 eflags=00000246

start.S的工作是把Stage2的后续部分全部加载到内存中(从0x8200开始),有103KB大小。

3.2.2. asm.S

asm.S是0x8200处的代码,先看一下CPU上下文环境:

eax=00000e00 ebx=00000001 ecx=00646165 edx=00000080 esi=00008116 edi=000081e8
eip=00008200 esp=00001ffe ebp=000062d8 iopl=0 nv up ei pl zr na po nc
cs=0000 ds=0000 es=1ae0 fs=0000 gs=0000 ss=0000 eflags=00000246
3.2.2.1. 最开始的代码应该设置好段寄存器和栈
cli
/* set up %ds, %ss, and %es */
/* cs=0000 ds=0000 es=0000 fs=0000 gs=0000 ss=0000 */
xorw	%ax, %ax
movw	%ax, %ds
movw	%ax, %ss
movw	%ax, %es

/* set up the real mode/BIOS stack */
movl	$STACKOFF, %ebp
movl	%ebp, %esp
sti

此时:

cs=0000 ds=0000 es=0000 ss=0000 esp=00001ff0 ebp=00001ff0。
3.2.2.2. 保护模式和实模式

因为GRUB没有实现自己的中断服务,所以访问硬件资源还是使用BIOS的中断服务例程(实模式)。GRUB的命令行环境是工作在保护模式下的,所以当GRUB需要访问BIOS中断的时候需要切换回实模式,于是在GRUB执行过程中会有频繁的实模式和保护模式的互相切换操作,当切换回实模式后别忘了保存保护模式下的栈指针

(1) 实模式进入保护模式

/* transition to protected mode */
DATA32	call EXT_C(real_to_prot)

/* The ".code32" directive takes GAS out of 16-bit mode. */
.code32

下图是实模式到保护模式的切换步骤:

GRUB没有设置分页机制和新的中断,所以GRUB的保护模式访问的是物理内存且是不能使用INT指令,不过对于bootloader来说够用了。因为需要切换到保护模式栈,原来的返回地址要放到新的栈上,以保证能够正常ret:

ENTRY(real_to_prot)
	...
	/* put the return address in a known safe location */
	movl	(%esp), %eax
	movl	%eax, STACKOFF  ; 把返回地址保存起来备用

	/* get protected mode stack */
	movl	protstack, %eax
	movl	%eax, %esp
	movl	%eax, %ebp      ; 设置保护模式的栈

	/* get return address onto the right stack */
	movl	STACKOFF, %eax
	movl	%eax, (%esp)    ; 把返回地址重新放到栈上
	
	/* zero %eax */
	xorl	%eax, %eax

	/* return on the old (or initialized) stack! */
	ret                     ; 正常返回

(2) 保护模式切换回实模式

	/* enter real mode */
	call	EXT_C(prot_to_real)
	
	.code16

下图说明了保护模式切换回实模式的步骤:

保护模式的栈需要保存起来以便恢复现场,让C代码正确运行,实模式的栈每次都重置为STACKOFF即可,和(1)一样,也要设置好返回地址:

ENTRY(prot_to_real)
	...
	/* save the protected mode stack */
	movl	%esp, %eax
	movl	%eax, protstack  ; 把栈保存起来

	/* get the return address */
	movl	(%esp), %eax
	movl	%eax, STACKOFF   ; 返回地址放到实模式栈里

	/* set up new stack */
	movl	$STACKOFF, %eax  ; 设置实模式的栈
	movl	%eax, %esp
	movl	%eax, %ebp
	... 
3.2.2.3. 创建C运行时环境

C的运行环境主要包括栈、bss数据区、代码区。随着切换到保护模式,栈已经设置好了;随着Stage2从磁盘加载到内存,代码区和bss区都已经在内存了,最后还需要把bss区给初始化一下(清0),接下来即可愉快的执行C代码了。

3.2.2.4. 执行cmain()

先执行一个init_bios_info()获取BIOS的信息,比如被BIOS使用的内存空间(影响我们Linux映像加载的位置)、磁盘信息、ROM信息、APM信息,最后调用cmain()。 cmain()函数在stage2.c文件中,其中最主要的函数run_menu()是启动一个死循环来提供命令行解析执行环境。

3.2.2.5. load_image()

如果grub.cfg或者用户执行kenrel命令,会调用load_image()函数来将内核加载到内存中。至于如何加载linux镜像在Documentation的boot.txt和zero-page.txt有详细说明。

load_image()是一个非常长的函数,它要处理支持的各种内核镜像格式。Linux镜像vmlinuz文件头是struct linux_kernel_header结构体,该结构体里头说明了这个镜像使用的boot协议版本、实模式大小、加载标记位和需要GRUB填写的一些参数(比如:内核启动参数地址)。

  • 实模式部分:始终被加载到0x90000位置,并从0x90200开始执行(linux 0.11就这样做了)。
  • 保护模式部分:我们现在使用的内核比较大(大于512KB),叫做bzImage,加载到0x100000(高位地址,1MB)开始的位置,可以任意大小了。否则小内核zImage放在0x10000到mbi.mem_lower * 1024(一般是0x90000)区域。
3.2.2.6. linux_boot()

我们正常的启动过程调用的是big_linux_boot()函数,把实模式部分copy到0x90000后,设置其他段寄存器值位0x9000, 设置CS:IP=9020:0000开始执行(使用far jmp)。

至此GRUB的工作完成,接下来执行权交给Linux了。

4. setup.S

该文件在arch/i386/boot/setup.S,主要作用是收集硬件信息并进入保护模式head.S。初始的CPU上下文如下:

eax=00000000 ebx=00009000 ecx=00000000 edx=00000003 esi=002d8b54 edi=0009a000
eip=00000000 esp=00009000 ebp=00001ff0 iopl=0 nv up di pl zr na po nc
cs=9020 ds=9000 es=9000 fs=9000 gs=9000 ss=9000  eflags=00000046

4.1. 自身检查

先检查自己setup.S是否合法,主要是检查末尾的两个magic是否一致

# Setup signature -- must be last
setup_sig1:	.word	SIG1
setup_sig2:	.word	SIG2

4.2. 收集硬件信息

主要是通过BIOS中断来收集硬件信息。收集的信息包括内存大小、键盘、鼠标、显卡、硬盘、APM等等。收集的硬件信息保存在0x9000处:

# 设置ds = 0x9000,用来保存硬件信息
movw	%cs, %ax			# aka SETUPSEG
subw	$DELTA_INITSEG, %ax 		# aka INITSEG
movw	%ax, %ds

这里看一下如何获取内存大小,这样OS才能进行内存管理。这里用三种方法获取内存信息:

  1. e820h:请求中断INT 15H,AX=E820H时返回可用的物理内存信息,e820由此得名,参考http://www.uruk.org/orig-grub/mem64mb.html。由于内存的使用是不连续的,通过连续调用INT 15H得到所有可用的内存区域,每次查询得到的结果ES:DI是个struct address_range_descriptor结构体,返回的结果都是64位的,完全能够满足目前PC的需求了。
     struct address_range_descriptor {
     	uint32_t base_addr_low;   // 起始物理地址
     	uint32_t base_addr_high;
     	uint32_t length_low;      // 长度
     	uint32_t length_high;
     	uint8_t type;             // 1=OS可用的, 2=保留的,OS不可用
     };
    
  2. e801h:通过请求中断INT15h,AX=e801H返回结果,最高只能得到4GB内存结果。
  3. 88h:古老的办法,通过请求中断INT15h,AH=88H返回结果。最高只能得到16MB或者64MB的内存,现在的电脑不适用了。

扩展阅读:http://wiki.osdev.org/Detecting_Memory_(x86)#E820h

4.3. 启用A20

让CPU访问1MB以上的扩展内存,否则访问的是X mod 1MB的地址。下面列举三种开启A20的方法:

  1. 使用I/0端口92H,AL的将1-bit置1
     inb	$0x92, %al			# Configuration Port A
     orb	$0x02, %al			# "fast A20" version
     andb	$0xFE, %al			# don't accidentally reset
     outb	%al, $0x92
    
  2. 使用BIOS中断INT 0x15, AX=0x2401
     movw	$0x2401, %ax
     pushfl					# Be paranoid about flags
     int	$0x15
     popfl
    
  3. 使用键盘控制器
     movb	 $0xD1, %al			# command write
     outb	 %al, $0x64
     call	 empty_8042
    
     movb	 $0xDF, %al			# A20 on
     outb	 %al, $0x60
     call	 empty_8042
    

4.4. 进入保护模式

4.4.1. 临时的GDT和IDT

这里的IDT全部是0;Linux目前使用的GDT如下:

gdt:
	.fill GDT_ENTRY_BOOT_CS,8,0

	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9A00				# code read/exec
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9200				# data read/write
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)
gdt_end:

这里只定义了两个DPL为0的代码段和数据段,只给内核使用的。

4.4.1. 设置CR0.PE

这里使用lmsw指令,它和mov cr0, X是等价的

movw	$1, %ax				# protected mode (PE) bit
lmsw	%ax				# This is it!
jmp	flush_instr

4.5. 调转到head.S(CS:EIP=0x10:100000)

至此硬件信息就收集完成,这些收集到的硬件信息都保存在0x90000处,后续OS可以使用这些硬件信息来管理了。

5. head.S

该文件位于arch/i386/kernel/head.S,这个是内核保护模式的代码的起点,笔者电脑的位置在0x100000,此时CPU上下文是:

eax=00000001 ebx=00000000 ecx=0000ff03 edx=47530081 esi=00090000 edi=00090000
eip=00100000 esp=00008ffe ebp=00001ff0 iopl=0 nv up di pl nz na pe nc
cs=0010 ds=0018 es=0018 fs=0018 gs=0018 ss=0018               eflags=00000002

注:已经进入保护模式,CS的值是GDT表项的索引。

它的作用就是设置真正的分段机制和分页机制、启动多处理器、设置C运行环境,最后执行start_kernel()函数。

5.1. startup_32

5.1.1. 加载临时的分段机制

boot_gdt_table就是临时的GDT,其实和start.S的一样:

	lgdt boot_gdt_descr - __PAGE_OFFSET
	movl $(__BOOT_DS),%eax
	movl %eax,%ds
	movl %eax,%es
	movl %eax,%fs
	movl %eax,%gs

ENTRY(boot_gdt_table)
	.fill GDT_ENTRY_BOOT_CS,8,0
	.quad 0x00cf9a000000ffff	/* kernel 4GB code at 0x00000000 */
	.quad 0x00cf92000000ffff	/* kernel 4GB data at 0x00000000 */

5.1.2. 初始化内核bss区和内核启动参数

为了让C代码正常运行,bss区全部清0,启动参数需要移动到boot_params位置。

5.1.3. 启动临时分页机制

临时的页表,只要能够满足内核使用就行。页目录表是swapper_pg_dir,它是一个4096大小的内存区域,默认全是0。一般__PAGE_OFFSET=0xC0000000(3GB),这是要把物理地址0x00000000映射到0xc0000000的地址空间(内核地址空间)。下面是页目录表和页表的初始化代码:

page_pde_offset = (__PAGE_OFFSET >> 20); // 3072,页目录的偏移

	// 页目录表存放在pg0位置,arch/i386/kernel/vmlinux.lds中定义
	movl $(pg0 - __PAGE_OFFSET), %edi
	movl $(swapper_pg_dir - __PAGE_OFFSET), %edx  // edx是页目录表的地址
	movl $0x007, %eax			/* 0x007 = PRESENT+RW+USER */
10:
	// 创建一个页目录项
	leal 0x007(%edi),%ecx			/* Create PDE entry */
	movl %ecx,(%edx)			/* Store identity PDE entry */
	movl %ecx,page_pde_offset(%edx)		/* Store kernel PDE entry */
	addl $4,%edx   // 指向swapper_pg_dir的下一个项
	movl $1024, %ecx   // 每个页表1024个项目
11:
	stosl  // eax -> [edi]; edi = edi + 4
	addl $0x1000,%eax // 每次循环,下一个页目录项
	loop 11b
	/* End condition: we must map up to and including INIT_MAP_BEYOND_END */
	/* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
	leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp  // 页表覆盖到这里就终止
	cmpl %ebp,%eax
	jb 10b
	movl %edi,(init_pg_tables_end - __PAGE_OFFSET)

下面是对上面代码的翻译(这样更有利于理解):

extern uint32_t *pg0;  // 初始值全0
extern uint32_t *swapper_pg_dir;  // 初始值全0

void init_page_tables()
{
	uint32_t PAGE_FLAGS = 0x007; // PRESENT+RW+USER
	uint32_t page_pde_offset = (_PAGE_OFFSET >> 20); // 3072
	uint32_t addr = 0 | PAGE_FLAGS;  // 内存地址+页表属性
	uint32_t *pg_dir_ptr = swapper_pg_dir; // 页目录表项指针
	uint32_t *pg0_ptr = pg0;  // 页表项指针
	
	for (;;) {
		// 设置页目录项,同时映射两个地址,让物理地址和虚拟地址都能访问,
		*pg_dir_ptr = pg0 | PAGE_FLAGS;     // 0, 1
		*(uint32_t *)((char *)pg_dir_ptr + page_pde_offset) = pg0 | PAGE_FLAGS;  // 768, 769
		pg_dir_ptr++;
		
		// 设置页表项目
		for (int i = 0; i < 1024; i++) {
			*pg0++ = addr;
			addr += 0x1000;
		}
		// 退出条件,实际上只映射了两个页目录就退出了(0,1,768, 769)
		if (pg0[INIT_MAP_BEYOND_END] | PAGE_FLAGS) >= addr) {
			init_pg_tables_end = pg0_ptr;
			return;
		}
	}	
};

5.1.4. 设置栈

/* Set up the stack pointer */
	lss stack_start,%esp

ENTRY(stack_start)
	.long init_thread_union+THREAD_SIZE
	.long __BOOT_DS

/* arch/i386/kernel/init_task.c
* Initial thread structure.
*
* We need to make sure that this is THREAD_SIZE aligned due to the
* way process stacks are handled. This is done by having a special
* "init_task" linker map entry..
*/
union thread_union init_thread_union 
	__attribute__((__section__(".data.init_task"))) =
		{ INIT_THREAD_INFO(init_task) };

内核最初使用的栈是init_task进程的,也就是0号进程的栈,这个进程是系统唯一一个静态定义而不是通过fork()产生的进程。

5.1.5. 设置真正的IDT和GDT

	lgdt cpu_gdt_descr   // 真正的GDT
	lidt idt_descr    //真正的IDT
	ljmp $(__KERNEL_CS),$1f   // 重置CS
1:	movl $(__KERNEL_DS),%eax	# reload all the segment registers
	movl %eax,%ss			# after changing gdt.  // 重置SS

	movl $(__USER_DS),%eax		# DS/ES contains default USER segment
	movl %eax,%ds
	movl %eax,%es

	xorl %eax,%eax			# Clear FS/GS and LDT
	movl %eax,%fs
	movl %eax,%gs
	lldt %ax
	cld			# gcc2 wants the direction flag cleared at all times
	// push一个假的返回地址以满足 start_kernel()函数return的要求
	pushl %eax		# fake return address  

对于IDT先全部初始化成ignore_int例程:

setup_idt:
	lea ignore_int,%edx
	movl $(__KERNEL_CS << 16),%eax
	movw %dx,%ax		/* selector = 0x0010 = cs */
	movw $0x8E00,%dx	/* interrupt gate - dpl=0, present */

	lea idt_table,%edi
	mov $256,%ecx
rp_sidt:
	movl %eax,(%edi)
	movl %edx,4(%edi)
	addl $8,%edi
	dec %ecx
	jne rp_sidt
	ret

ignore_int例程就干一件事,打印一个错误信息"Unknown interrupt or fault at EIP %p %p %p\n"

对于GDT我们最关心的__KERNEL_CS、__KERNEL_DS、__USER_CS、__USER_DS这4个段描述符:

.quad 0x00cf9a000000ffff	/* 0x60 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff	/* 0x68 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff	/* 0x73 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff	/* 0x7b user 4GB data at 0x00000000 */

至此分段机制、分页机制、栈都设置好了,接下去可以开心的jmp start_kernel了。

6. start_kernel

该函数在linux/init/main.c文件里。我们可以认为start_kernel是0号进程init_task的入口函数,0号进程代表整个linux内核且每个CPU有一个。 这个函数开始做一系列的内核功能初始化,我们重点看rest_init()函数。

6.1.rest_init

这是start_kernel的最后一行,它启动一个内核线程运行init函数后就什么事情也不做了(死循环,始终交出CPU使用权)。

static void noinline rest_init(void)
{
	kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND); // 启动init
	……
	/* Call into cpu_idle with preempt disabled */
	cpu_idle();  // 0号进程什么事也不做
}

6.2. init()

该函数的末尾fork了”/bin/init”进程。这样1号进程init就启动了,接下去就交给init进程去做应用层该做的事情了!

// 以下进程启动后父进程都是0号进程
if (ramdisk_execute_command) {
	run_init_process(ramdisk_execute_command);
	printk(KERN_WARNING "Failed to execute %s\n",
			ramdisk_execute_command);
}

/*
 * We try each of these until one succeeds.
 *
 * The Bourne shell can be used instead of init if we are 
 * trying to recover a really broken machine.
 */
if (execute_command) {
	run_init_process(execute_command);
	printk(KERN_WARNING "Failed to execute %s.  Attempting "
				"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

附录1. 启动多核CPU

以上解读的内容只在0号CPU上执行,如果是多CPU的环境,还要初始化其他的CPU。多CPU启动的起点是start_kernel()->rest_init()>init()->smp_init()。而smp_init()函数给每个CPU上调用cpu_up()do_boot_cpu()函数,每个CPU都要再走一遍head.S的流程,然后启动自己的idle进程(内核态0号进程)。

附录2. x64的不同

i386和x64的启动代码主要区别在head.S中。

  • 页表格式不同,i386使用两级页表,x64使用4级页表。
  • 多了兼容32位的代码段和数据段__USER32_CS、__USER32_DS和__KERNEL32_CS
  • x64段寄存器用法和i386的不同:x64下面CS、DS、ES、SS不用了,始终为0。而FS、GS寄存器的用法倒像是实模式下的,主要考虑是保留两个作为基地址好让线性地址计算方便。FS:XX = MSR_FS_BASE + XXGS:XX = MSR_GS_BASE + XX, 不是段描述符索引了(像实模式的分段)。

婚姻周年纪念日

第1年:纸婚
第2年:棉婚 第3年:皮革婚 第4年:水果婚 第5年:木婚 第6年:铁婚
第7年:铜婚
第8年:陶婚 第9年:柳婚
第10年:铝婚
第11年:钢婚 第12年:丝婚 第13年:丝带婚 第14年:象牙婚
第15年:水晶婚 第20年:瓷婚
第25年:银婚
第30年:珍珠婚 第35年:珊瑚婚 第40年:红宝石婚 第45年:蓝宝石婚
第50年:金婚
第55年:绿宝石婚
第60年:钻石婚
第70年:白金婚
第75年:白石婚
第80年:   橡树婚


结婚后的每一个纪念日都有不同的名称,不同的象征意义:
第一年是 纸婚(意思是一张纸印的婚姻关系,比喻最初结合薄如纸,要小心保护!)Paper wedding
第二年 棉婚(加厚一点,尚须磨炼!)Cotton wedding
第三年 皮革婚(开始有点韧性)Leather wedding
第四年 丝婚(缠紧,如丝般柔韧,你浓我浓。)Silk wedding
第五年 木婚(硬了心,已经坚韧起来)Wood wedding
第六年 铁婚(夫妇感情如铁般坚硬永固) Iron or Sugar Candy wedding
第七年 铜婚(比铁更不会生锈,坚不可摧) Copper wedding
第八年 陶婚 (如陶瓷般美丽,并须呵护)Pottery wedding
第九年 柳婚 (像垂柳一样,风吹雨打都不怕。)Willow wedding
第十年 锡婚 (锡器般坚固,不易跌破。)Tin wedding
第十一年 钢婚 (如钢铁般坚硬,今生不变。)Steel wedding
第十二年 链婚 (像铁链一样,心心相扣)Linen wedding
第十三年 花边婚 (多姿多彩,多样化的生活)Lace wedding
第十四年 象牙婚 (时间愈久,色泽愈光亮美丽)Ivory wedding
第十五年 水晶婚 (透明清澈而光彩夺目)Crystal wedding


以后每5年一个名称:
第二十年 瓷婚 (光滑无暇,需呵护,不让跌破)China wedding
第二十五年 银婚 (已有恒久价值,是婚后第一个大庆典)Silver wedding
第三十年 珍珠婚 (像珍珠般浑圆,美丽和珍贵)Pearls wedding
第三十五年 珊瑚婚 (嫣红而宝贵,生色出众)Coral wedding
第四十年 红宝石婚 (名贵难得,色泽永恒)Ruby wedding
第四十五年 蓝宝石婚 (珍贵灿烂,值得珍惜)Sapphire wedding
第五十年 金婚 (至高无上,婚后第二大庆典,情如金坚,爱情历久弥新)Golden wedding
第五十五年 翡翠婚 (如翡翠玉石,人生难求)Emerald wedding
第六十年 钻石婚(夫妻一生中最大的一次结婚典庆,珍奇罕有,今生无悔,是最隆重庆典) Diamond wedding (Diamond Jubilee)
凡六十一七十结婚周年纪念,中国人统称为“福禄寿婚”。

linux 常见终端热键以及Ctrl+C、Ctrl+Z比较 [转]

linux中存在一些按键,那么如何查阅目前的一些按键内容了?可以利用stty(setting tty 终端机的意思)。stty也可以帮助设置终端机的输入按键代表意义。

 >$ stty -a
speed 38400 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ;
eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke

需要注意的是特殊字体那几个,此外^表示[Ctrl]那个按键的意思,如:intr = ^C表示利用【Ctrl】+c来完成的。几个重要的代表意义是:

eof:End of file的意思,代表结束输入;

erase:向前删除一个字符;

intr:送出一个interrupt(中断)的信号给目前正在运行的程序;
kill:删除在目前命令行上的所有字符;

quit:送出一个quit的信号给目前正在运行的进程;

start:在某个进程停止后,重新启动它的输出;

stop:停止目前屏幕的输出;

susp:送出一个terminal stop的信号给正在运行的进程;

如果你想要执行[ctrl]+h来进行字符的删除,那么可以执行:

root@mycomputer:~# stty erase ^h
Ctrl+C终止目前的命令

Ctrl+D输入结束(eof),例如邮件结束的时候

eof代表End of file的意思,代表结束输入

Ctrl+M就是Enter

Ctrl+S暂停屏幕的输出

Ctrl+Q恢复屏幕的输出

Ctrl+U在提示符下,将整行命令删除

Ctrl+Z暂停目前的命令

Ctrl+Z和Ctrl+C都是中断命令,但是它们的作用却不一样。

Ctrl+C是强制中断程序的执行,而Ctrl+Z是将任务中断,但是此任务并没有结束,还是在进程中只是保持挂起的状态,用户可以使用fg/bg操作继续前台或后台飞任务,fg命令重新启动前台被中断的任务。bg命令把被中断的任务放在后台执行。

来源:http://blog.sina.com.cn/s/blog_14ecbe4520102wrmv.html

 

信号具有平台相关性,不同平台下能使用的信号种类是有差异的。

Linux下支持的信号:

SEGV, ILL, FPE, BUS, SYS, CPU, FSZ, ABRT, INT, TERM, HUP, USR1, USR2, QUIT, BREAK, TRAP, PIPE

Windows下支持的信号:

SEGV, ILL, FPE, ABRT, INT, TERM, BREAK

React 定义组件的参数-生命周期

定义组件的参数-生命周期
创建期:getDefaultProps
创建期:getInitialState
创建期:componentWillMount
创建期:componentDidMount
存在期:componentWillReceiveProps
存在期:shouldComponentUpdate
存在期:componentWillUpdate
存在期:componentDidUpdate
销毁&清理期:componentWillUnmount

定义组件的参数-生命周期
生命周期相关参数,是React定义组件时提供的一系列处理函数(钓子函数),这些函数会在组件生命周期的某个阶段调用。

创建期:getDefaultProps

object getDefaultProps()

创建期:getInitialState

object getInitialState()
在组件挂载前(即:创建期)调用一次,其返回值将做为this.state的初始值。

getInitialState()方法会组件类创建的时候调用一次,其返回值会被缓存下来。该方法用于设置props属性的默认值,但仅对于非必须属性。如果父组件没有指定props中的某个值,此返回对象中的相应属性将会合并到this.props。

getInitialState()方法会在组件实例创建前调用,这时还不能使用this.props属性,且其返回对象是在所有实例间共享的。

创建期:componentWillMount

componentWillMount()
componentWillMount()服务器端和客户端都只调用一次,在初始化渲染执行之前被调用。如果在这个方法内调用setState()方法,render()方法将会收到更新后的state,也就是说这是我做在组件渲染前最后一个修改state的机会。

创建期:componentDidMount

componentDidMount()
componentDidMount()会在组件初始化(渲染完成)后立即调用一次,我们一般在这个方法中使用this.getDOMNode()方法访问原始DOM。

存在期:componentWillReceiveProps

componentWillReceiveProps(object nextProps)

componentWillReceiveProps在将要接受新的props时被调用
componentWillReceiveProps()方法会在组件生命周期的存在期调用,当组件感知到props属性改变,会调用此方法。render()方法将会在其后调用,这时我们可以通过this.setState()来阻止组件的再次渲染。

存在期:shouldComponentUpdate

boolean shouldComponentUpdate(object nextProps, object nextState)
shouldComponentUpdate()方法发生在组件生命周期的存在器,在组件收到新的props或state。在这个方法中,我们可以访问组件的props和state属性,通过这两个属性可以确认组件是否需要更新,如果不需要更新,则返回false,则其后的方法将不会在执行。如:

shouldComponentUpdate: function(nextProps, nextState) {
return nextProps.id !== this.props.id;
}

存在期:componentWillUpdate

componentWillUpdate(object nextProps, object nextState)
componentWillUpdate()会在收到新的props或state后调用,类似componentWillMount()。

存在期:componentDidUpdate

componentDidUpdate(object prevProps, object prevState)
componentDidUpdate()会在组件重新渲染后立即被调用,当我们需要在组件重新渲染后操作DOM则需要使用这个方法。

销毁&清理期:componentWillUnmount

componentWillUnmount()
componentWillUnmount()是组件销毁&清理期唯一调用的方法,它会在组件从DOM中移除时被调用,这时我们可以清理一些状态或清理在componentDidMount中创建的DOM元素。

优秀程序员眼中的整洁代码[转]

有多少程序员,就有多少定义。所以我只询问了一些非常知名且经验丰富的程序员。

image.php_-57.gif

Bjarne Stroustrup,C++语言发明者,C++ Programming Language(中译版《C++程序设计语言》)一书作者。

我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。

Bjarne用了“优雅”一词。说得好!我MacBook上的词典提供了如下定义:外表或举止上令人愉悦的优美和雅观;令人愉悦的精致和简单。注意对“愉悦”一词的强调。Bjarne显然认为整洁的代码读起来令人愉悦。读这种代码,就像见到手工精美的音乐盒或者设计精良的汽车一般,让你会心一笑。

Bjarne也提到效率——而且两次提及。这话出自C++发明者之口,或许并不出奇;不过我认为并非是在单纯追求速度。被浪费掉的运算周期并不雅观,并不令人愉悦。留意Bjarne怎么描述那种不雅观的结果。他用了“引诱”这个词。诚哉斯言。糟糕的代码引发混乱!别人修改糟糕的代码时,往往会越改越烂。

务实的Dave Thomas和Andy Hunt从另一角度阐述了这种情况。他们提到破窗理论4。窗户破损了的建筑让人觉得似乎无人照管。于是别人也再不关心。他们放任窗户继续破损。最终自己也参加破坏活动,在外墙上涂鸦,任垃圾堆积。一扇破损的窗户开辟了大厦走向倾颓的道路。

Bjarne也提到完善错误处理代码。往深处说就是在细节上花心思。敷衍了事的错误处理代码只是程序员忽视细节的一种表现。此外还有内存泄漏,还有竞态条件代码。还有前后不一致的命名方式。结果就是凸现出整洁代码对细节的重视。

Bjarne以“整洁的代码只做好一件事”结束论断。毋庸置疑,软件设计的许多原则最终都会归结为这句警语。有那么多人发表过类似的言论。糟糕的代码想做太多事,它意图混乱、目的含混。整洁的代码力求集中。每个函数、每个类和每个模块都全神贯注于一事,完全不受四周细节的干扰和污染。

image.php_-58.gif

Grady Booch,Object Oriented Analysis and Design with Applications(中译版《面向对象分析与设计》)一书作者。

整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

Grady的观点与Bjarne的观点有类似之处,但他从可读性的角度来定义。我特别喜欢“整洁的代码如同优美的散文”这种看法。想想你读过的某本好书。回忆一下,那些文字是如何在脑中形成影像!就像是看了场电影,对吧?还不止!你还看到那些人物,听到那些声音,体验到那些喜怒哀乐。

阅读整洁的代码和阅读Lord of the Rings(中译版《指环王》)自然不同。不过,仍有可类比之处。如同一本好的小说般,整洁的代码应当明确地展现出要解决问题的张力。它应当将这种张力推至高潮,以某种显而易见的方案解决问题和张力,使读者发出“啊哈!本当如此!”的感叹。

窃以为Grady所谓“干净利落的抽象”(crisp abstraction),乃是绝妙的矛盾修辞法。毕竟crisp几乎就是“具体”(concrete)的同义词。我MacBook上的词典这样定义crisp一词:果断决绝,就事论事,没有犹豫或不必要的细节。尽管有两种不同的定义,该词还是承载了有力的信息。代码应当讲述事实,不引人猜测。它只该包含必需之物。读者应当感受到我们的果断决绝。

image.php_-59.gif

“老大”Dave Thomas,OTI公司创始人,Eclipse战略教父

整洁的代码应可由作者之外的开发者阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名。它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确地定义和提供清晰、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需信息均可通过代码自身清晰表达。

Dave老大在可读性上和Grady持相同观点,但有一个重要的不同之处。Dave断言,整洁的代码便于其他人加以增补。这看似显而易见,但亦不可过分强调。毕竟易读的代码和易修改的代码之间还是有区别的。

Dave将整洁系于测试之上!要在十年之前,这会让人大跌眼镜。但测试驱动开发(Test Driven Development)已在行业中造成了深远影响,成为基础规程之一。Dave说得对。没有测试的代码不干净。不管它有多优雅,不管有多可读、多易理解,微乎测试,其不洁亦可知也。

Dave两次提及“尽量少”。显然,他推崇小块的代码。实际上,从有软件起人们就在反复强调这一点。越小越好。

Dave也提到,代码应在字面上表达其含义。这一观点源自Knuth的“字面编程”(literate programming)5。结论就是应当用人类可读的方式来写代码。

image.php_-60.gif

Michael Feathers,Working Effectively with Legacy Code(中译版《修改代码的艺术》)一书作者。

我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的。整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码——全心投入的某人留下的代码。

一言以蔽之:在意。这就是本书的题旨所在。或许该加个副标题,如何在意代码。

Michael一针见血。整洁代码就是作者着力照料的代码。有人曾花时间让它保持简单有序。他们适当地关注到了细节。他们在意过。

image.php_-61.gif

Ron Jeffries,Extreme Programming Installed(中译版《极限编程实施》)以及Extreme Programming Adventures in C#(中译版《C#极限编程探险》)作者。

Ron初入行就在战略空军司令部(Strategic Air Command)编写Fortran程序,此后几乎在每种机器上编写过每种语言的代码。他的言论值得咀嚼。

近年来,我开始研究贝克的简单代码规则,差不多也都琢磨透了。简单代码,依其重要顺序:

能通过所有测试;

没有重复代码;

体现系统中的全部设计理念;

包括尽量少的实体,比如类、方法、函数等。

在以上诸项中,我最在意代码重复。如果同一段代码反复出现,就表示某种想法未在代码中得到良好的体现。我尽力去找出到底那是什么,然后再尽力更清晰地表达出来。

在我看来,有意义的命名是体现表达力的一种方式,我往往会修改好几次才会定下名字来。借助Eclipse这样的现代编码工具,重命名代价极低,所以我无所顾忌。然而,表达力还不只体现在命名上。我也会检查对象或方法是否想做的事太多。如果对象功能太多,最好是切分为两个或多个对象。如果方法功能太多,我总是使用抽取手段(Extract Method)重构之,从而得到一个能较为清晰地说明自身功能的方法,以及另外数个说明如何实现这些功能的方法。

消除重复和提高表达力让我在整洁代码方面获益良多,只要铭记这两点,改进脏代码时就会大有不同。不过,我时常关注的另一规则就不太好解释了。

这么多年下来,我发现所有程序都由极为相似的元素构成。例如“在集合中查找某物”。不管是雇员记录数据库还是名-值对哈希表,或者某类条目的数组,我们都会发现自己想要从集合中找到某一特定条目。一旦出现这种情况,我通常会把实现手段封装到更抽象的方法或类中。这样做好处多多。

可以先用某种简单的手段,比如哈希表来实现这一功能,由于对搜索功能的引用指向了我那个小小的抽象,就能随需应变,修改实现手段。这样就既能快速前进,又能为未来的修改预留余地。

另外,该集合抽象常常提醒我留意“真正”在发生的事,避免随意实现集合行为,因为我真正需要的不过是某种简单的查找手段。

减少重复代码,提高表达力,提早构建简单抽象。这就是我写整洁代码的方法。

Ron以寥寥数段文字概括了本书的全部内容。不要重复代码,只做一件事,表达力,小规模抽象。该有的都有了。

image.php_-62.gif

Ward Cunningham,Wiki发明者,eXtreme Programming(极限编程)的创始人之一,Smalltalk语言和面向对象的思想领袖。所有在意代码者的教父。

如果每个例程都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在,就可以称之为漂亮的代码。

这种说法很Ward。它教你听了之后就点头,然后继续听下去。如此在理,如此浅显,绝不故作高深。你大概以为此言深合己意吧。再走近点看看。

“……深合己意”。你最近一次看到深合己意的模块是什么时候?模块多半都繁复难解吧?难道没有触犯规则吗?你不是也曾挣扎着想抓住些从整个系统中散落而出的线索,编织进你在读的那个模块吗?你最近一次读到某段代码、并且如同对Ward的说法点头一般对这段代码点头,是什么时候的事了?

Ward期望你不会为整洁代码所震惊。你无需花太多力气。那代码就是深合你意。它明确、简单、有力。每个模块都为下一个模块做好准备。每个模块都告诉你下一个模块会是怎样的。整洁的程序好到你根本不会注意到它。设计者把它做得像一切其他设计般简单。

那Ward有关“美”的说法又如何呢?我们都曾面临语言不是为要解决的问题所设计的困境。但Ward的说法又把球踢回我们这边。他说,漂亮的代码让编程语言像是专为解决那个问题而存在!所以,让语言变得简单的责任就在我们身上了!当心,语言是冥顽不化的!是程序员让语言显得简单。

稿源:代码湾

转自:https://www.oschina.net/news/87473/good-programmers-clean-code

mac os x10.12 安装thrift0.8 源码

参考:http://www.cnblogs.com/peterpanzsy/p/4210127.html

http://thrift.apache.org/docs/install/

一:安装最新版(自动安装)

最简单的是用homebrew进行安装

  • 安装homebrew 在终端输入ruby -e “$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)”
  • 安装thrift   brew install thrift

用brew安装的thrift版本是0.9的,but,我们项目中得thrift版本是0.8的,所以果断卸载掉。

brew uninstall thrift。转到下面第二种方法安装thrift

二:手动安装0.8.0

先安装依赖。

http://www.jattcode.com/installing-autoconf-automake-libtool-on-mac-osx-mountain-lion/

  • 安装BOOST

下载:http://www.boost.org/

命令:./bootstrap.sh,该命令用于生成bjam可执行文件,这个东西就是用来编译boost库

命令:sudo ./b2 threading=multi address-model=64 variant=release stage install

  • 安装 libevent

下载:http://libevent.org/

命令:./configure –prefix=/usr/local

命令:make

命令:sudo make install

  • 安装 Apache Thrift

下载:http://thrift.apache.org/

编译命令:./configure –prefix=/usr/local/ –with-boost=/usr/local/lib –with-libevent=/usr/local/lib –without-ruby –without-python –without-perl –without-php

有一些不相关的code genorater可以不要了,不然又得会报错。。。

安装命令:sudo make install

thrift -version 可以查看安装是否成功和版本

thrift-0.8.0.tar.gz

安装8的时候会遇到如下问题,并按如下解决:

以下摘自美团 Created by 曹继光, last modified by 严鑫 on 十月 10, 2014

Osx 10.9 是比较新的系统, 从这个版本开始, 系统默认编译器从GCC 改为 Clang(GCC -v 一下, 给的是clang 的提示).

Thrift 0.8  代码相对又比较老, 在新的编译器上遇到了下列一些问题.

问题1 :

多个源码文件报错 # include <tr1/functional>, file not found, 改为 # include <boost/tr1/functional.hpp> 解决.

需要修改以下文件:

  • lib/cpp/src/concurrency/ThreadManager.h  line:24
  • lib/cpp/src/async/TAsyncChannel.h line:23
  • lib/cpp/src/async/TAsyncChannel.cpp line:21
  • lib/cpp/src/async/TAsyncProcessor.h line:23
  • lib/cpp/src/async/TAsyncBufferProcessor.h line:23

原因:  在我试验的环境下, 此头文件有后缀名/usr/local/include/boost/ tr1/functional.hpp

问题2:

cpp 测试代码 lib/cpp/test/Benchmark.cpp 编译失败,  shared_ptr  ambigous , 用 boost::shared_ptr 替换 shared_ptr,

错误消失.

原因: Clang 支持 C++ 11, 其 标准库自带 shared_ptr(std::shared_ptr, 参考 http://zh.cppreference.com/w/cpp/memory/shared_ptr)

故需显式指定名字空间.

三:测试

下面编写一个HelloWorld.thrift 来测试一下,内容如下:

namespace java com.xx.mobile.hotel.sc.demo

service HelloWorldService {
string sayHello(1:string username)
}

执行:thrift -gen java HelloWorld.thrift 将在同级目录下生成gen-java/com/xx/mobile/hotel/sc/demo/HelloWorldService.java文件。