存档

‘PHP’ 分类的存档

ab测试swoole和ngixn+php-fpm对比

2015年10月29日 没有评论

最近由于公司项目需要,要将一些原来的业务提取分离出来做成服务的模式供调用,目前我们的解决方案是使用韩天峰大大的swoole框架来解决这个问题。swoole这个框架虽然不算是一个新的东西,但是目前使用的其实还真不太多,在上一家做游戏的公司,我接触到了swoole,但是一致没有机会实用,所以一直不知道这个东西到底有多犀利。公司有几位做php的同事,不过他们一方面公司的项目比较近,需要他们去做,另一方面以前我对swoole也有过简单的接触,实话是说挺喜欢这个东西的。又花了近一周的时间来了解swoole,今天用swoole写的一个短信发送的项目就基本完成了,就用ab做了一些swoole测试,同时做了一些nginx的测试。swoole卓越的性能让我惊呆了,发现php原来也可以做这么强大多工作,感觉php的春天来了!

说了这么多废话,看一下swoole和nginx的性能测试对比吧。我这里就只给出ngixn测试的脚本代码,就是一个简单的mysql插入,而swoole那边是swoole框架搭建的一个server,用来处理收到的各种请求多,里面包含http协议头部和body部分的解析,以及请求的验证,最终是短信记录和发送。不过由于发送短信要收费,所以测试时把发送短信功能去掉,只有入库纪录功能。

nginx测试脚本

<?php
$link = mysql_connect("127.0.0.1","root","") or die(mysql_error());
mysql_set_charset("utf8");
mysql_select_db("sql_test");
$sql = "INSERT INTO ab_test (`msg`,`record_time`) VALUE(\"".uniqid()."itest\ ",\"".date("Y-m-d H:i:s”).”\”)”;
$result = mysql_query($sql);
var_dump($result);

swoole部分的代码由于设计公司业务和也比较多就不上了,只贴出nginx和swoole pk的测试数据了。

测试环境

os:centos7(mac pro的虚拟机里面装的centos7)

mem:4G(虚拟设置的4G,free查看时发现不到4G)

disk:固态硬盘(其实这里的影响最大是mysql的写入)

nginx 环境下就是上面的代码,但是swoole 是验证请求以后直接返回,由于我们swoole里面mysql和http请求都是异步处理,所以返回速度要比阻塞的nginx+fpm效率要高很多(nginx并不阻塞,关键是fpm和mysql).

10000请求1000并发

nginx的表现

nginx_10000_1000

swoole的表现

swoole_10000_1000

10000请求1500的并发

nginx的表现

nginx_10000_1500

swoole的表现

swoole_10000_1500

测试数据不难看出nginx+php-fpm模式在1000并发的时候就有数百多失败请求你,而且请求时间 用了75秒,而swoole只有12秒。我们再看一下qps参数(Requests per second)参数,由于1500的时候nginx此时全部返回500已经不可用,那就拿1000并发来看,nginx的在100多,swoole的达到了800多,而swoole在1500并发时候QPS也是800+。1500并发的时候虽然用时都差不多,但是nginx这使用失败率超过97%了,这个失败nginx几乎不可用,而swoole还是出色完成了任务。你可能会说swoole是直接返回,异步处理中可能也有失败的吧?很遗憾,我对比了数据表纪录,swool以100%的完成率全部完成了任务。

当然就目前来看1500还不是swoole的瓶颈,我在做2000并发测试时ab工具出了一些问题就没有再做。如果以后有机会就把它补上!

swoole,phper的春天!

分类: PHP 标签: ,

PHP扩展开发之扩展函数

2015年9月22日 没有评论

本来想着看了前面的文章,大家对php扩展开发中写自己的扩展函数会有一个很清晰的了解.但后来自己又梳理了一番,发现有几个地方可能还是需要展开一下的,这样会对扩展函数的开发有一个更深层次的理解.首先我们先来写一个简单的求和的php扩展函数.


PHP_FUNCTION(king_sum){
 long num1,num2,sum;
 if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC,"ll",&num1,&num2) == FAILURE){
 RETURN_FALSE();
 }
 sum = num1+num2;
 RETURN_LONG(sum);
}

代码很简单,接受2个参数,然后求和返回结果.接下来我们看看这里面都用到那些知识点.

PHP_FUNCTION宏

虽然上一篇在GDB里面已经展开说过这个宏了,这里还是要再展开一下,多了一些东西.


//main/php.h
#define PHP_FUNCTION ZEND_FUNCTION

//Zend/zend_API.h
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

//Zend/zend.h
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

上面PHP_FUNCTION(king_sum)最终的展开结果是”void zif_king_sum(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)”,除了函数名没有其他的变化(如果你对宏不是很清楚,可以google一下C语言宏).宏展开的过程这里面就不再多说了,今天注意一下宏”INTERNAL_FUNCTION_PARAMETERS”,里面几个参数是什么意思哪?我们来看一下官方的文档.

INTERNAL_FUNCTION_PARAMETERS
名称和类型 描述 访问宏
int ht 用户实际传递参数的数量 ZEND_NUM_ARGS()
zval *return_value PHP 变量的指针,可填充返回值传递给用户。默认值是 IS_NULL RETVAL_*, RETURN_*
zval **return_value_ptr 当返回引用时,PHP 将其设为变量的指针。不建议返回引用。
zval *this_ptr 假如这是一个方法调用,其指向存放 $this对象的 PHP 变量。 getThis()
int return_value_used 指示返回值是否会被调用者使用的标志。

这几个参数的作用上面已经说的很清楚了.ht这个参数我们在解析参数时会遇到,前面的几篇文章里面有传参时,我们都会看到ZEND_NUM_ARGS()宏,return_value参数是我们目前写扩展函数中要经常使用到的,下面会在函数返回值时候提到操作相关函数的宏.this_ptr是调用类的时候才会用到的,再开始了解PHP面向对象相关的知识以前,我们是很少会遇到它的.return_value_used是内核传递进来的一个参数,脚本里面调用函数的时候返回值是否使用.

从PHP_FUNCTION这个宏可以看出,扩展函数展开以后都是void类型的,而且函数声明中我们也就看到一个PHP函数传参相关的,其余都是和返回相关的。由于前面用了一整篇来写扩展函数的传参,所以这里我们就不在展开,没有看到的朋友可以回头看一下php扩展开发之传参.

变量修改

在最初的 文章php弱类型的实现中,我们就已经知道了php中的变量最终是通过zval来存储的,相信通过这几篇文章中的例子,大家也可以看出来一下。php在创建变量的时候需要申请内存空间和一些初始化。在申请空间的时候,php提供了一套自己的函数,通过“emalloc,ecalloc,erealloc,efree”这些函数申请的内存,php会进行自己的垃圾回收,有php自身的一套垃圾回收机制.就算你忘记efree了,在请求接受以后这部分内存也会被释放掉.但是你需要注意的是如果你的php-fpm是以静态模式运行的时候,php模块初始化部分的内存是不会释放掉的,这对高并发的网站来说有效的减少了php内存的申请和释放的次数,大大的提高了php的运行效率。但是如果你的内存只有1G而且跑的还有其他运行程序,那么你以这种方式来跑php可能是灾难性的,很容易就把系统的内存资源耗尽。同时需要注意的有使用emalloc申请的内存要用efree去释放,malloc申请的内存要用free去释放。因为efree释放的内存情况上面它是把占用的内存交还给了php的内存管理,方便其他系统调用。关于php的内存管理这里就不在展开,以后如果有机会,会再详细的谈一谈。

zval的内存申请和初始化时,php也提供了几个相关的宏来方便我们操作:


//Zend/zend.h
#define MAKE_STD_ZVAL(zv) \
 ALLOC_ZVAL(zv); \
 INIT_PZVAL(zv);

#define ALLOC_INIT_ZVAL(zp) \
 ALLOC_ZVAL(zp); \
 INIT_ZVAL(*zp);

#define INIT_PZVAL(z) \
 (z)->refcount__gc = 1; \
 (z)->is_ref__gc = 0;

#define INIT_ZVAL(z) z = zval_used_for_init;

//Zend/zend_gc.h
#define ALLOC_ZVAL(z) \
 do { \
 (z) = (zval*)emalloc(sizeof(zval_gc_info)); \
 GC_ZVAL_INIT(z); \
 } while (0)

用这些php提供的宏我们可以很容易的创建出一个变量,那么怎么去给这些变量赋值那?

zval要处理所有php变量,其最终存储字符串/数字/对象/数组等数据是靠的zvalue_value联合体。


typedef union _zvalue_value {
 long lval; /* long value */
 double dval; /* double value */
 struct {
 char *val;
 int len;
 } str;
 HashTable *ht; /* hash table value */
 zend_object_value obj;
 zend_ast *ast;
} zvalue_value;

如果我们自己直接对这个联合体进行赋值操作,那么我们需要根据真实情况进行复杂的操作,这是让人很头疼的问题。不过Zend内核也为我们提供了一系列的宏来解决这个问题。


#define ZVAL_RESOURCE(z, l) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_RESOURCE;\
 } while (0)

#define ZVAL_BOOL(z, b) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = ((b) != 0); \
 Z_TYPE_P(__z) = IS_BOOL; \
 } while (0)

#define ZVAL_NULL(z) { \
 Z_TYPE_P(z) = IS_NULL; \
 }

#define ZVAL_LONG(z, l) { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_LONG; \
 }

#define ZVAL_DOUBLE(z, d) { \
 zval *__z = (z); \
 Z_DVAL_P(__z) = d; \
 Z_TYPE_P(__z) = IS_DOUBLE; \
 }

#define ZVAL_STRING(z, s, duplicate) do { \
 const char *__s=(s); \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = strlen(__s); \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, Z_STRLEN_P(__z)):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_STRINGL(z, s, l, duplicate) do { \
 const char *__s=(s); int __l=l; \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = __l; \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, __l):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_EMPTY_STRING(z) do { \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = 0; \
 Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_ZVAL(z, zv, copy, dtor) do { \
 zval *__z = (z); \
 zval *__zv = (zv); \
 ZVAL_COPY_VALUE(__z, __zv); \
 if (copy) { \
 zval_copy_ctor(__z); \
 } \
 if (dtor) { \
 if (!copy) { \
 ZVAL_NULL(__zv); \
 } \
 zval_ptr_dtor(&__zv); \
 } \
 } while (0)

宏函数虽然不少,但是其命名很规范,都是ZVAL_*的格式,很容易记忆的,都是对应相应的数据类型。我们来看一下用这些宏创建变量和赋值的例子吧。


zval *name,*age;

MAKE_STD_ZVAL(name);

MAKE_STD_ZVAL(age);

ZVAL_STRING(name,"King",1);

ZVAL_LONG(age,25);

函数调用

我们写php代码的时候,经常要调用自己写的其他函数.同理如果其他的我们需要调用其他的扩展函数时,我们没必要再把扩展函数代码赋值一份过来调用,Zend内核提供了相应的方法来让我们调用其他扩展函数。但是今天这个部分就先跳过去,后面我会在花费一个整篇文章来介绍这个话题。

函数体中都是C/C++代码,这也没什么好说的。如果你对这些知识欠缺,自己找些C/C++的书籍补一补。

函数返回

从上面PHP_FUNCTION宏展开中,我们可以看到扩展函数被声明成void,是没有返回内容的,但是扩展里面传递进来了2个指针,return_value和return_value_ptr.这不像我们写PHP函数,一般函数结果都是直接返回的,而扩展函数毕竟是要由C/C++开发出来,所以难免会类似C/C++的风格,熟悉C/C++编程的朋友对这些肯定不会陌生。这是Zend内核设计的模式,我们做为开发者也没有办法去更改他,只能适应这个模式。

在Zend/zend_API.h中,我们可以看到一些操作return_value相关的宏RETVAL_*和RETURN_*,其实RETURN_*是对RETVAL_*的封装.


#define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b)
#define RETVAL_NULL() ZVAL_NULL(return_value)
#define RETVAL_LONG(l) ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE ZVAL_BOOL(return_value, 1)

#define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
#define RETURN_NULL() { RETVAL_NULL(); return;}
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE { RETVAL_FALSE; return; }
#define RETURN_TRUE { RETVAL_TRUE; return; }

RETURN_*宏是对RETVAL_*宏的二次封装,而RETVAL_*宏是对return_value支持的操作,而return_value指针前面我们已经提过它是我们返回结果的重要手段之一.

模块载入函数

当然如果你这写到这里就以为结束了,然后去动手编译你的扩展,你会看到提示你这个方法不存在的。那是因为C扩展和PHP语言之间有一个zend_module_entry,里面有个zend_function_entry数组,这里面是记录我们当前扩展中有那些php函数的。如果你是手动创建的话需要自己来填写这个模块信息的变量,zend_module_entry内容很多,我们可以按照ext_skel工具生成的模板来填写。

zend_module_entry king_module_entry = {
 STANDARD_MODULE_HEADER, //标准的一个结构体头部
 "king",//模块名称
 king_functions,//zend_function_entry数组,保存扩展php函数的数组
 PHP_MINIT(king),//模块初始化函数,没有具体的需要做的可以为NULL
 PHP_MSHUTDOWN(king),//模块关闭调用函数,没有具体的需要做的可以为NULL
 PHP_RINIT(king), //模块运行前调用函数,跟在PHP_MINIT函数之后,
 PHP_RSHUTDOWN(king), /* Replace with NULL if there's nothing to do at request end, */
 PHP_MINFO(king),//
 PHP_KING_VERSION,//这个地方是我们扩展的版本,版本信息
 STANDARD_MODULE_PROPERTIES
};

const zend_function_entry king_functions[] = {
 PHP_FE(confirm_king_compiled, NULL) /* For testing, remove later. */
 PHP_FE(king_sum,king_sum_arginfo)//添加我们自己的函数,前面是调用函数,后面是参数信息
 PHP_FE_END /* Must be the last line in king_functions[] */
};

//king_sum函数参数信息
ZEND_BEGIN_ARG_INFO(king_sum_arginfo,1)
 ZEND_ARG_INFO(0,king_sum_num1)
 ZEND_ARG_INFO(0,king_sum_num2)
ZEND_END_ARG_INFO()

//
typedef struct _zend_arg_info {
 const char *name;
 zend_uint name_len;
 const char *class_name;
 zend_uint class_name_len;
 zend_uchar type_hint;
 zend_uchar pass_by_reference;
 zend_bool allow_null;
 zend_bool is_variadic;
} zend_arg_info;

//位置main/php.h
#define PHP_FE ZEND_FE
//位置Zend/zebd_API.h
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },

上面的代码中我们初始化了一个名为king_module_entry的zend_module_entry结构体,里面第三行的参数为当前模块函数数组。king_functions是一个zend_function_entry数组,我们可以使用PHP_FE宏往数组里面添加函数,第一个参数为函数名字,第二个参数为参数信息.参数信息是一个zend_arg_info数组:


typedef struct _zend_arg_info {
const char *name; //参数名称
zend_uint name_len;
const char *class_name;
zend_uint class_name_len;
zend_uchar type_hint;
zend_uchar pass_by_reference; //是否引用传递
zend_bool allow_null;
zend_bool is_variadic;
} zend_arg_info;

同样php也提供了一些宏来填充这些数组,下面我们来看一下这些宏:


#define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, sizeof(#name)-1, NULL, 0, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 1 },

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_arg_info name[] = { \
{ NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };

ZEND_BEGIN_ARG_INFO是一个通用的结构体头部宏,要和ZEND_END_ARG_INFO配套使用.ZEND_ARG_INFO提供了否引用和参数名称的设置,pass_by_ref为是否引用,0为否,1为是,后面的name 为参数名称。第二个ZEND_ARG_PASS_INFO提供对参数是否引用的设置,参数具体值同上.后面的宏就不仔细介绍了,可以根据其名称推断出来其作用。

完成了这些,我们才算完成了一个完整的PHP扩展。我们可以按照前面文章进行编译和安装,然后进行测试了.

分类: PHP 标签: ,

php扩展开发之GDB调试

2015年8月19日 没有评论

俗话说的好"磨刀不误砍柴工",我们在开发扩展的时候,会像写C语言代码一样,经常会遇到程序core掉了.然而PHP脚本中常用的调试方式"echo,print_r,var_dump"却不能给我们什么大的帮助.今天我们就看看如何利用GDB来调试我们的php扩展程序?

为了方便我们使用gdb调试,最好在编译php程序的时候,把configure工具的”–enable-debug”参数启用,默认它是禁用的,因为它会产生很多方便我们调试的信息(注意在生产环境不要启用它,它会降低一部分php的性能).今天会主要说一下利用gdb进行断点调试,利用core文件调试已经不少的教程,所以我就不想再多做这些重复工作了.

前面的文章大家都看到如果声明一个php扩展函数,例如PHP_FUNCTION(count),这里如果我们直接利用cout函数来做断点,可能我们捕捉不到(这里我用了count做例子,但大家不要自己扩展里面这么做,因为count已经是php内建的扩展函数了,这样会引起不必要的问题).因为zend为了避免我们写的函数和C系统函数或这其他类库重名所以这里使用了PHP_FUNCTION宏进行声明,那么这个宏是什么样子哪?


#define PHP_FUNCTION ZEND_FUNCTION

#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )

#define ZEND_FN(name) zif_##name

#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

上面我们提到的提到的”PHP_FUNCTION(count)“最终的展开结果是”void zif_count(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)“,因此如果我们直接断点count可能就不会有结果的,这里我们需要用zif_count来断点就会捕捉到了.

如果我们使用的是一个第三方的扩展,我们不是很清楚里面有那些函数,或者修改过函数的前缀,已经不再是”zif”了.那么我们怎么获得我们需要的函数信息那?

我们可以使用nm工具来查看动态链接库里面的函数信息,就那我们上一篇那个交换2个数字的函数扩展为例子,看看如果使用nm吧.


nm king.so

//下面信息为nm摘取的部分信息

0000000000000e80 T zif_confirm_king_compiled
0000000000000f10 T zif_king_author
0000000000000fd0 T zif_king_call_system_func
0000000000000f30 T zif_king_exchange_number
0000000000000e60 T zm_activate_king
0000000000000e70 T zm_deactivate_king
0000000000001190 T zm_info_king
0000000000000e50 T zm_shutdown_king
0000000000000e40 T zm_startup_king

具体的信息如下图:

nm

 

看一下我们的扩展是否加入到php解释器中去那


php -m|grep king

//king

如果看到了”king”说明我们的扩展已经ok了,现在就做开始做gdb调试吧


cd /home/king/web_project/test/ext_king

gdb php

b zif_king_exchange_number

//Make breakpoint pending on future shared library load? (y or [n])

//这里用于这动态链接中,所以在我们php中是找不到这符号的,所以点y即可

//运行我们的php测试脚本

run ./exchange.php

//后面就是正常的gdb调试了

如此我们就可以清楚看到程序是怎么执行,更方便我们开发扩展程序.

如果我们有php程序core掉的文件,那么我们可以利用core文件进行调试.


gdb php -c core

#后面的可以和正常的gdb调试core文件一样,不过php提供了一个.gdbinit的帮助脚本,这个脚本在你下载的源文件的根目录下面,下面source的路径自行替换

source  ~/download/php-5.6.10/.gdbinit

zbacktrace

如果你需要更多.gdbinit脚本信息请自行google,或者去鸟哥的博客看看.

参考资料:

 如何调试PHP的Core之获取基本信息 

http://www.kuqin.com/language/20111022/313184.html

分类: PHP 标签:

php扩展开发之传参

2015年8月9日 没有评论

最近跟着服务器组同学学写C++,不是想转做C++开发,只是想借此机会锻炼一下自己.php的扩展不止可以使用原生的C,同样的可以使用C++来开发扩展来扩展我们的php.不过今天我们还不会谈C++扩展php,依旧还是原生的C来扩展我们的php.上一篇,我给了一个简单的扩展开发HelloWorld的例子.除了证明我们可以用C扩展php,它对我们日常工作可能要解决的问题来说毫无作用.今天我们来看一下php扩展传参是如果实现的,我们在日常的php开发当中写函数时要传参,同样我们看系统提供的函数中也有参数,但是我们在扩展函数的声明中是没有传参相关的参数的,那么它是如何实现的哪?

Zend API提供了解析参数的函数帮我们来解决这个问题:

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, …)

ZEND_API int zend_get_parameters(int ht, int param_count, …)

我这里列了2个函数,zend_parse_parameters和zend_get_parameters,你可以在Zend/zend_API.c中找到他们.今天我们主要了解的是zend_parse_parameters,应为这是现阶段php5扩展中开发主要的解析参数的函数,而zend_get_parameters是针对php5之前的扩展用到的.需要你需要扩展老版本的php或者兼容旧的版本,请自行google翻阅资料,我个人觉得其意义不是很大,这里就不再多加探讨了.

zend_parse_parameters有2个主要参数,还有若干可变的参数,可变参数的作用就是接受我们php脚本中传递给扩展的参数.我们先看看这2个主要参数的作用吧:

int num_args就是本函数参数的个数,const char *type_spec是一个用于格式化的字符串,类似C里面的printf,不过他们2个却是完全不一样的.

参数   代表着的类型
b   Boolean
l   Integer 整型
d   Floating point 浮点型
s   String 字符串
r   Resource 资源
a   Array 数组
o   Object instance 对象
O   Object instance of a specified type 特定类型的对象
z   Non-specific zval 任意类型~
Z   zval**类型
f   表示函数、方法名称,PHP5.1里貌似木有... ...
下面的列表给出这些参数对应C里面的那些数据类型
参数  对应C里的数据类型
b   zend_bool
l   long
d   double
s   char*, int 前者接收指针,后者接收长度
r   zval*
a   zval*
o   zval*
O   zval*, zend_class_entry*
z   zval*
Z   zval**
后面跟的参数就是用来存储格式化字符串里面所表示的php传递的参数,需要注意的是字符串类型要占用2个参数,一个用来存储字符串,一个用来存储字符串长度.例子这里就不再单独的写了,细心的读者会发现上一篇扩展开发HelloWorld已经有介绍了.
今天我们写一个引用传参的例子,当初我在这个地方也纠结了好久,具体的是我们写一个互换2个数字的函数.
首先我们先用php写一下这个函数(注释部分为脚本输出):
<?php
$x = 1;
$y = 2;
exchange_num($x,$y);
echo $x."\n".$y."\n";
function exchange_num(&$a,&$b){
$c = $a;
$a = $b;
$b = $c;
}
//2
//1
这里我们使用了&符号来使用类似的功能,那么我们用扩展如何实现类似的引用哪?
PHP_FUNCTION(king_exchange_number){
long num1,num2;
zval *arg1,*arg2;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"zz", &arg1, &arg2) == FAILURE){
RETURN_NULL();
}
num1 = Z_LVAL_P(arg1);
num2 = Z_LVAL_P(arg2);
php_printf("num1:%ld,num2:%ld\n",num1,num2);
ZVAL_LONG(arg1,num2);
ZVAL_LONG(arg2,num1);
RETURN_NULL();
}
细心的朋友会发现我没有使用long类型来接受参数,而是换成zval *来接受.最初的时候我也试图利用long来接受数据,事实上我们可以用long来接受参数,我们可以读取参数里面的值,也可以改变参数的.但是我们却无法改变外部传进来参数的值,因为参数是局部变量,它在函数执行结束以后就会被释放掉,所以我们即使改变了这些用来接受参数的变量的值,其外部参数还是不会变化的.那么如果才能做到改变外部的参数哪?答案当然是指针,只有我们传递来的是一个指针的时候,我们才可以改变外部的参数值.但是我们回顾上面格式化字符串对照表里面,没有指针,但是注意看z是对应任意类型的,所以我们这里使用z.
在你当前的zend_function_entry数组中加入(以后的例子中会省略掉这一部分,读者自行添加即可)
PHP_FE(king_exchange_number,NULL)
然后保存重新编译扩展.然后我们在用我们写的扩展使用上面的功能(注释部分为脚本的输出结果).
<?php
$num1 = 11;
$num2 = 89;
king_exchange_number($num1,$num2);
echo "\$num1:$num1\n";
echo "\$num2:$num2\n";
//$num1:89
//$num2:11
到此相信你对php扩展传参也有个大致的了解了.文中的扩展在php-5.10中运行正常,如有疑问可以留言.

php扩展开发之”Hello World!”

2015年7月21日 没有评论

最近在看《PHP扩展开发及内核应用》,书的内容不是特别多,但感觉都是精华,值得大家去一读的.这个系列的文章也是自己的读书的笔记或者一点心得吧,会把自己遇到的问题和踩过的坑都会提出来,大家可以引以为鉴,文章的不足和错误也请大家指出来.PHP做为现在最火的脚本语言之一,在Web方面更是当之无愧的王者,吸引大家使用PHP的原因有很多,而PHP的社区也是非常活跃和成功的,目前PHP7也预计要在十月份出来正式版本的.

其实PHP的扩展也是十分简单和便捷的,当然你在考虑用C/C++扩展你的PHP之前,建议还是考虑一下这么做是必须的或者说是必要的吗(用C/C++扩展你的PHP这篇文章开头关于扩展PHP的动机还是建议大家看一下的)?

现在我们将进入今天的整体写一个简单的整体,开发我们自己的PHP扩展.我下载的PHP版本是PHP5.6.10,也是最新版的,需要注意的是这个系列文章中的PHP都是PHP5以后版本的,之前的都是版本Zend engine都是1而PHP5之后Zend engine升级到2,因此如果你要是还在维护PHP5之前的版本,那么这个系列文章对您的帮助可能不是很大.

PHP源码提供了一个非常便捷的帮助大家创建自己的扩展工具,在ext目录下面大家可以看到它–“ext_skel”,在windows下面替代的工具是”ext_skel_win32.php”.

我们来看一下ext_skel有那些参数


./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
 [--skel=dir] [--full-xml] [--no-help]

--extname=module       module is the name of your extension
 --proto=file          file contains prototypes of functions to create
 --stubs=file          generate only function stubs in file
 --xml                 generate xml documentation to be added to phpdoc-svn
 --skel=dir            path to the skeleton directory
 --full-xml            generate xml documentation for a self-contained extension(not yet implemented)
 --no-help             don't try to be nice and create comments in the code and helper functions to test if the module compiled

这里面的extname是指定我们自己扩展的名称,proto是指定包含我们想要创建的函数文件,一般文件是def格式的.

这里我们创建自己的hellworld.def文件,内容如下


void hello_world(string name)

下面我们只需要执行下面的命令,就完成了我们扩展的大部分工作了.


./ext_skel --extname=hello_world --proto=helloworld.def

Creating directory hello_world
Creating basic files: config.m4 config.w32 .gitignore hello_world.c php_hello_world.h CREDITS EXPERIMENTAL tests/001.phpt hello_world.php [done].

To use your new extension, you will have to execute the following steps:

1. $ cd ..
2. $ vi ext/hello_world/config.m4
3. $ ./buildconf
4. $ ./configure --[with|enable]-hello_world
5. $ make
6. $ ./sapi/cli/php -f ext/hello_world/hello_world.php
7. $ vi ext/hello_world/hello_world.c
8. $ make

Repeat steps 3-6 until you are satisfied with ext/hello_world/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.

上面的第1行是我们执行的命令,后面的脚本自动生成的内容,而我们开发扩展基本就按照这个流程来完成的.下面我们先去完成我们的hello_world函数去,然后我们的扩展工作基本就完成了.

进入ext/hello_world目录,编辑hello_world.c文件,这是我们扩展的主要文件了,只要我们的扩展内容不是特别多,我们可以完全把函数放在这里面的来完成.找到系统帮我们生成的代码中的”PHP_FUNCTION(hello_world)”,然后我们在此完成我们的hello_world功能.


PHP_FUNCTION(hello_world)
{
char *name = NULL;
int argc = ZEND_NUM_ARGS();
int name_len;

if (zend_parse_parameters(argc TSRMLS_CC, "s", &name, &name_len) == FAILURE)
return;

//php_error(E_WARNING, "hello_world: not yet implemented");
php_printf("Hello World!\n");
php_printf("Hello %s!\n",name);
RETURN_NULL();
}

其实我们就动了4行代码,注释掉php_error,然后和后面加上的3行代码.至此我们的功能就全部实现了.剩下就是按照扩展了.但是如果你去hello_world.c文件里面代码,你会发现ext_skel帮助我们生成很多代码,里面还是有一些东西是需要我们注意一下.

我们来看一下紧跟着hello_world函数的代码.


/* {{{ hello_world_functions[]
 *
 * Every user visible function must have an entry in hello_world_functions[].
 */
const zend_function_entry hello_world_functions[] = {
 PHP_FE(confirm_hello_world_compiled, NULL) /* For testing, remove later. */
 PHP_FE(hello_world, NULL)
 PHP_FE_END /* Must be the last line in hello_world_functions[] */
};
/* }}} */

/* {{{ hello_world_module_entry
 */
zend_module_entry hello_world_module_entry = {
 STANDARD_MODULE_HEADER,
 "hello_world",
 hello_world_functions,
 PHP_MINIT(hello_world),
 PHP_MSHUTDOWN(hello_world),
 PHP_RINIT(hello_world), /* Replace with NULL if there's nothing to do at request start */
 PHP_RSHUTDOWN(hello_world), /* Replace with NULL if there's nothing to do at request end */
 PHP_MINFO(hello_world),
 PHP_HELLO_WORLD_VERSION,
 STANDARD_MODULE_PROPERTIES
};
/* }}} */

“const zend_function_entry hello_world_functions”这个数组定义了我们扩展里面包含那些函数,当初我没有使用ext_skel的proto参数生成,就没有把我自己写的函数加入到hello_world_functions.导致我在编译安装完扩展以后,在PHP代码里面使用我在扩展里面写的函数的时候,就一直报错”Call to undefined function”,还是google好久才找到问题所在.所以如果你不是用ext_skel的proto参数生成的,在编译安装之前要注意一下这里.下面"zend_module_entry hello_world_module_entry "定义是hello_world模块.关于这些内容具体解释可以参考《PHP扩展开发与内核应用》第5章1节和4节的一些内容.

配置config.m4

现在如果我们要安装我们的扩展,还需要配置扩展目录下面的config.m4文件啦.

dnl PHP_ARG_WITH(hello_world, for hello_world support,
dnl Make sure that the comment is aligned:
dnl [ --with-hello_world Include hello_world support])

dnl Otherwise use enable:

PHP_ARG_ENABLE(hello_world, whether to enable hello_world support,
dnl Make sure that the comment is aligned:
[ --enable-hello_world Enable hello_world support])

dnl是m4文件的注释, 根据需要去掉相应的注释,PHP_ARG_WITH是我们在configure时”–with-xx”,此种方式一般依赖一些库的.而PHP_ARG_ENABLE是”–enable-xx”的方式,一般都是可以单独编译安装的,不依赖任何其他库支持的.我们这里选择PHP_ARG_ENABLE即可.

安装扩展

下面就是编译安装扩展了,如果你做过相关的工作就可以跳过这个小节的直接动手去尝试了.


phpize

./configure

make

sudo make install

在你的php.ini最后加上


extension=hello_world.so

//如果你的扩展是zend的,那需要改成zend_extension即可

重启你的apache或者php-fpm进行测试即可了.

测试扩展

编写一个测试的php脚本进行一下测试,查看我们的扩展是否安装成功.


<?php
hello_world("king");

?>

//最终输出如下,说明我们的扩展已经正常工作了

Hello World!
Hello king!

我们的hello world扩展开发也完成了.

 

php弱类型的实现

2015年6月30日 没有评论

php是一门弱类型的语言,这基本是为人所共知的.但是php是用C语言编写的,C语言是一门强类型的语言,而由C语言开发而来的php没有像C++/Java这些延续C语言的强类型,那php是如何实现这个哪?

PHP在声明和使用变量的时候,并不需要显示的指明其数据类型,这是php弱类型带来的便捷(当然也有性能的损耗),但这并不是说PHP中没有类型的.在PHP中,存在8种变量类型,我们大致可以分为3类

标量类:boolean、integer、float(double)

复合类型:array、object

特殊类型:resource、NULL

上面我们谈了PHP中的类型,只是为了方便我们清楚PHP也是有类型这个概念的.如果我们想要弄清楚上面的问题,那么我们就需要看看PHP是如何实现这些变量的定义和存储的.

变量存储结构

PHP把变量存储到zval结构体中,zval结构体定义在Zend/zend_types.h文件中


typedef struct _zval_struct zval;

不难看出zval实际上面是struct _zval_struct结构体,_zval_struct定义在Zend/zend.h


struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};

PHP使用这个结构体来存储所有用户变量.当然需要注意的是,PHP在存储变量时是存储在PHP用户空间(堆内存中),所以我们在PHP中可以把函数中的变量直接返回.而我们在C语言中有不少变量是声明在栈中的,如果我们直接把变量返回,那么可能会引发stack overflow的,特别是我们这边先接触PHP在回过头来看C的同学特别需要注意.PHP对堆内存有自己的内存管理和垃圾回收机制,所以一般我们PHP程序员大部分都是很少注意内存的消耗的.建议在程序比较占内存的时候,使用unset手动释放内存,这样会减轻服务器的资源压力.

zval结构体中有四个字段,其含义分别为:

属性名                含 义                默 认值
refcount__gc  表示引用计数         1
is_ref__gc        表示是否为引用       0
value                  存储变量的值
type                   变量具体的类型
目前对我们本文来说涉及到的是value和type,下面我们来看一下这2个字段.

变量类型

zval结构体的type字段就是我们今天问题的关键了,PHP就是通过存储时把字段的类型放在这个type字段里面来实现的弱类型的.type的值可以为: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE 之一.从字面上就很好理解,他们只是类型的唯一标示,根据类型的不同将不同的值存储到value字段。 除此之外,和他们定义在一起的类型还有IS_CONSTANT和IS_CONSTANT_ARRAY。

变量的值存储

看前面在存储时,变量最终是存储在 zvalue_value联合体中的,其定义在Zend/zend.h头文件中.


typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;

这里使用联合体而不是用结构体是出于空间利用率的考虑,因为一个变量同时只能属于一种类型。 如果使用结构体的话将会不必要的浪费空间,而PHP中的所有逻辑都围绕变量来进
行的,这样的话, 内存浪费将是十分大的。这种做法成本小但收益非常大。

PHP正是通过上面几个结构体来实现的弱类型,如果您感觉还有写不清楚,可以看一下<深入理解PHP内核>这本书.

在php中使用socket进行tcp链接

2015年1月21日 没有评论

今天提供一个php利用socket建立tcp链接的类,当然后面可能会对这个类进行升级。php对C语言的socket操作进行了很好的封装和继承,而且个人感觉使用起来也比C语言方便很多(可能因为我本身就是一个phper而不是一个C语言开发者)。


class TcpScoket{

var $address;

var $port;

var $sock;

var $sockResult;

var $recvMsg='';

function __construct($address,$port=80){
$this->address = $address;
$this->port = $port;

$this->sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
if(!$this->sock) $this->getLastError();
$this->sockResult = socket_connect($this->sock,$this->address,$this->port);
if(!$this->sockResult) $this->getLastError();
}

/**
* 发送信息
* @param string $msg 发送信息
* @return mix 成功返回发送字符数,失败返回false
*/
function sendMsg($msg){
$msgLen = strlen($msg);
$num = socket_send($this->sock,$msg,$msgLen,0);
return $num;
}
/**
* 接受信息
* @param integer $len buff长度
* @return string 接受信息
*/
function recvMsg($len=512){

while(true){
$num = @socket_recv($this->sock,$buffer,$len,0);
if($num){
$this->recvMsg.=$buffer;
}else{
break;
}
}

return $this->recvMsg;
}

/**
* 获得上次的socket错误
* @return void
*/
function getLastError(){
$error = socket_last_error();
echo socket_strerror($error);
exit;
}

function __destruct(){
socket_close($this->sock);
}

}// end of class

使用它也是非常简单的,下面给出一个很简单的example,当然也欢迎大家补充.


$tcp = new TcpScoket("127.0.0.1",5730);
$tcp->sendMsg("你好");
echo $tcp->recvMsg();

分类: PHP 标签:

在手机上利用PHP完成对电脑的操作

2014年3月11日 没有评论

哎,有时世事正如那句话,有心栽花花不发,无心插柳柳成荫啊!前半夜又折腾了好久mysql的主从配置,最后发现原来网上大部分mysql主从的配置都是针对mysql5.1或者之前版本的,mysql5.1版本以后的配置改变了!12点要去睡觉时,突然想看一会再床上看一会电影,又不想再起来关电脑了,设置定时关机吧,我又不知道自己想看多久,肿么办那?
突然一道靓丽的火花从脑海一闪而过,wamp+手机浏览器?不错想到就去做吧!
首要实验的条件和环境先说一下吧:
网络需求:无线路由
电脑环境要求:php、apache
手机要求:wifi、手机浏览器
上面提到的配置需求都是最基本的,电脑环境可以使用php的集成环境wamp、xampp都行,当然自己安装也ok,手机的话目前任意一款带wifi的只能手机几乎都可以吧!为啥需要强调wifi,那是因为我这里的手机访问电脑是通过手机wifi连接无线路由来访问电脑的web服务的!
上面大致说了一下原理,那么web服务端怎么实现这些手机浏览器传递过来的信息那?
其实我们这里重要利用一个php的program execution函数exec来实现我们手机浏览器传递过来的信息!
我的目的就是实现手机可控制的关机和取消关机,那么我就把我web服务端的php脚本文件贴出来吧,供大家参考!

shutdown.php脚本内容:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
	a{font-size: 32px;}
</style>
<a href="?type=cancle">取消关机</a>
<br><br>
<a href="?type=close">关机</a>
<?php
$type=isset($_GET['type'])?$_GET['type']:false;
$cmd='';
switch ($type){
	case 'cancle':
		$cmd="shutdown -a";
		break;
	case 'close':
		$cmd="shutdown -f -s -t 120";
		break;
	default:
		$cmd='';
		break;
}
if(!empty($cmd))	
exec($cmd);
?>

然后通过浏览器访问这个文件,点击对应的超链接就可以实现关机和取消关机的功能了!这里其实就是把dos命令通过exec传递给了cmd.exe进行了执行,也就是说只要是dos相关的命令均可以通过exec进行执行。所以如果将我这里的提交命令的格式换成表单提交的话,表单里面放入要执行的命令,进行简易的改进,这也可以当做一个简易的木马程序来使用了!所以在生成环境下,我们一般要对这些安全威胁比较大的函数可以在php.ini中禁用掉!
太复杂的程序这里就不做太多的讲解了,读者可以根据自己的水平进行摸索,这里再提一下就是没有wifi或者无线路由的情况怎么办那?
没有无线路由、或者是在家里外面访问我们家里的电脑时,我们可以通过路由的端口映射功能来完成同样的功能,这里必须是你家里的ADSL分配的是一个相对稳定的IP地址,如果经常改变也不适合,还有就是多层路由的复杂网络这里也不做讨论。

是不是有点心动,像博主一样躺在床上就可以操作电脑,而且还不需要无线鼠标、键盘之类的。心动不如行动,have a try!

分类: PHP 标签:

php的shell脚本”Extension ‘.t1.php’ not present”问题

2014年3月11日 没有评论

php做为一门出色的脚本语言,其实也可以用来做很多内容的。今天工作时需要做一个定时任务的东西,考虑到用php写shell完全可以做到这个功能,于是就开始动手写了起来!

我先在windows上面将php代码改成shell脚本,然后上传到centos下面进行测试,发现一直报下面的错误:

Extension 2015 ‘./t1.php’ not cheap nba jerseys present
其中的t1.php是我的测试文件。最早的时候我在Centos下面用vim创建的一个脚本没有这个问题,但是我从Windows下面上传的文件就一直有这个问题。

从Google一下,发现别人也遇到了这个问题,好像是windows下面和Unix的文件格式不同造成的,解决办法用dos2unix转化一下文件的格式就可以了!

[root@nginx-php    cron]#  Quinoa-Porridge  yum  cheap nba jerseys  install dos2unix
[root@nginx-php  Mysql服务器内存过小启动失败问题  cron]#  cheap jerseys  dos2unix  Proof  t1.php
[root@nginx-php  403  cron]#  利用php实现文件下载功能  ./t1.php
分类: PHP 标签:

利用php实现文件下载功能

2014年3月11日 没有评论

今天介绍2种php下载文件功能:

第一种比较简单,根据文件的url路径进行一个303重定向:

/* 下载文件的url地址  Was  */
var  Mysql服务器内存过小启动失败问题  $file  cheap nfl jerseys  = "download.txt";
if  wholesale mlb jerseys  (isset($file))
{
Header("HTTP/1.1 303  最~最完整的文書處理快捷鍵大全~~~(必收藏)  See Other");
Header("Location: $file");
exit;
}

第二种方式使用readfile函数,当然使用file-get-contents函数或者其他可以获得文件流的函数一样可以实现同样的效果:

<?php
$file  wholesale nfl jerseys  =  Forbidden问题  'monkey.gif';

if (file_exists($file))  wholesale jerseys  {
header('Content-Description: File  Hamburg  Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment;  Fylde  filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control:  cheap jerseys  must-revalidate');
header('Pragma:  ?????  public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
?>

这个demo其实官方手册里面readfile函数的demo,我没有做改动就直接拿了过来给大家做参考!

当然如果我们想对这些下载文件进行进一步的限制,就最好就用第二种方法了,例如非会员用户不给予下载或者其他自己需求都行!

分类: PHP 标签: ,