最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

怎么样“抄“一个PHP扩展

XAMPP案例 中文小张 738浏览 0评论

 

写一个WEB服务器,如果用file_get_contents从磁盘中读取文件,并发直线下降,用sendfile可以提升性能。但是PHP不支持,开发扩展我又不会,只能靠抄袭PHP扩展源码维持一下生活这样子。

看一下sendfile的原型:

这个函数在linux2.6.3之前的内核,out_fd只能是socket类型。

我们要实现的sendfile的PHP函数原型也差不多,为了简单,我就不要offset这个参数了,而且规定out_fd必须是stream类型的资源,in_fd必须是普通文件类型的资源:

mixed sendfile(resource $out_fd, resource $in_fd, int $count);

生成开发骨架,怎么办,不会,Google一下,好像运行个命令就可以了:

php ./ext_skel.php --ext churchcd church

我用的php7.3版本,好像无需手动去注释,也好,省事。按照网上的教程,不管三七二十一,先复制一份 PHP_FUNCTION(sendfile) .

PHP_FUNCTION(sendfile){    }

接下来咋办?我又不会,只能看看别人怎么搞的,到ext里面找找,好像都得先接收传过来的变量。唉,试试吧,我又不会能怎么办。

PHP_FUNCTION(sendfile){    zval *out;    zval *in;    zend_long count = 0;        ZEND_PARSE_PARAMETERS_START(3, 3)        Z_PARAM_RESOURCE(out)        Z_PARAM_RESOURCE(in)        Z_PARAM_LONG(count)    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);}

连猜带蒙(人家的宏名字取得多好,跟读英文似的),这一堆宏应该就是用来接收变量。

你看看 PARSE_PARAMETERS_START 直译过来就是开始解析参数, 至于它的两个参数,你去这个宏定义的地方看看

#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args) 	ZEND_PARSE_PARAMETERS_START_EX(0, min_num_args, max_num_args)

完美的命名,这个宏要求的最小参数个数和最大参数个数。这个很容易就联想到,最小参数个数不就是必填参数个数么?最大参数个数不就是必填+选填个数总数么?

PARAM_RESOURCE 直译过来就是资源类型的参数

PARAM_LONG 直译过来就是整型参数

PARAM_OPTIONAL 直译过来就是可选的

PARSE_PARAMETERS_END 直译过来就是结束解析参数

至于前面的 ZEND 和 Z ,你还不允许人家加个前缀,表示这宏是人家命名的呀?

根据我们之前的分析,前两个用zval接,count用zend_long接。

接下来怎么玩?我们不是要调用sendfile吗?不管三七二十一,先把C语言的sendfile函数调用写上去,如果成功就返回写入的长度,失败就返回false.

PHP_FUNCTION(sendfile){    zval *out;    zval *in;    zend_long count = 0;    int final_out_fd;    int final_in_fd;        ZEND_PARSE_PARAMETERS_START(2, 3)        Z_PARAM_RESOURCE(out)        Z_PARAM_RESOURCE(in)        Z_PARAM_OPTIONAL        Z_PARAM_LONG(count)    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);        ret = sendfile(final_out_fd, final_in_fd, NULL, count);    if (ret > 0) {    	RETURN_LONG(ret);    } else {    	RETURN_FALSE;    }}

然后呢?想办法把zval类型变成int类型的fd,怎么变呢,我又不会,只能继续发挥拿来主义精神,去ext找找看人家是怎么玩的。

翻啊翻 。。。。终于在ext/sockets/sockets.c的 PHP_FUNCTION(socket_import_stream) 中找到把zval转成int类型的方法.

PHP_FUNCTION(socket_import_stream){	zval				 *zstream;	php_stream			 *stream;	PHP_SOCKET			 socket; /* fd */		php_stream_from_zval(stream, zstream);	if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1)) {		/* error supposedly already shown */		RETURN_FALSE;	}		...

OK,开抄。

PHP_FUNCTION(sendfile){	zval *out;    zval *in;    php_stream *i;    php_stream *o;    zend_long count = 0;    FILE *in_fd;    PHP_SOCKET out_fd;    int final_out_fd;    int final_in_fd;    unsigned int ret;    ZEND_PARSE_PARAMETERS_START(3, 3)        Z_PARAM_RESOURCE(out)        Z_PARAM_RESOURCE(in)        Z_PARAM_LONG(count)    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);    php_stream_from_zval(o, out);    if (php_stream_cast(o, PHP_STREAM_AS_SOCKETD, (void**)&out_fd, 1)) {        /* error supposedly already shown */        RETURN_FALSE;    }    final_out_fd = out_fd;    php_stream_from_zval(i, in);    if (php_stream_cast(i, PHP_STREAM_AS_STDIO, (void **) ∈_fd, 1)) {        RETURN_FALSE;    }    final_in_fd = fileno(in_fd);    ret = sendfile(final_out_fd, final_in_fd, NULL, count);    if (ret > 0) {    	RETURN_LONG(ret);    } else {    	RETURN_FALSE;    }}

先把 zval 转成 php_stream ,再把 php_stream 用 php_stream_cast 转成 STDIO 。再调用 fileno 把 stream 资源转成int类型的文件描述符。

这几个函数都不用我解释,人家的命名太完美了, php_stream_is 判断 php_stream 是不是指定类型的流。 php_stream_cast 流转换函数。

好鸡动,是不是快成功了。

接下来怎么玩?我又不会了,还是去看看人家怎么玩的吧。好像要配置参数信息之类的,连猜带蒙。

ZEND_BEGIN_ARG_INFO_EX(arginfo_sendfile, 0, 0, 3)    ZEND_ARG_INFO(0, out)    ZEND_ARG_INFO(0, in)    ZEND_ARG_INFO(0, count)ZEND_END_ARG_INFO()

还要把函数加到函数实体结构体里面:

static const zend_function_entry church_functions[] = {	PHP_FE(sendfile,		arginfo_sendfile)	PHP_FE_END};

收功,我们写完PHP的一个功能,往往会跑个单元测试,来验证这个功能是不是达到我们的预期。刚好看到我们的扩展根目录有个tests目录,没办法,我又不会,只能再去别的ext里面偷师。

先新建一个request.txt,里面的内容是

GET / HTTP/1.0Host: 127.0.0.1

注意一下http协议格式,后面的换行也是内容

--TEST--When OutFd is SOCKET, InFd is STDIO--SKIPIF--<?php stream_socket_client("tcp://127.0.0.1:80", $errno, $errstr);if ($errno) {	echo 'skip';}?>--FILE--<?php$socket = stream_socket_client("tcp://127.0.0.1:80", $errno, $errstr);$fd = fopen('tests/request.txt', 'r');sendfile($socket, $fd, filesize('tests/request.txt'));$response = '';while (!feof($socket)) {    $response .= fgets($socket, 1024);}fclose($fd);fclose($socket);var_dump(strpos($response, '200') !== false);?>--EXPECT--bool(true)

哇,应该快好了吧。好鸡动。赶紧编译四步曲:

phpize./configuremakemake test   #跑一下单元测试

好开心,居然没问题.

sudo make install #安装

成功运用到自己玩的项目中,抄袭完成。

写一个WEB服务器,如果用file_get_contents从磁盘中读取文件,并发直线下降,用sendfile可以提升性能。但是PHP不支持,开发扩展我又不会,只能靠抄袭PHP扩展源码维持一下生活这样子。

看一下sendfile的原型:

这个函数在linux2.6.3之前的内核,out_fd只能是socket类型。

我们要实现的sendfile的PHP函数原型也差不多,为了简单,我就不要offset这个参数了,而且规定out_fd必须是stream类型的资源,in_fd必须是普通文件类型的资源:

mixed sendfile(resource $out_fd, resource $in_fd, int $count);

生成开发骨架,怎么办,不会,Google一下,好像运行个命令就可以了:

php ./ext_skel.php --ext churchcd church

我用的php7.3版本,好像无需手动去注释,也好,省事。按照网上的教程,不管三七二十一,先复制一份 PHP_FUNCTION(sendfile) .

PHP_FUNCTION(sendfile){    }

接下来咋办?我又不会,只能看看别人怎么搞的,到ext里面找找,好像都得先接收传过来的变量。唉,试试吧,我又不会能怎么办。

PHP_FUNCTION(sendfile){    zval *out;    zval *in;    zend_long count = 0;        ZEND_PARSE_PARAMETERS_START(3, 3)        Z_PARAM_RESOURCE(out)        Z_PARAM_RESOURCE(in)        Z_PARAM_LONG(count)    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);}

连猜带蒙(人家的宏名字取得多好,跟读英文似的),这一堆宏应该就是用来接收变量。

你看看 PARSE_PARAMETERS_START 直译过来就是开始解析参数, 至于它的两个参数,你去这个宏定义的地方看看

#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args) 	ZEND_PARSE_PARAMETERS_START_EX(0, min_num_args, max_num_args)

完美的命名,这个宏要求的最小参数个数和最大参数个数。这个很容易就联想到,最小参数个数不就是必填参数个数么?最大参数个数不就是必填+选填个数总数么?

PARAM_RESOURCE 直译过来就是资源类型的参数

PARAM_LONG 直译过来就是整型参数

PARAM_OPTIONAL 直译过来就是可选的

PARSE_PARAMETERS_END 直译过来就是结束解析参数

至于前面的 ZEND 和 Z ,你还不允许人家加个前缀,表示这宏是人家命名的呀?

根据我们之前的分析,前两个用zval接,count用zend_long接。

接下来怎么玩?我们不是要调用sendfile吗?不管三七二十一,先把C语言的sendfile函数调用写上去,如果成功就返回写入的长度,失败就返回false.

PHP_FUNCTION(sendfile){    zval *out;    zval *in;    zend_long count = 0;    int final_out_fd;    int final_in_fd;        ZEND_PARSE_PARAMETERS_START(2, 3)        Z_PARAM_RESOURCE(out)        Z_PARAM_RESOURCE(in)        Z_PARAM_OPTIONAL        Z_PARAM_LONG(count)    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);        ret = sendfile(final_out_fd, final_in_fd, NULL, count);    if (ret > 0) {    	RETURN_LONG(ret);    } else {    	RETURN_FALSE;    }}

然后呢?想办法把zval类型变成int类型的fd,怎么变呢,我又不会,只能继续发挥拿来主义精神,去ext找找看人家是怎么玩的。

翻啊翻 。。。。终于在ext/sockets/sockets.c的 PHP_FUNCTION(socket_import_stream) 中找到把zval转成int类型的方法.

PHP_FUNCTION(socket_import_stream){	zval				 *zstream;	php_stream			 *stream;	PHP_SOCKET			 socket; /* fd */		php_stream_from_zval(stream, zstream);	if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1)) {		/* error supposedly already shown */		RETURN_FALSE;	}		...

OK,开抄。

PHP_FUNCTION(sendfile){	zval *out;    zval *in;    php_stream *i;    php_stream *o;    zend_long count = 0;    FILE *in_fd;    PHP_SOCKET out_fd;    int final_out_fd;    int final_in_fd;    unsigned int ret;    ZEND_PARSE_PARAMETERS_START(3, 3)        Z_PARAM_RESOURCE(out)        Z_PARAM_RESOURCE(in)        Z_PARAM_LONG(count)    ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);    php_stream_from_zval(o, out);    if (php_stream_cast(o, PHP_STREAM_AS_SOCKETD, (void**)&out_fd, 1)) {        /* error supposedly already shown */        RETURN_FALSE;    }    final_out_fd = out_fd;    php_stream_from_zval(i, in);    if (php_stream_cast(i, PHP_STREAM_AS_STDIO, (void **) ∈_fd, 1)) {        RETURN_FALSE;    }    final_in_fd = fileno(in_fd);    ret = sendfile(final_out_fd, final_in_fd, NULL, count);    if (ret > 0) {    	RETURN_LONG(ret);    } else {    	RETURN_FALSE;    }}

先把 zval 转成 php_stream ,再把 php_stream 用 php_stream_cast 转成 STDIO 。再调用 fileno 把 stream 资源转成int类型的文件描述符。

这几个函数都不用我解释,人家的命名太完美了, php_stream_is 判断 php_stream 是不是指定类型的流。 php_stream_cast 流转换函数。

好鸡动,是不是快成功了。

接下来怎么玩?我又不会了,还是去看看人家怎么玩的吧。好像要配置参数信息之类的,连猜带蒙。

ZEND_BEGIN_ARG_INFO_EX(arginfo_sendfile, 0, 0, 3)    ZEND_ARG_INFO(0, out)    ZEND_ARG_INFO(0, in)    ZEND_ARG_INFO(0, count)ZEND_END_ARG_INFO()

还要把函数加到函数实体结构体里面:

static const zend_function_entry church_functions[] = {	PHP_FE(sendfile,		arginfo_sendfile)	PHP_FE_END};

收功,我们写完PHP的一个功能,往往会跑个单元测试,来验证这个功能是不是达到我们的预期。刚好看到我们的扩展根目录有个tests目录,没办法,我又不会,只能再去别的ext里面偷师。

先新建一个request.txt,里面的内容是

GET / HTTP/1.0Host: 127.0.0.1

注意一下http协议格式,后面的换行也是内容

--TEST--When OutFd is SOCKET, InFd is STDIO--SKIPIF--<?php stream_socket_client("tcp://127.0.0.1:80", $errno, $errstr);if ($errno) {	echo 'skip';}?>--FILE--<?php$socket = stream_socket_client("tcp://127.0.0.1:80", $errno, $errstr);$fd = fopen('tests/request.txt', 'r');sendfile($socket, $fd, filesize('tests/request.txt'));$response = '';while (!feof($socket)) {    $response .= fgets($socket, 1024);}fclose($fd);fclose($socket);var_dump(strpos($response, '200') !== false);?>--EXPECT--bool(true)

哇,应该快好了吧。好鸡动。赶紧编译四步曲:

phpize./configuremakemake test   #跑一下单元测试

好开心,居然没问题.

sudo make install #安装

成功运用到自己玩的项目中,抄袭完成。

转载请注明:XAMPP中文组官网 » 怎么样“抄“一个PHP扩展

您必须 登录 才能发表评论!