怎麼樣「抄「一個PHP擴展

寫一個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直譯過來就是結束解析參數

至於前面的ZENDZ,你還不容許人家加個前綴,表示這宏是人家命名的呀?

根據咱們以前的分析,前兩個用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_streamphp_stream_cast轉成STDIO。再調用filenostream資源轉成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 #安裝 
複製代碼

成功運用到本身玩的項目中,抄襲完成。

相關文章
相關標籤/搜索