寫一個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 church
cd 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 **) &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 #安裝
複製程式碼
成功運用到自己玩的專案中,抄襲完成。