分类目录归档:PHP

PHP源码,PHP扩展,PHP程序

序列化和json

序列化和json

【序列化的概念】

序列化是将对象状态转换为可保持或可传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

将对象的状态信息转换为可以存储或传输的窗体的过程。 在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

通常,对象实例的所有字段都会被序列化,这意味着数据会被表示为实例的序列化数据。这样,能够解释该格式的代码有可能能够确定这些数据的值,而不依赖于该成员的可访问性。类似地,反序列化从序列化的表示形式中提取数据,并直接设置对象状态,这也与可访问性规则无关。 对于任何可能包含重要的安全性数据的对象,如果可能,应该使该对象不可序列化。如果它必须为可序列化的,请尝试生成特定字段来保存不可序列化的重要数据。如果无法实现这一点,则应注意该数据会被公开给任何拥有序列化权限的代码,并确保不让任何恶意代码获得该权限。

【JSON的概念】

JSON,JavaScript Object Notation,一种更轻、更友好的用于接口(AJAX、REST等)数据交换的格式。 JSON是结构化数据串行化的文本格式,作为XML的一种替代品,用于表示客户端与服务器间数据交换有效负载的格式。它是从ECMAScript语言标准衍生而来的。JSON的设计目标是使它成为小的、轻便的、文本的,而且是JavaScript的一个子集。

【长度的比较】

如下一段代码,显示了对数组和对象编码后生成的字符串及其长度

class Foo {

    public $int = 1;
    public $bool = TRUE;
    public $array = array(array(1), 2 => 'test', 'string');

    public function test($flag) {
        echo $flag, 'test function for Foo <br />';
    }

    public static function output($str) {
        echo $str, '<br />';
    }

    public static function compare_serialize_and_json($data) {
        $serialize_str =  serialize($data);
        self::output('序列化后的值:' . $serialize_str . "; length=" .
            strlen($serialize_str));

        $json_str = json_encode($data);
        self::output('JSON后的值:' . $json_str . "; length=" . strlen($json_str));
    }

}

$test_data = array('wwww' => 0, 'phppan' => 1, 'com' => 2);
//序列化数组

echo '数组:<br />';
Foo::compare_serialize_and_json($test_data);

$foo = new Foo();
echo '对象:<br />';
Foo::compare_serialize_and_json($foo);

输出:

数组:
序列化后的值:a:3:{s:4:"wwww";i:0;s:6:"phppan";i:1;s:3:"com";i:2;}; length=52
JSON后的值:{"wwww":0,"phppan":1,"com":2}; length=29
对象:
序列化后的值:O:3:"Foo":3:{s:3:"int";i:1;s:4:"bool";b:1;s:5:"array";a:3:{i:0;
    a:1:{i:0;i:1;}i:2;s:4:"test";i:3;s:6:"string";}}; length=111
JSON后的值:{"int":1,"bool":true,"array":{"0":[1],"2":"test","3":"string"}}; length=63

很明显的长度区别,serialize在编码后大概是json的两倍

原因:

  • serialize后字符串包含了子串的长度,这可能是速度方面的优化,典型的空间换时间,但是它本身还是太重了。
  • serialize有更加详细的类型区分,而json只有四种类型,并且是以简单的符号表示。

【速度的比较】

以代码说明问题,如下比较速度的代码:

$max_index = 10;
ini_set("memory_limit","512M");
$array = array_fill(0, 1000000, rand(1, 9999));

echo 'serialize:<br />';
$start = xdebug_time_index();
for ($i = 0;  $i < $max_index; $i++) {
    $str = serialize($array);
}
$end = xdebug_time_index();
echo $end - $start, '<br />';

echo 'json:<br />';
$start = xdebug_time_index();
for ($i = 0;  $i < $max_index; $i++) {
    $str = json_encode($array);
}
$end = xdebug_time_index();
echo $end - $start, '<br />';
unset($array, $str);

输出:

serialize:
9.5371007919312
json:
1.4313209056854

serialize的速度在大数据量的情况下比json差了快一个数量级。

从上面两点看,json不管是在速度还是在生成的字符串的大小上都比serialize要好,那为什么serialize还要存在呢? 原因在下面这个点:实现的功能。

【处理对象】

如下代码:

header("Content-type:text/html;charset=utf8");
class Foo {
     public function test($flag) {
        echo $flag, 'test function for Foo <br />';
    }
}

$foo = new Foo();

echo '反序列化测试:<br />';
$foo->test(1);
$serialize_str = serialize($foo);
$obj = unserialize($serialize_str);
$obj->test(2);

$foo->test(1);
$json_str = json_encode($foo);
$obj = json_decode($json_str);
$obj->test(2);
die();

输出:

反序列化测试:
1test function for Foo
2test function for Foo
1test function for Foo 

( ! ) Fatal error: Call to undefined method stdClass::test()

json无法处理对象方法等数据。

【使用范围】

  • 序列化使用serialize,特别是对象的存储。这是其存在的意义。
  • 与对象无关的数据存储可以使用json,如包含大量数字的数组等。只是当遇到这种情况,我们需要做的可能是重构数据库了。
  • 数据交换时使用JSON,这也是其定义所在。
  • 目前JSON是能用于UTF-8编码的数据。

PHP的Socket编程

PHP的Socket编程

计算机进程可以使用socket和其他进程通信,通过socket,其他进程的位置是透明的。这些进程可以在同一台计算机上也可以在不同的计算机上。

在PHP中,socket是以扩展的方式加载的,如果无法使用socket相关函数,请确认是否有打开此扩展。
下面我们以一个面向连接的客户端和服务器的简单实现说明一些函数的使用,在此之后,简单介绍在PHP的内部是如何实现这些函数的。

【客户端实现】
如下所示代码为客户端的实现代码:

set_time_limit(0);
 
$host = "127.0.0.1";
$port = 2046;
 
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)or die("Could not create	socket\n"); // 创建一个Socket
 
$connection = socket_connect($socket, $host, $port) or die("Could not connet server\n");    //  连接
 
socket_write($socket, "time") or die("Write failed\n"); // 数据传送 向服务器发送消息
 
while ($buffer = socket_read($socket, 1024, PHP_NORMAL_READ)) {
    echo("Data sent was: time\nResponse was:" . $buffer . "\n");
}
 
socket_close($socket);

客户端首先创建一个socket,并且连接服务器。向服务器发送一个time的消息,等待服务器返回信息,读取服务器的信息并输出。

【服务器实现】

 
set_time_limit(0);
 
$host = "127.0.0.1";
$port = 2046;
 
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Could not create	socket\n"); // 创建一个Socket
 
$result = socket_bind($socket, $host, $port) or die("Could not bind tosocket\n"); //绑定Socket到端口
 
$result = socket_listen($socket, 3) or die("Could not set up socket listener\n"); // 开始监听连接
 
$spawn = socket_accept($socket) or die("Could not accept incoming connection\n"); // 处理通信
 
$input = socket_read($spawn, 1024) or die("Could not read input\n"); // 数据传送 获得客户端的输入
 
$input = trim($input);
echo 'input:', $input, "\n";
 
if ($input == 'time') {
    $output = date("Y-m-d H:i:s"). "\n"; //处理客户端输入并返回结果
}else{
    $output = "input error \n"; //处理客户端输入并返回结果
}
 
echo "output:", $output, "\n";
 
//	数据传送 向客户端写入返回结果
socket_write($spawn, $output, strlen($output)) or die("Could not write output\n");	
 
// 关闭sockets
socket_close($spawn);
socket_close($socket);

服务器端创建一个socket,并且绑定端口,监听连接,读取客户端的数据,根据客户端的输入返回不同的值,最后写入数据到客户端。关闭socket。
只是这个服务器不能接受多个连接并且只完成一个操作
【PHP内部源码说明】
从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。
下面我们以socket_create的源码实现来说明PHP的内部实现。
前面我们有说到php的socket是以扩展的方式实现的。在源码的ext目录,我们找到sockets目录。这个目录存放了PHP对于socket的实现。直接搜索PHP_FUNCTION(socket_create),在sockets.c文件中找到了此函数的实现。如下所示代码:

/* {{{ proto resource socket_create(int domain, int type, int protocol) U
   Creates an endpoint for communication in the domain specified by domain, of type specified by type */
PHP_FUNCTION(socket_create)
{
        long            arg1, arg2, arg3;
        php_socket      *php_sock = (php_socket*)emalloc(sizeof(php_socket));
 
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) {
                efree(php_sock);
                return;
        }
 
        if (arg1 != AF_UNIX
#if HAVE_IPV6
                && arg1 != AF_INET6
#endif
                && arg1 != AF_INET) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket domain [%ld] specified for argument 1, assuming AF_INET", arg1);
                arg1 = AF_INET;
        }
 
        if (arg2 > 10) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket type [%ld] specified for argument 2, assuming SOCK_STREAM", arg2);
                arg2 = SOCK_STREAM;
        }
 
        php_sock->bsd_socket = socket(arg1, arg2, arg3);
        php_sock->type = arg1;
 
        if (IS_INVALID_SOCKET(php_sock)) {
                SOCKETS_G(last_error) = errno;
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC));
                efree(php_sock);
                RETURN_FALSE;
        }
 
        php_sock->error = 0;
        php_sock->blocking = 1;
                                                                                                                                           1257,1-8      61%
        ZEND_REGISTER_RESOURCE(return_value, php_sock, le_socket);
}
/* }}} */

从整个函数的实现看,程序基本上是一个错误和异常处理。PHP本身引入了php_socket结构体,其创建时调用socket函数实现。

PS:关于PHP的网络编程,我们会在TIPI系列文章中作详细的说明。
【socket函数】
函数名 描述
socket_accept() 接受一个Socket连接
socket_bind() 把socket绑定在一个IP地址和端口上
socket_clear_error() 清除socket的错误或最后的错误代码
socket_close() 关闭一个socket资源
socket_connect() 开始一个socket连接
socket_create_listen() 在指定端口打开一个socket监听
socket_create_pair() 产生一对没有差别的socket到一个数组里
socket_create() 产生一个socket,相当于产生一个socket的数据结构
socket_get_option() 获取socket选项
socket_getpeername() 获取远程类似主机的ip地址
socket_getsockname() 获取本地socket的ip地址
socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc() 这个函数创建一个能够发送接收读写的iovec数据结构
socket_iovec_delete() 删除一个已分配的iovec
socket_iovec_fetch() 返回指定的iovec资源的数据
socket_iovec_free() 释放一个iovec资源
socket_iovec_set() 设置iovec的数据新值
socket_last_error() 获取当前socket的最后错误代码
socket_listen() 监听由指定socket的所有连接
socket_read() 读取指定长度的数据
socket_readv() 读取从分散/聚合数组过来的数据
socket_recv() 从socket里结束数据到缓存
socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg() 从iovec里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的socket
socket_sendmsg() 发送消息到socket
socket_sendto() 发送消息到指定地址的socket
socket_set_block() 在socket里设置为块模式
socket_set_nonblock() socket里设置为非块模式
socket_set_option() 设置socket选项
socket_shutdown() 这个函数允许你关闭读、写、或指定的socket
socket_strerror() 返回指定错误号的周详错误
socket_write() 写数据到socket缓存
socket_writev() 写数据到分散/聚合数组

PHP安全模式下的exec执行问题

PHP安全模式下的exec执行问题

今天同事遇到一个问题,在执行某个程序的时,将执行的命令写错了,发现程序依然可以执行,可是当把程序在终端运行时又显示此文件不存在。 于是最近一直沉迷于源码的我追踪了整个执行过程,得到如下答案。

以上的问题出现在安全模式开启的情况下。

按照PHP的源码结构,exec函数的实现应该在/ext/standard目录下,在此目录找到exec.c文件,exec函数的实现就在这里。如果不熟悉源码结构,可以考虑使用editplus全局搜索PHP_FUNCTION(exec),当然,你也可以使用其它的工具。这只是一个查找的方式。重点是接下来我们要看的代码。

整个调用顺序如下:

[PHP_FUNCTION(exec) -> php_exec_ex -> php_exec]

/* {{{ php_exec
 * If type==0, only last line of output is returned (exec)
 * If type==1, all lines will be printed and last lined returned (system)
 * If type==2, all lines will be saved to given array (exec with &$array)
 * If type==3, output will be printed binary, no lines will be saved or returned (passthru)
 *
 */
PHPAPI int php_exec(int type, char *cmd, zval *array, zval *return_value TSRMLS_DC)
{
    ... //  省略
    if (PG(safe_mode)) {    //  安全模式
        if ((c = strchr(cmd, ' '))) {
            *c = '\0';
            c++;
        }
        if (strstr(cmd, "..")) {    //  不能在指向程序的路径中包含 .. 成分
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "No '..' components allowed in path");
            goto err;
        }

        b = strrchr(cmd, PHP_DIR_SEPARATOR);    //  #define PHP_DIR_SEPARATOR '/'

         ... //  省略

        spprintf(&d, 0, "%s%s%s%s%s", PG(safe_mode_exec_dir), (b ? "" : "/"), (b ? b : cmd), (c ? " " : ""), (c ? c : ""));
        if (c) {
            *(c - 1) = ' ';
        }
        cmd_p = php_escape_shell_cmd(d);
        efree(d);
        d = cmd_p;
    } else {
        cmd_p = cmd;
    }
    ...//省略
}
/* }}} */

从上面的代码可以看出,PHP在实现exec函数时,安全模式下,会去掉命令中包含的所有路径,只留下需要执行的命令及其参数。 最后将这个命令与PG(safe_mode_exec_dir)连接起来作为需要执行的命令返回 。这也就是我们在安全模式下,对于需要执行的命令,即使路径是错的也可以正常执行的原因。