写一个WEB服务器,若是用file_get_contents从磁盘中读取文件,并发直线降低,用sendfile能够提高性能。可是PHP不支持,开发扩展我又不会,只能靠抄袭PHP扩展源码维持一下生活这样子。php
看一下sendfile的原型:linux
这个函数在linux2.6.3以前的内核,out_fd只能是socket类型。shell
咱们要实现的sendfile的PHP函数原型也差很少,为了简单,我就不要offset这个参数了,并且规定out_fd必须是stream类型的资源,in_fd必须是普通文件类型的资源:服务器
mixed sendfile(resource $out_fd, resource $in_fd, int $count);
复制代码
生成开发骨架,怎么办,不会,Google一下,好像运行个命令就能够了:php7
php ./ext_skel.php --ext church
cd church
复制代码
我用的php7.3版本,好像无需手动去注释,也好,省事。按照网上的教程,无论三七二十一,先复制一份PHP_FUNCTION(sendfile)
.并发
PHP_FUNCTION(sendfile)
{
}
复制代码
接下来咋办?我又不会,只能看看别人怎么搞的,到ext里面找找,好像都得先接收传过来的变量。唉,试试吧,我又不会能怎么办。socket
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);
}
复制代码
连猜带蒙(人家的宏名字取得多好,跟读英文似的),这一堆宏应该就是用来接收变量。tcp
你看看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 **) &in_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.0
Host: 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
./configure
make
make test #跑一下单元测试
复制代码
好开心,竟然没问题.
sudo make install #安装
复制代码
成功运用到本身玩的项目中,抄袭完成。