使用 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

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