标签: 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
  • 使用七牛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', 's.fiaox.com/wp-content',
    				str_replace('blog.zhengxianjun.com/wp-includes', 's.fiaox.com/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 这款插件,该插件可以绑定到七牛空间。因为我在使用过程中出现了一些问题,之前发布的文章中图片地址出现了错误,所以使用上面所述的方法实现。