JavaScript Crypto-JS 使用手册

使用 Crypto-JS 可以非常方便地在 JavaScript 进行 MD5、SHA1、SHA2、SHA3、RIPEMD-160 哈希散列,进行 AES、DES、Rabbit、RC4、Triple DES 加解密。

基于 Crypto-JS 实现的在线加密解密工具 —— 在线哈希、在线散列 和 在线加密、在线解密

下面讲述如何使用 Crypto-JS。

Crypto-JS 源码托管在 Google Code,当前版本是 3.1。因为一般在国内也无法打开,所以把源代码分享到百度盘:http://pan.baidu.com/s/1bnyIGxT

Crypto-JS 原网址是 https://code.google.com/p/crypto-js/文中的英文描述均是原作者所有,Crypto-JS 采用的授权协议跟 MIT 类似,可以在任何场景放心使用。

文章最末附上 Crypto-JS 文件目录结构。

使用 Crypto-JS 进行哈希、散列

  • md5

引入 md5.js

<script src="cryptojs/rollups/md5.js"></script>

调用 md5 方法

var str = '123456';
CryptoJS.MD5(str);
  • pbkdf2

PBKDF2 is a password-based key derivation function. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can’t be used directly as a cryptographic key, some processing is required.

A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.

引入 pbkdf2.js

<script src="cryptojs/rollups/pbkdf2.js"></script>

调用 pbkdf2 方法

// 官方示例
var str = '123456';
var salt = CryptoJS.lib.WordArray.random(128/8);

var key128Bits = CryptoJS.PBKDF2(str, salt, { keySize: 128/32 });
var key256Bits = CryptoJS.PBKDF2(str, salt, { keySize: 256/32 });
var key512Bits = CryptoJS.PBKDF2(str, salt, { keySize: 512/32 });

var key512Bits1000Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, {
keySize: 512/32,
iterations: 1000
});
  • ripemd160

引入 ripemd160.js

<script src="cryptojs/rollups/ripemd160.js"></script>

调用 ripemd160 方法

var str = '123456';
CryptoJS.RIPEMD160(str);
  • sha 系列:sha1、sha2、sha3…

sha 具有一系列散列算法

The SHA hash functions were designed by the National Security Agency (NSA). SHA-1 is the most established of the existing SHA hash functions, and it’s used in a variety of security applications and protocols. Though, SHA-1’s collision resistance has been weakening as new attacks are discovered or improved.

SHA-256 is one of the four variants in the SHA-2 set. It isn’t as widely used as SHA-1, though it appears to provide much better security.

SHA-512 is largely identical to SHA-256 but operates on 64-bit words rather than 32.
CryptoJS also supports SHA-224 and SHA-384, which are largely identical but truncated versions of SHA-256 and SHA-512 respectively.

SHA-3 is the winner of a five-year competition to select a new cryptographic hash algorithm where 64 competing designs were evaluated.
SHA-3 can be configured to output hash lengths of one of 224, 256, 384, or 512 bits. The default is 512 bits.

引入 sha 相关 js

<script src="cryptojs/rollups/sha1.js"></script>
<script src="cryptojs/rollups/sha3.js"></script>
<script src="cryptojs/rollups/sha224.js"></script>
<script src="cryptojs/rollups/sha256.js"></script>
<script src="cryptojs/rollups/sha384.js"></script>
<script src="cryptojs/rollups/sha512.js"></script>

调用 sha 相关方法

var str = '123456';
CryptoJS.SHA1(str);

var str = '123456';
CryptoJS.SHA3(str);

// 配置输出的哈希长度
CryptoJS.SHA3(str, {outputLength : 224});
CryptoJS.SHA3(str, {outputLength : 256});
CryptoJS.SHA3(str, {outputLength : 384});
CryptoJS.SHA3(str, {outputLength : 512});

var str = '123456';
CryptoJS.SHA224(str);

var str = '123456';
CryptoJS.SHA256(str);

var str = '123456';
CryptoJS.SHA284(str);

var str = '123456';
CryptoJS.SHA512(str);
  • hmac 系列:hmac_md5、hmac_ripemd160、hmac_sha1、hmac_sha3…

引入 hmac 相关 js

<script src="cryptojs/rollups/hmac-sha1.js">
</script>
<script src="cryptojs/rollups/hmac-sha3.js">
</script>
<script src="cryptojs/rollups/hmac-sha224.js">
</script>
<script src="cryptojs/rollups/hmac-sha256.js">
</script>
<script src="cryptojs/rollups/hmac-sha384.js">
</script>
<script src="cryptojs/rollups/hmac-sha512.js">
</script>

调用 hmac 相关 方法

var str = '123456';
var password = 'password';

// Hmac 相关调用前都增加了 Hmac
CryptoJS.HmacMD5(str, password);
CryptoJS.HmacRIPEMD160(str, password);
CryptoJS.HmacSHA1(str, password);
CryptoJS.HmacSHA3(str, password);
CryptoJS.HmacSHA224(str, password);
CryptoJS.HmacSHA256(str, password);
CryptoJS.HmacSHA384(str, password);
CryptoJS.HmacSHA512(str, password);

使用 Crypto-JS 进行加密、解密

Crypto-JS 相关 encrypt 函数会并不直接返回字符串,需要调用返回对象的 toString 方法,或者通过 Crypto-JS 转码才能得到真实的结果。

  • AES

引入 aes.js

<script src="cryptojs/rollups/aes.js"></script>

调用 CryptoJS.AES

// 官方示例, 每次输出的密文都不一样
CryptoJS.AES.encrypt("Message", "Secret Passphrase");

// 正确用法

var str = '123456';
// 密钥 16 位
var key = '0123456789abcdef';
// 初始向量 initial vector 16 位
var iv = '0123456789abcdef';
// key 和 iv 可以一致

key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);

var encrypted = CryptoJS.AES.encrypt(str, key, {
	iv: iv,
	mode: CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
});

// 转换为字符串
encrypted = encrypted.toString();

// mode 支持 CBC、CFB、CTR、ECB、OFB, 默认 CBC
// padding 支持 Pkcs7、AnsiX923、Iso10126
// 、NoPadding、ZeroPadding, 默认 Pkcs7, 即 Pkcs5

var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
	iv: iv,
	mode: CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
});

// 转换为 utf8 字符串
decrypted = CryptoJS.enc.Utf8.stringify(decrypted);

// 引入其他加密模式, 填充模式需要引入对应的 js 文件
/*
cryptojs/components/mode-cfb-min.js
cryptojs/components/mode-ctr-min.js
cryptojs/components/mode-ecb-min.js
cryptojs/components/mode-ofb-min.js
cryptojs/components/pad-ansix923-min.js
cryptojs/components/pad-iso10126-min.js
cryptojs/components/pad-iso97971-min.js
cryptojs/components/pad-nopadding-min.js
*/
  • DES、Triple DES

DES、Triple DES 的调用形式与 AES 一致。

引入 triple.js, DES 和 Triple DES 都定义在 tripledes.js

<script src="cryptojs/rollups/tripledes.js"></script>

调用 CryptoJS.DES, CryptoJS.TripleDES

var str = '123456';
var key = '0123456789abcdef';
var iv = '0123456789abcdef';

key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);

// DES 加密
var encrypted = CryptoJS.DES.encrypt(str, key, {
	iv: iv,
	mode: CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
});

// 转换为字符串
encrypted = encrypted.toString();

// DES 解密
var decrypted = CryptoJS.DES.decrypt(encrypted, key, {
	iv: iv,
	mode: CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
});

// 转换为 utf8 字符串
decrypted = CryptoJS.enc.Utf8.stringify(decrypted);

// Triple DES 加密
var encrypted = CryptoJS.TripleDES.encrypt(str, key, {
	iv: iv,
	mode: CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
});

// 转换为字符串
encrypted = encrypted.toString();

// Triple DES 解密
var decrypted = CryptoJS.TripleDES.decrypt(encrypted, key, {
	iv: iv,
	mode: CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
});

// 转换为 utf8 字符串
decrypted = CryptoJS.enc.Utf8.stringify(decrypted);
  • Rabbit、RC4

Rabbit、RC4 调用方式一致,不支持 mode、padding。

引入 rabbit.js

<script src="cryptojs/rollups/rabbit.js"></script>

调用 CryptoJS.Rabbit

var str = '123456';
var key = '0123456789abcdef';
var iv = '0123456789abcdef';

key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);

var encrypted = CryptoJS.Rabbit.encrypt(str, key, {
	iv: iv
});

// 转换为字符串
encrypted = encrypted.toString();

var decrypted = CryptoJS.Rabbit.decrypt(encrypted, key, {
	iv: iv
});

// 转换为 utf8 字符串
decrypted = CryptoJS.enc.Utf8.stringify(decrypted);

引入 rc4.js

<script src="cryptojs/rollups/rc4.js"></script>

调用 CryptoJS.RC4

var str = '123456';
var key = '0123456789abcdef';
var iv = '0123456789abcdef';

key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);

var encrypted = CryptoJS.RC4.encrypt(str, key, {
	iv: iv
});

// 转换为字符串
encrypted = encrypted.toString();

var decrypted = CryptoJS.RC4.decrypt(encrypted, key, {
	iv: iv
});

// 转换为 utf8 字符串
decrypted = CryptoJS.enc.Utf8.stringify(decrypted);

Crypto-JS 文件目录结构

  • cryptojs
    • components
      • aes.js
      • cipher-core.js
      • core.js
      • enc-base64.js
      • enc-utf16.js
      • evpkdf.js
      • format-hex.js
      • hmac.js
      • lib-typedarrays.js
      • md5.js
      • mode-cfb.js
      • mode-ctr.js
      • mode-ctr-gladman.js
      • mode-ecb.js
      • mode-ofb.js
      • pad-ansix923.js
      • pad-iso10126.js
      • pad-iso97971.js
      • pad-nopadding.js
      • pad-zeropadding.js
      • pbkdf2.js
      • rabbit.js
      • rabbit-legacy.js
      • rc4.js
      • ripemd160.js
      • sha1.js
      • sha3.js
      • sha224.js
      • sha256.js
      • sha384.js
      • sha512.js
      • tripledes.js
      • x64-core.js
    • rollups
      • aes.js
      • hmac-md5.js
      • hmac-ripemd160.js
      • hmac-sha1.js
      • hmac-sha3.js
      • hmac-sha224.js
      • hmac-sha256.js
      • hmac-sha384.js
      • hmac-sha512.js
      • md5.js
      • pbkdf2.js
      • rabbit.js
      • rabbit-legacy.js
      • rc4.js
      • ripemd160.js
      • sha1.js
      • sha3.js
      • sha224.js
      • sha256.js
      • sha384.js
      • sha512.js
      • tripledes.js

cryptojs/components 目录下的所有文件都会对应一个压缩文件,比如 cryptojs/components/aes.js 同时会有一个 cryptojs/components/aes-min.js 文件。

cryptojs/rollups 目录下的所有文件都已经压缩完毕,每个文件都可以单独调用。

phpDocumentor 使用介绍

开发 PHP 不像开发 Java 有较强的文档注释规范,通过注释可以便捷地生成接口文档。

我在寻找 PHP 接口文档工具时使用过 phpDoc,页面效果不怎么好。辗转使用了 phpDocumentor,页面效果可以接受。

phpDocumentor output

使用 phpDocumentor 步骤(仅在 Windows 系统验证通过):

首先安装 php5

下载 phpDocumentor.phar 文件

访问百度盘 http://pan.baidu.com/s/1bnErGXh 下载 phar 文件。

在 PHP5 目录(php.ini 所在的目录)下创建 phar 目录,并把 phpDocumentor.phar 移动到该目录。

进入 PHP5 目录,打开 cmd 命令窗口,执行 phpDocumentor 命令

php phar\phpDocumentor.phar -d D:\ran\framework -t D:\zhengxianjun_cdn\www\ran-api

-d 参数表示源代码目录

-t 参数表示生成的接口文档存放目录

命令执行完成之后会在 D:\zhengxianjun_cdn\www\ran-api\reports\errors.html 记录下源代码中接口描述不规范的点,逐条修改即可。

phpDocument 生成的接口文档的样式中模式会使用 Google 的字体,在国内自然打不开,导致网页打开很慢。找到 D:\zhengxianjun_cdn\www\ran-api\css\template.css,删除第一行的 @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro);。因为每次生成接口文档都会覆盖 template.css,另一个一劳永逸的办法是在 hosts 文件中加上 127.0.0.1 fonts.googleapis.com

phpDocument 生成的接口文档适配手机网页,但查看不是很便捷。

使用 phpDocument,就需要按照它的规范来抒写代码注释。

phpDocument 代码注释规范:http://www.phpdoc.org/docs/latest/index.html

支持 28 个标签,有些标签没有完全实现,但实际常用的标签就 10 个左右。

在注释文档中支持部分 html 标签,例如

phpDocument source code

对应的展示效果如下

phpDocumentor 效果

想要编写复杂的代码示例仍然不是很容易,但通过注释生成接口文档基本已满足我的需求。

想要细致了解 phpDocument.phar 文件的用法,可以通过 php phar\phpDocumentor.phar –help 命令获得帮助。

示例站点

PHP Ran Framework 的接口文档便是通过 phpDocumentor 创建的 http://ran-api.qiniudn.com

PHP 图片压缩、图片缩放

使用 PHP 进行图片压缩,首先通过 imagecreatefrom* 系列函数创建源图像 resource 对象,再通过 imagecreate 或者 imagecreatetruecolor 函数创建确定宽度和高度的目标图像对象,接着进行一系列处理,最后通过 imagecopy* 系列函数把源图像对象拷贝到目标图像对象。完成上述处理之后,调用 imagegif、imagejpeg、imagepng 把目标图像对象转换为图像字符串输出到文件或者浏览器。似乎没有直接把图像对象转换为字符串的函数?可以通过 ob_start 捕获字符串输出,并调用 ob_get_clean 取得字符串。

imagecreatefrom* 系列函数

imagecreatefromgd 从 GD 文件或 URL 创建图像对象
imagecreatefromgd2 从 GD2 文件或 URL 创建图像对象
imagecreatefromgif 从 gif 文件创建图像对象
imagecreatefromjpeg 从 jpeg/jpg 文件创建图像对象
imagecreatefrompng 从 png 文件创建图像对象
imagecreatefromwbmp 从 wbmp 文件创建图像对象
imagecreatefromwebp 从 webp 文件创建图像对象
imagecreatefromxbm 从 xbm 文件创建图像对象
imagecreatefromxpm 从 xpm 文件创建图像对象
上述所有函数全部接收一个参数 — $filename, 失败时均会返回 false
imagecreatefromstring 从字符串创建图像对象
接收一个参数 — 图像二进制字符串

imagecopy* 系列函数

imagecopy 拷贝图像的一部分
imagecopymerge 拷贝并合并图像的一部分
imagecopymergegray 用灰度拷贝并合并图像的一部分
imagecopyresampled 重采样拷贝部分图像并调整大小
imagecopyresized 拷贝部分图像并调整大小

图片压缩实现代码

$image = 'D:\TEMP\woxinfeishi-bukezhuanye.jpg';
$source = imagecreatefromjpeg($image);

$s_width = imagesx($source);
$s_height = imagesy($source);

$max_width = 200;
$max_height = 200;

if ($s_width > $max_width
	|| $s_height > $max_height)
{
	$s_rate = $s_width / $s_height;
	$t_rate = $max_width / $max_height;

	if ($s_rate > $t_rate)
	{
		$t_width = $max_width;
		$t_height = floor($t_width / $s_rate);
	}
	else
	{
		$t_height = $max_height;
		$t_width = floor($t_height * $s_rate);
	}

	$target = imagecreatetruecolor($t_width,
		$t_height);
	imagecopyresampled($target, $source, 0, 0, 0, 0,
		$t_width, $t_height,
		$s_width, $s_height);
}
else
{
	$target = $source;
}

ob_start();
imagejpeg($target);
$image_data = ob_get_clean(); // 图像数据
imagedestroy($source);
imagedestroy($target);

上述代码实现图片等比压缩,如果是缩放到固定高宽则只需要直接设置为最终高宽。

HTML特殊符号、命名实体、十进制编码对照表

HTML特殊符号、命名实体、十进制编码对照表

特殊符号 命名实体 十进制编码 特殊符号 命名实体 十进制编码
Α &Alpha; &#913; Β &Beta; &#914;
Γ &Gamma; &#915; Δ &Delta; &#916;
Ε &Epsilon; &#917; Ζ &Zeta; &#918;
Η &Eta; &#919; Θ &Theta; &#920;
Ι &Iota; &#921; Κ &Kappa; &#922;
Λ &Lambda; &#923; Μ &Mu; &#924;
Ν &Nu; &#925; Ξ &Xi; &#926;
Ο &Omicron; &#927; Π &Pi; &#928;
Ρ &Rho; &#929; Σ &Sigma; &#931;
Τ &Tau; &#932; Υ &Upsilon; &#933;
Φ &Phi; &#934; Χ &Chi; &#935;
Ψ &Psi; &#936; Ω &Omega; &#937;
α &alpha; &#945; β &beta; &#946;
γ &gamma; &#947; δ &delta; &#948;
ε &epsilon; &#949; ζ &zeta; &#950;
η &eta; &#951; θ &theta; &#952;
ι &iota; &#953; κ &kappa; &#954;
λ &lambda; &#955; μ &mu; &#956;
ν &nu; &#957; ξ &xi; &#958;
ο &omicron; &#959; π &pi; &#960;
ρ &rho; &#961; ς &sigmaf; &#962;
σ &sigma; &#963; τ &tau; &#964;
υ &upsilon; &#965; φ &phi; &#966;
χ &chi; &#967; ψ &psi; &#968;
ω &omega; &#969; ϑ &thetasym; &#977;
ϒ &upsih; &#978; ϖ &piv; &#982;
&bull; &#8226; &hellip; &#8230;
&prime; &#8242; &Prime; &#8243;
&oline; &#8254; &frasl; &#8260;
&weierp; &#8472; &image; &#8465;
&real; &#8476; &trade; &#8482;
&alefsym; &#8501; &larr; &#8592;
&uarr; &#8593; &rarr; &#8594;
&darr; &#8595; &harr; &#8596;
&crarr; &#8629; &lArr; &#8656;
&uArr; &#8657; &rArr; &#8658;
&dArr; &#8659; &hArr; &#8660;
&forall; &#8704; &part; &#8706;
&exist; &#8707; &empty; &#8709;
&nabla; &#8711; &isin; &#8712;
&notin; &#8713; &ni; &#8715;
&prod; &#8719; &sum; &#8722;
&minus; &#8722; &lowast; &#8727;
&radic; &#8730; &prop; &#8733;
&infin; &#8734; &ang; &#8736;
&and; &#8869; &or; &#8870;
&cap; &#8745; &cup; &#8746;
&int; &#8747; &there4; &#8756;
&sim; &#8764; &cong; &#8773;
&asymp; &#8773; &ne; &#8800;
&equiv; &#8801; &le; &#8804;
&ge; &#8805; &sub; &#8834;
&sup; &#8835; &nsub; &#8836;
&sube; &#8838; &supe; &#8839;
&oplus; &#8853; &otimes; &#8855;
&perp; &#8869; &sdot; &#8901;
&lceil; &#8968; &rceil; &#8969;
&lfloor; &#8970; &rfloor; &#8971;
&loz; &#9674; &spades; &#9824;
&clubs; &#9827; &hearts; &#9829;
&diams; &#9830; &nbsp; &#160;
¡ &iexcl; &#161; ¢ &cent; &#162;
£ &pound; &#163; ¤ &curren; &#164;
¥ &yen; &#165; ¦ &brvbar; &#166;
§ &sect; &#167; ¨ &uml; &#168;
© &copy; &#169; ª &ordf; &#170;
« &laquo; &#171; ¬ &not; &#172;
­ &shy; &#173; ® &reg; &#174;
¯ &macr; &#175; ° &deg; d&#176;
± &plusmn; &#177; ² &sup2; &#178;
³ &sup3; &#179; ´ &acute; &#180;
µ &micro; &#181;

解析中文域名并配置 Nginx 服务器

部署中文域名站点,下面讲诉设置中文域名站点——“郝越.我爱你”。

设置域名解析

第一步是在设置域名 A 记录,使主机记录中的 @ 和 www 都指向一个 ip。

域名解析

上图为万网域名解析后台解析设置

设置 Nginx server_name

第二步是修改 nginx/conf/nginx.conf 或者其他 vhost 文件,在 server_name 这一行调整为经过 idn_to_ascii 函数编码的中文域名,而不是原始的中文域名。

server {
		listen       80;
		server_name  xn--vq3al9d.xn--6qq986b3xl;
		index index.php;
		root /alidata/www/love;
}

“郝越.我爱你”经过编码之后是“xn--vq3al9d.xn--6qq986b3xl”。

在百度搜索“中文域名编码”,进入在线编码的网站,提交中文域名,即可将中文域名转为 punycode 或者是 GBK 编码。在 nginx 配置文件中使用 punycode 即可。

设置完成之后 reload nginx,即可访问中文域名。

访问中文域名

在浏览器地址栏看到的虽然是中文域名,但实际的链接是 http://xn--vq3al9d.xn--6qq986b3xl 。通过 JavaScript document.domain 获取到的域名也是 “xn--vq3al9d.xn--6qq986b3xl”,两者其实是同一个,通过任何一个地址都能访问到服务器。

扩展:使用 PHP 进行中文域名编码

使用 PHP 进行中文域名编码

国际化域名。

比如中文域名,在浏览器地址栏以中文显示,但实际是以 punycode 进行解析和访问。服务器设置 server_name 时不能直接设置中文域名,而是设置中文域名对应的 punycode。

PHP 提供的 IDN 函数可以把非英文域名转换为 punycode,详见 PHP IDN Functions

idn_to_ascii 将域名转换为 IDNA ASCII 编码
idn_to_unicode idn_to_utf8 别名
idn_to_utf8 将域名 从 ASCII 编码转换为 Unicode 编码

代码示例

$xn_idn = idn_to_ascii('郝越.我爱你');
echo $xn_idn; // xn--vq3al9d.xn--6qq986b3xl

$idn = idn_to_utf8($xn_idn);
echo $idn; // 郝越.我爱你

使用 IDN 函数之前需要先安装 php5-intl 扩展:

sudo apt-get install php5-intl

PHP Socket 实现 TCP、UDP 报文的发送与接收

利用 PHP Socket 相关函数实现 TCP、UDP 端口监听。

需要注意,下面的示例代码中没有处理 Socket 错误。实际应用场景中每一步 Socket 的连接、写入、读取都需要进行错误判断和处理,相应的函数 socket_connect、socket_write、socket_read 以及 socket_bind、socket_listen 返回 false 时,需要调用 socket_last_error() 获取最新的 socket 错误号 $errno,并通过 socket_strerror($errno) 获取错误号对应的能够阅读的错误描述信息。

PHP Socket TCP 发送数据示例

$host = '127.0.0.1';
$port = '81';
$message = 'Hello TCP Server';

function send_tcp_message($host, $port, $message)
{
	$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
	@socket_connect($socket, $host, $port);

	$num = 0;
	$length = strlen($message);
	do
	{
		$buffer = substr($message, $num);
		$ret = @socket_write($socket, $buffer);
		$num += $ret;
	} while ($num < $length);

	$ret = '';
	do
	{
		$buffer = @socket_read($socket, 1024, PHP_BINARY_READ);
		$ret .= $buffer;
	} while (strlen($buffer) == 1024);

	socket_close($socket);

	return $ret;
}

$ret = send_tcp_message($host, $port, $message);

PHP Socket TCP 接收数据示例

创建一个 Server 接收 TCP 连接,需要先监听一个端口。

$host = '127.0.0.1';
$port = '81';
$callback = 'echo';

function receive_tcp_message($host, $port, $callback)
{
	$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

	// socket_bind() 的参数 $host 必传, 由于是监听本机, 此处可以固定写本机地址
	// 注意: 监听本机地址和内网地址效果不一样
	@socket_bind($socket, $host, $port);
	@set_time_limit(0);

	// 绑定端口之后调用监听函数, 实现端口监听
	@socket_listen($socket, 5);

	// 接下来只需要一直读取, 检查是否有来源连接即可, 如果有, 则会得到一个新的 socket 资源
	while ($child = @socket_accept($socket))
	{
		// 休息 1 ms, 也可以不用休息
		usleep(1000);

		if (false === socket_getpeername($child, $remote_host, $remote_port))
		{
			@socket_close($child);
			continue;
		}

		// 读取请求数据
		// 例如是 http 报文, 则解析 http 报文
		$request = '';
		do
		{
			$buffer = @socket_read($child, 1024, PHP_BINARY_READ);
			if (false === $buffer)
			{
				@socket_close($child);
				continue 2;
			}
			$request .= $buffer;
		} while (strlen($buffer) == 1024);

		// 此处省略如何调用 $callback
		$response = $callback($remote_host, $remote_port, $request);

		if (!strlen($response))
		{
			// 至少返回含有一个空格的字符串
			$response = ' ';
		}

		// 因为是 TCP 链接, 需要返回给客户端处理数据
		$num = 0;
		$length = strlen($response);
		do
		{
			$buffer = substr($response, $num);
			$ret = @socket_write($child, $buffer);
			$num += $ret;
		} while ($num < $length);

		// 关闭 socket 资源, 继续循环
		@socket_close($child);
	}
}

// 客户端来的任何请求都会打印到屏幕上
receive_tcp_message($host, $port, $callback);
// 如果程序没有出现异常,该进程会一直存在

有一个快捷的函数 socket_create_listen($port),创建、绑定、监听一步到位。

PHP Socket UDP 发送数据示例

$host = '127.0.0.1';
$port = '82';
$message = 'Hello UDP Server';

function send_udp_message($host, $port, $message)
{
	$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
	@socket_connect($socket, $host, $port);

	$num = 0;
	$length = strlen($message);
	do
	{
		$buffer = substr($message, $num);
		$ret = @socket_write($socket, $buffer);
		$num += $ret;
	} while ($num < $length);

	socket_close($socket);

	// UDP 是一种无链接的传输层协议, 不需要也无法获取返回消息
	return true;
}

send_udp_message($host, $port, $message);

PHP Socket UDP 接收数据示例

$host = '127.0.0.1';
$port = '82';
$callback = 'echo';

function receive_udp_message($host, $port, $callback)
{
	$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

	@socket_bind($socket, $host, $port);
	@set_time_limit(0);

	while (true)
	{
		usleep(1000);

		$ret = @socket_recvfrom($socket, $request, 16384, 0, $remote_host, $remote_port);
		if ($ret)
		{
			$callback($remote_host, $remote_port, $request);
		}

		// 不需要返回给客户端任何消息, 继续循环
	}
}

// 客户端来的任何请求都会打印到屏幕上
receive_udp_message($host, $port, $callback);
// 如果程序没有出现异常,该进程会一直存在

PHP Socket 相关函数

  1. socket_accept — Accepts a connection on a socket
  2. socket_bind — Binds a name to a socket
  3. socket_clear_error — Clears the error on the socket or the last error code
  4. socket_close — Closes a socket resource
  5. socket_cmsg_space — Calculate message buffer size
  6. socket_connect — Initiates a connection on a socket
  7. socket_create_listen — Opens a socket on port to accept connections
  8. socket_create_pair — Creates a pair of indistinguishable sockets and stores them in an array
  9. socket_create — Create a socket (endpoint for communication)
  10. socket_get_option — Gets socket options for the socket
  11. socket_getpeername — Queries the remote side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type
  12. socket_getsockname — Queries the local side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type
  13. socket_import_stream — Import a stream
  14. socket_last_error — Returns the last error on the socket
  15. socket_listen — Listens for a connection on a socket
  16. socket_read — Reads a maximum of length bytes from a socket
  17. socket_recv — Receives data from a connected socket
  18. socket_recvfrom — Receives data from a socket whether or not it is connection-oriented
  19. socket_recvmsg — Read a message
  20. socket_select — Runs the select() system call on the given arrays of sockets with a specified timeout
  21. socket_send — Sends data to a connected socket
  22. socket_sendmsg — Send a message
  23. socket_sendto — Sends a message to a socket, whether it is connected or not
  24. socket_set_block — Sets blocking mode on a socket resource
  25. socket_set_nonblock — Sets nonblocking mode for file descriptor fd
  26. socket_set_option — Sets socket options for the socket
  27. socket_shutdown — Shuts down a socket for receiving, sending, or both
  28. socket_strerror — Return a string describing a socket error
  29. socket_write — Write to a socket

JavaScript Math

JavaScript Math 是 JavaScript 内置(built-in)的一个对象,包含常用的数学常量及数学方法。

JavaScript Math

Math 对象详细介绍 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math

Math 有8个常量,接近18个数学方法,并有多个新增方法。

Math 常量

Math.E 欧拉常数 2.718281828459045
Math.LN2 2的自然对数 0.6931471805599453
Math.LN10 10的自然对数 2.302585092994046
Math.LOG2E 以10为底E的对数 1.4426950408889634
Math.LOG10E 以2为底E的对数 0.4342944819032518
Math.PI 圆周率 3.141592653589793
Math.SQRT1_2 1/2的平方根 0.7071067811865476
Math.SQRT2 2的平方根 1.4142135623730951

Math 方法

Math.abs(x) 获取 x 的绝对值
Math.acos(x) 获取 x 的反余弦值
Math.acosh(x) 获取 x 的反双曲余弦值 实验方法
Math.asin(x) 获取 x 的反正弦值
Math.asinh(x) 获取 x 的反双曲正弦值 实验方法
Math.atan(x) 以介于 -PI/2 与 PI/2 弧度之间的数值来获取 x 的反正切值
Math.atanh(x) 获取 x 的反双曲正切值 实验方法
Math.atan2(x, y) 获取 y/x 的反正切值
Math.cbrt(x) 获取 x 的立方根 实验方法
Math.ceil(x) 获取大于等于 x 的最小整数
Math.cos(x) 获取 x 的余弦值
Math.cosh(x) 获取 x 的双曲余弦值 实验方法
Math.exp(x) 获取 Ex, Math.E 的 x 次幂
Math.expm1(x) 获取 Math.exp(x)-1 的值 实验方法
Math.floor(x) 获取小于等于 x 的最大整数
Math.fround(x) 获取与 x 最相近的单精度浮点数 实验方法
Math.hypot([x[,y[,…]]]) 获取所有参数的平方和的平方根, 统计 实验方法
Math.imul(x) 获取一个32位整数的乘积 ? 实验方法
Math.log(x) 获取以 Math.E 为底数, x 为指数的对数, 自然对数
Math.log1p(x) 获取 1 + x 的自然对数 实验方法
Math.log10(x) 获取以 10 为底数, x 为指数的对数 实验方法
Math.log2(x) 获取以 2 为底数, x 为指数的对数 实验方法
Math.max([x[,y[,…]]]) 获取所有参数中的最大值
Math.min([x[,y[,…]]]) 获取所有参数中的最小值
Math.pow(x,y) 获取 x 的 y 次幂
Math.random() 获取 0 到 1 之间的伪随机数
Math.round(x) 获取 x 四舍五入后的整数
Math.sign(x) 获取 x 的标记, 判定 x 是正数, 负数还是 0 -1, 0, 1
Math.sin(x) 获取 x 的正弦值
Math.sinh(x) 获取 x 的双曲正弦值 实验方法
Math.sqrt(x) 获取 x 的平方根
Math.tan(x) 获取 x 的正切值
Math.trunc(x) 获取 x 的整数部分,去除小数 实验方法

从 Math 常量可以看出 JavaScript 浮点数最多表示多少位。下面这段代码比较有趣:

1.1 + 2.2 // == 3.3 ?
// 得到结果 3.3000000000000003
Math.fround(1.1 + 2.2) == 3.299999952316284
// ? 得到最接近的浮点数

Linux安装SVN

使用 SVN 进行网站的发布工具。

在 Linux 上安装 SVN 步骤:

安装 SVN

# yum install subversion

创建 SVN 仓库

# cd /opt
# mkdir svn
# svnadmin create /opt/svn/web

通过上述命令在服务器 /opt/svn 目录下创建了一个仓库 web。接下来需要修改仓库的配置文件。

配置 SVN 仓库

# cd /opt/svn/web
# ls
conf db format hooks locks README.txt
# ls
authz passwd svnserve.conf

conf 目录下存放 SVN 仓库相关配置文件。

1、修改 authz 文件
authz 是 SVN 认证文件,vim 打开

[groups]
admin = svn_admin
[/]
@admin = rw

在 [groups] 下增加 admin,在文件末尾增加 [/],换行增加 @admin=rw。@ 紧跟着用户组,等号右侧是读写权限。

2、修改 passwd 文件

passwd 是 SVN 账号密码文件,vim 打开

[users]
svn_admin = svn_password

在 [users] 下增加 svn_admin,等号右侧是该账号的密码

3、修改 svnserve.conf 文件

svnserve.conf 是该 SVN 仓库运行配置,vim 打开

[general]
anon-access = read
auth-access = write
authz-db = /opt/svn/web/conf/authz
password-db = /opt/svn/web/conf/passwd

在 [general] 下增加访问控制和 authz-db、password-db 文件路径。

启动 SVN 仓库

# svnserve -d -r /opt/svn/web/

启动 SVN 仓库之后,SVN server 即创建完成。

检查仓库运行

# netstat -anp | grep svn
tcp 0 0 0.0.0.0:3690 0.0.0.0:* LISTEN 15019/svnserve

检查端口可以发现 SVN serve 已经运行在端口 3690。

通过 telnet 127.0.0.1 3690 连接该端口可以收到类似这样的信息 ( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops partial-replay ) ) )。

通过 curl http://127.0.0.1:3690 可以收到类似这样的信息 ( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops partial-replay ) ) ) 。

checkout 仓库

通过 SVN TortoiseSVN 客户端 checkout 仓库 svn://127.0.0.1 验证账户并登录就可以正常使用 SVN 了。进一步可以了解如何使用 https 访问 SVN 服务。

后续在 Linux 上部署 SVN 更新脚本即可实现 SVN 管理站点发布上线。

开机启动 SVN 服务

一般来说,Linux 服务器重启情况比较少。但为了方便,可以 SVN 服务添加到开机启动项中。

在文件 /etc/rc.local 中增加一行 /usr/bin/svnserve -d -r /opt/svn/web/

不使用 /etc/init.d/svnserver 启动 SVN 服务是因为该命令默认启动时没有携带 -r 参数,即默认没有指定仓库路径。

使用七牛CDN对博客静态资源进行加速

WordPress 博客程序默认静态资源全部存放在 wp-content、wp-includes 目录下,包括主题样式和各种插件样式。

使用七牛CDN对博客静态资源进行加速步骤:

第一步、同步所有静态资源到七牛空间

第二步、替换静态资源域名

if (!is_admin())
{
	function qiniu_ob_start()
	{
		ob_start('qiniu_cdn_replace');
	}

	function qiniu_cdn_replace($html)
	{
		if ($_SERVER['SERVER_PORT'] == '443' || $_SERVER['HTTPS'] == 'on')
		return  str_replace('blog.zhengxianjun.com/wp-content', 'dn-zhengxianjun.qbox.me/wp-content',
				str_replace('blog.zhengxianjun.com/wp-includes', 'dn-zhengxianjun.qbox.me/wp-includes', $html));

		return  str_replace('blog.zhengxianjun.com/wp-content', 'zhengxianjun.qiniudn.com/wp-content',
				str_replace('blog.zhengxianjun.com/wp-includes', 'zhengxianjun.qiniudn.com/wp-includes', $html));
	}

	add_action('wp_loaded', 'qiniu_ob_start');
}

上述代码需要添加到 wp-content/themes/twentyfifteen/functions.php。其中,twentyfifteen 是当前博客使用的主题。

实现效果

访问博客首页

博客首页

查看网络资源

网络资源

可以看到博客已经使用了CDN对静态资源加速。

以后每次更新静态资源时需要同时更新到七牛空间。

为了简化静态资源同步,使用脚本 wp-static-sync.php 同步静态资源到本地七牛空间目录,七牛的 QRSBox 会自动更新目录下的所有文件。

<?php
// 文件 wp-static-sync.php
if (!isset($_SERVER['argv']))
{
	return;
}

function copy_static($static_source, $static_target)
{
	if (!is_dir($static_source))
	{
		echo "static_source : <$static_source> is not a dir\n";
		return false;
	}

	if (!is_dir($static_target))
	{
		if (false === mkdir($static_target))
		{
			echo "mkdir ERROR target <$static_target>\n";
			return false;
		}
		else
		{
			echo "mkdir Ok target <$static_target>\n";
		}
	}

	$handler_source = opendir($static_source);
	$handler_target = opendir($static_target);

	$reg_static = '/\.css$|\.js$|\.gif$|\.jpg$|\.png$/i';

	while ($name = readdir($handler_source))
	{
		if ($name == '.' || $name == '..') continue;

		$file_source = $static_source . '/' . $name;

		if (is_file($file_source))
		{
			if (preg_match($reg_static, $name))
			{
				$file_target = $static_target . '/' . $name;
				if (false === copy($file_source, $file_target))
				{
					echo "copy ERROR <$file_source> to <$file_target>\n";
				}
				else
				{
					echo "copy Ok <$file_source> to <$file_target>\n";
				}
			}
		}
		else if (is_dir($file_source))
		{
			$file_target = $static_target . '/' . $name;
			if (false === copy_static($file_source, $file_target))
			{
				return false;
			}
			// 删除空目录
			if (count(scandir($file_target)) == 2)
			{
				if (false === rmdir($file_target))
				{
					echo "rmdir ERROR empty  <$file_target>\n";
				}
				else
				{
					echo "rmdir Ok empty  <$file_target>\n";
				}
			}
		}
	}
}

$static_source = 'D:\work\blog';
$static_target = 'D:\work\zhengxianjun_cdn\www\static';

copy_static($static_source . '/wp-content', $static_target . '/wp-content');
copy_static($static_source . '/wp-includes', $static_target . '/wp-includes');

至此,虽然流程上略长,但基本实现了想要的功能。

另一种方法,是使用 wpjam-qiniu 这款插件,该插件可以绑定到七牛空间。因为我在使用过程中出现了一些问题,之前发布的文章中图片地址出现了错误,所以使用上面所述的方法实现。