标签归档:PHP扩展

PHP源码阅读笔记三十五:PHP中的SESSION实现之多种存储方式

PHP源码阅读笔记三十五:PHP中的SESSION实现之多种存储方式
源码版本:php5.3.1
环境:VS2008

在php.ini中,可以看到配置项session.save_handler = files
默认情况下,php.ini 中设置的 SESSION 保存方式是 files(session.save_handler = files),即使用读写文件的方式保存 SESSION 数据,而 SESSION 文件保存的目录由 session.save_path 指定,文件名以 sess_ 为前缀,后跟 SESSION ID,如:sess_sf26st2tfamqbarvnkfo2aftf2。文件中的数据即是序列化之后的 SESSION 数据了。如果访问量大,可能产生的 SESSION 文件会比较多,这时可以设置分级目录进行 SESSION 文件的保存,效率会提高很多,设置方法为:session.save_path=”N;/save_path”,N 为分级的级数,save_path 为开始目录。当写入 SESSION 数据的时候,PHP 会获取到客户端的 SESSION_ID,然后根据这个 SESSION ID 到指定的 SESSION 文件保存目录中找到相应的 SESSION 文件,不存在则创建之,最后将数据序列化之后写入文件。读取 SESSION 数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 SESSION 变量。如果需要对session文件设置mode,设置方法为:session.save_path = “N;MODE;/path”

同样我们可以使用session_set_save_handler函数设置session的处理方法。
bool session_set_save_handler ( callback $open , callback $close , callback $read , callback $write , callback $destroy , callback $gc )
那么此时可能需要思考一些问题,对于save_handler的设置是如何实现的?在内部存在怎样的结构存储这些处理方法?不同的存储方式之间是如何替换的?如果以一个扩展的方法添加,则需要实现哪些方法?这些方法在哪里添加到系统中的?

【结构】
/ext/session/php_session.h文件

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#define PS_OPEN_ARGS void **mod_data, const char *save_path, const char *session_name TSRMLS_DC
#define PS_CLOSE_ARGS void **mod_data TSRMLS_DC
#define PS_READ_ARGS void **mod_data, const char *key, char **val, int *vallen TSRMLS_DC
#define PS_WRITE_ARGS void **mod_data, const char *key, const char *val, const int vallen TSRMLS_DC
#define PS_DESTROY_ARGS void **mod_data, const char *key TSRMLS_DC
#define PS_GC_ARGS void **mod_data, int maxlifetime, int *nrdels TSRMLS_DC
#define PS_CREATE_SID_ARGS void **mod_data, int *newlen TSRMLS_DC
 
/* default create id function */
PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS);
 
typedef struct ps_module_struct {
	const char *s_name;	// session存储方式的名字 如默认的files
	int (*s_open)(PS_OPEN_ARGS);
	int (*s_close)(PS_CLOSE_ARGS);
	int (*s_read)(PS_READ_ARGS);
	int (*s_write)(PS_WRITE_ARGS);
	int (*s_destroy)(PS_DESTROY_ARGS);
	int (*s_gc)(PS_GC_ARGS);
	char *(*s_create_sid)(PS_CREATE_SID_ARGS);
} ps_module;

第43到52行 定义ps_module,用于存放整个php_session结构,通过方法名应该可以很容易的识别出其作用意图。
【以扩展方式支持session】
如果一个扩展模块需要支持session,则在其PHP_MINIT_FUNCTION方法中调用php_session_register_module方法。
如在/ext/sqlite/sqlite.c 1395行,其调用了php_session_register_module(ps_sqlite_ptr);,说明在此扩展模块初始化时已经将其作为一个session的存储方式加载到内存中了。并且在此扩展中有sess_sqlite.c文件专门处理关于session中的应该定义的各方法,其分别以宏PS_OPEN_FUNC、PS_CLOSE_FUNC、PS_READ_FUNC、PS_WRITE_FUNC、PS_DESTROY_FUNC、PS_GC_FUNC、PS_CREATE_SID_FUNC等给出。这些宏的定义如下:

57
58
59
60
61
62
63
#define PS_OPEN_FUNC(x) 	int ps_open_##x(PS_OPEN_ARGS)
#define PS_CLOSE_FUNC(x) 	int ps_close_##x(PS_CLOSE_ARGS)
#define PS_READ_FUNC(x) 	int ps_read_##x(PS_READ_ARGS)
#define PS_WRITE_FUNC(x) 	int ps_write_##x(PS_WRITE_ARGS)
#define PS_DESTROY_FUNC(x) 	int ps_delete_##x(PS_DESTROY_ARGS)
#define PS_GC_FUNC(x) 		int ps_gc_##x(PS_GC_ARGS)
#define PS_CREATE_SID_FUNC(x)	char *ps_create_sid_##x(PS_CREATE_SID_ARGS)

php_session_register_module方法的实现在/ext/session/session.c 1021行开始。如下:

1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
#define MAX_MODULES 10
#define PREDEFINED_MODULES 2
 
static ps_module *ps_modules[MAX_MODULES + 1] = {
	ps_files_ptr,
	ps_user_ptr
};
 
PHPAPI int php_session_register_module(ps_module *ptr) /* {{{ */
{
	int ret = -1;
	int i;
 
	for (i = 0; i < MAX_MODULES; i++) {
		if (!ps_modules[i]) {
			ps_modules[i] = ptr;
			ret = 0;
			break;
		}
	}
	return ret;
}
/* }}} */

从上面的代码我们可能看到:PHP中的存储方式只能有10种,如果需要更多的方式,则需要修改MAX_MODULES的值,重新编译PHP。
存储方式的添加顺序与对应的扩展模块加载顺序有关。

【关于session_set_save_handler】
此函数用于将session的存储方式设置为用户自定义的函数,此函数的各参数对应ps_module结构的各个部分,并且会是新session.save_handler配置为user
在将用户定义的函数设置为session的存储方式时会提前判断这些用户函数是否可用。如果存在一个函数不可用,则警告并退出,表示设置失败。

【关于各方法的调用】
对于各存储方式定义的函数以类似于PS(mod)->s_open的方式调用。
对于用户自定义的方法,通过PS(mod_user_names)保存函数名,在mod_user.c中以类似于retval = ps_call_handler(PSF(open), 2, args TSRMLS_CC);的方式调用。其中#define PSF(a) PS(mod_user_names).name.ps_##a

到此处理我们可以回答之前的问题。
1、save_handler的设置是如何实现的?
在php.ini中设置session.save_handler,在请求初始化时(PHP_RINIT_FUNCTION)查找对应的ps_module,将其赋值到全局变量PS(mod),后续都是通过PS(mod)来处理session的存储操作,如果用户使用了session_set_save_handler方法设置存储方式,则此时的存储方式为user,通过user的模块方法中转调用用户指定的方法
2、在内部存在怎样的结构存储这些处理方法?
此问题可以参见上面关于结构的说明,ps_module
3、不同的存储方式之间是如何替换的?
可以在php.ini中设置session.save_handler修改,或者使用session_set_save_handler使用用户自定义的函数
4、如果以一个扩展的方法添加,则需要实现哪些方法?
需要实现PS_OPEN_FUNC、PS_CLOSE_FUNC、PS_READ_FUNC、PS_WRITE_FUNC、PS_DESTROY_FUNC、PS_GC_FUNC、PS_CREATE_SID_FUNC等宏,这些分别对应ps_module的各个方法
5、这些方法在哪里添加到系统中的?
通过php_session_register_module函数在模块初始化时加载

PHP扩展开发:简单类实现

PHP扩展开发:简单类实现
话说之前有写过如何在vs2008下开发PHP扩展,今天我们来实现一个简单类。
对于一个类,一般包括类定义,声明成员变量,声明成员函数,定义类常量,
对于类继承,重载,接口实现等不在这里说明。
首先我们看下这个简单类的PHP实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
 * 简单类示例的PHP实现  2010-11-03 sz
 * @author phppan.p#gmail.com  http://www.phppan.com                               
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
class Phppan {
 
    private $_name;
 
    CONST URL = 'http://www.phppan.com';
 
    public function __construct() {
    }
 
    public function getName() {
        return $this->_name;
    }
 
    public function setName($name) {
        $this->_name = $name;
    }
}

我们同样使用在之前创建的martin扩展
下面说明其实现过程:
1、声明类的成员函数
在php_martin.h文件中声明类的成员函数,如下所示代码:

1
2
3
4
5
 
PHP_METHOD(phppan, __construct);                                      
PHP_METHOD(phppan, __destruct);
PHP_METHOD(phppan, getName);
PHP_METHOD(phppan, setName);

2、定义类的成员函数
在martin.c文件中给出这4个函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 
 
/** {{{ 
*/
PHP_METHOD(phppan, __construct) {
 
}
/* }}} */
 
/** {{{ 
*/
PHP_METHOD(phppan, __destruct) {
}
/* }}} */
 
/** {{{ 
*/
PHP_METHOD(phppan, getName) {
	zval *self, *name;
 
	self = getThis();
 
	name = zend_read_property(Z_OBJCE_P(self), self, ZEND_STRL("_name"), 0 TSRMLS_CC);
 
	RETURN_STRING(Z_STRVAL_P(name), 0);
}
/* }}} */
 
/** {{{ 
*/
PHP_METHOD(phppan, setName) {
	char *arg = NULL;
	int arg_len;
	zval *value, *self;
 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
 
	self = getThis();
 
	MAKE_STD_ZVAL(value);
	ZVAL_STRING(value, arg, arg_len, 0);
 
	SEPARATE_ZVAL_TO_MAKE_IS_REF(&value);
	zend_update_property(Z_OBJCE_P(self), self, ZEND_STRL("_name"), value TSRMLS_CC);
 
	RETURN_TRUE;
}
/* }}} */

3、注册这些函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
/* {{{ martin_functions[]
 *
 * Every user visible function must have an entry in martin_functions[].
 */
const zend_function_entry martin_functions[] = {
	PHP_FE(martin_echo,	NULL)		
	PHP_ME(phppan, __construct, 	NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) 
	PHP_ME(phppan, __destruct,  	NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) 
	PHP_ME(phppan, getName, 	 	 	NULL, ZEND_ACC_PUBLIC) 
	PHP_ME(phppan, setName, 	 	 	setName_args, ZEND_ACC_PUBLIC) 
	{NULL, NULL, NULL}	/* Must be the last line in martin_functions[] */
};
/* }}} */

4、在扩展模块初始化时初始化类,并声明成员变量和常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
zend_class_entry *phppan_ce;
 
/* 类方法的参数 */
ZEND_BEGIN_ARG_INFO(setName_args, 0)
	ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
 
/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(martin)
{
	/* If you have INI entries, uncomment these lines 
	REGISTER_INI_ENTRIES();
	*/
	zend_class_entry phppan;
 
	INIT_CLASS_ENTRY(phppan, "Phppan", martin_functions);
	phppan_ce = zend_register_internal_class_ex(&phppan, NULL, NULL TSRMLS_CC);
 
 
	/* 声明常量URL */
	zend_declare_class_constant_stringl(phppan_ce, ZEND_STRL("URL"), ZEND_STRL("http://www.phppan.com") TSRMLS_CC);
 
	/* 声明私有成员变量 _name  */
	zend_declare_property_null(phppan_ce, ZEND_STRL("_name"), ZEND_ACC_PRIVATE TSRMLS_CC);
 
 
	return SUCCESS;
}
/* }}} */

以上为所有代码
在这些代码里面有一些东西需要说明一下:
1、类方法的定义与php的单个函数的定义一样,使用zend_function_entry结构数组,不同的是单个方法使用PHP_FE宏,
类方法的定义使用PHP_ME宏,在h文件中使用ZEND_METHOD声明,普通的函数使用ZEND_FUNCTION声明。
PHP_ME宏
定义在php.h中
#define PHP_ME ZEND_ME
#define ZEND_ME(classname, name, arg_info, flags) ZEND_FENTRY(name, ZEND_MN(classname##_##name), arg_info, flags)
2、在注册类时把该结构体作为参数交给相关的类注册方法即可
ZEND_BEGIN_ARG_INFO(setName_args, 0)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
3、取this变量使用getThis();
4、使用zend_read_property读取类成员变量,返回的是zval 指针类型
5、使用zend_update_property更新类成员变量。
6、初始化类使用INIT_CLASS_ENTRY宏
7、注册类使用zend_register_internal_class_ex函数

function registration failed duplicate name.问题的解决方法:
这个由于我们在写这个简单类时偷了一下懒,将martin_functions作为模块的函数也将它作为了类的方法。
解决方法是替换上面的martin_functions数组,增加phppan_functions,并且在注册时使用phppan_functions,在模块的functions字段使用martin_functions

/* {{{ martin_functions[]
 *
 * Every user visible function must have an entry in martin_functions[].
 */
const zend_function_entry martin_functions[] = {
    {NULL, NULL, NULL}  /* Must be the last line in martin_functions[] */
};
 
const zend_function_entry phppan_functions[] = {
    PHP_ME(phppan, __construct,     NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
    PHP_ME(phppan, __destruct,      NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
    PHP_ME(phppan, getName,             NULL, ZEND_ACC_PUBLIC)
    PHP_ME(phppan, setName,             setName_args, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}  /* Must be the last line in martin_functions[] */
};
/* }}} */
 
INIT_CLASS_ENTRY(phppan, "Phppan", phppan_functions);

WIN7下VS2008下编译PHP扩展的6个细节

1、编译生成的dll文件无法加载的问题
此时apache启动时可能会报如下错误:
PHP Warning: PHP Startup: Invalid library (maybe not a PHP library) ‘php_martin.dll’ in Unknown on line 0
原因:get_module在动态链接库中不对外开放
修改:在vs2008的项目属性,选择【Configuration Properties】-> 【C/C++】-> 【Preprocessor】-> 【Preprocessor Definitions】增加(COMPILE_DL_MARTIN)宏定义
查看方式:进入vs2008的命令行模式,进入dll所在文件夹,输入命令:dumpbin /exports php_martin.dll
查看是否提供了get_module函数
以上的martin需换成你自己的扩展名

2、LNK2001: unresolved external symbol _ZVAL_ADDREF问题
在之前的文章PHP5.3版本编译扩展时出现:LNK2001: unresolved external symbol _ZVAL_ADDREF
有提到解决方案,只是这样是将新的接口转换成旧的接口,这对于无法修改的旧代码可以适用,但是对于新的代码,我们建议在旧版本的时候使用Z_ADDREF_P将ZVAL_ADDREF替换,如下所示代码:

1
2
3
#ifndef Z_ADDREF_P
#define Z_ADDREF_P(x) ZVAL_ADDREF(x)
#endif

感谢鸟哥的指导

3、对于在被其它c文件include的c文件,在进行编译操作时需要将其从项目中排除掉。

4、Runtime Library
在编译时如遇到显示如下错误时:

1
2
3
4
5
Error	229	error LNK2019: unresolved external symbol __imp___free_dbg referenced in function
 
Error	230	error LNK2019: unresolved external symbol __imp___malloc_dbg referenced in function 
 
Error	231	error LNK2019: unresolved external symbol __imp___strdup_dbg referenced in function

LNK2019: unresolved external symbol __imp___free_dbg referenced
在vs2008的项目属性,选择【Configuration Properties】-> 【C/C++】-> 【Code Generation】-> 【Runtime Library】,将其改为/MDd,而不是/MD

5、32位,64位问题
如有报错:Error 137 error C2466: cannot allocate an array of constant size 0
这可能是VS2008 默认使用 64 位的 time_t 结构
建议在命令中添加:/D “_USE_32BIT_TIME_T=1″

6、对于不同版本的dll编译,除了对应版本的源码外,所需要的php5ts.lib文件也要使用其相对应版本
否则会在链接时报LNK2019错误,如:
error LNK2019: unresolved external symbol __imp__zend_str_tolower_dup referenced in function
error LNK2019: unresolved external symbol __imp__gc_remove_zval_from_buffer referenced in function

–EOF–