Writing a WEB server, if using file_get_contents to read files from disk and concurrency plummets, using SendFile can improve performance. But PHP does not support, and I do not know how to develop extensions, so I have to make a living by copying the source code of PHP extensions.
Take a look at the sendFile prototype:
This function can only be of socket type in Linux kernels prior to 2.6.3.
The PHP prototype we are implementing for sendFile is similar. For simplicity, I will omit offset and specify that out_fd must be a stream and in_fd must be a normal file:
mixed sendfile(resource $out_fd, resource $in_fd, int $count);
Copy the code
Generate the skeleton, how to do, no, Google, it seems to run a command is ok:
php ./ext_skel.php --ext church
cd church
Copy the code
I use the VERSION of PHP7.3, it seems that there is no need to manually comment, good, save trouble. Make a copy of PHP_FUNCTION(sendFile) by following the tutorial online.
PHP_FUNCTION(sendfile)
{
}
Copy the code
What’s next? I don’t know how to do that. I have to look in ext to see if I have to receive the variable first. Oh, try. I can’t do anything.
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);
}
Copy the code
This is a bunch of macros that are supposed to receive variables.
Take a look at PARSE_PARAMETERS_START, which literally means to start parsing parameters. For its two parameters, go to the macro definition
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args) \
ZEND_PARSE_PARAMETERS_START_EX(0, min_num_args, max_num_args)
Copy the code
Perfectly named, this macro requires the minimum and maximum number of arguments. The minimum number of parameters is the required number of parameters, isn’t it? Isn’t the maximum number of parameters the total number of required + optional parameters?
PARAM_RESOURCE literally translates to the parameter of the resource type
PARAM_LONG literally translates to an integer parameter
PARAM_OPTIONAL literally translates as optional
PARSE_PARAMETERS_END literally translates to end parsing parameters
“ZEND” and “Z” are not allowed to use a prefix to indicate that the macro was named by someone else.
According to our previous analysis, the first two are bounded by zval and count by zend_long.
What’s next? Aren’t we going to call SendFile? Write the C sendfile call first, returning the length on success or false on failure.
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; }}Copy the code
And then what? Zval fd = zval fd = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD = zval FD
Flip and flip… C PHP_FUNCTION(socket_import_stream) to convert zval to 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; }...Copy the code
OK, open copy.
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; }}Copy the code
Convert zval to PHp_stream, and convert php_stream to STDIO with php_stream_cast. Call fileno to convert the stream resource to a file descriptor of type int.
Php_stream_is determines whether php_stream is a stream of the specified type. Php_stream_cast stream conversion function.
Good move. I think we’re getting there.
What’s next? I don’t know how to play. I’ll go see how they play. It seems to configure parameter information and so on.
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()
Copy the code
Also add the function to the function entity structure:
static const zend_function_entry church_functions[] = {
PHP_FE(sendfile, arginfo_sendfile)
PHP_FE_END
};
Copy the code
When we write a PHP feature, we often run a unit test to verify that the feature is working as expected. There is a test directory in the root directory of our extension. I have no choice but to steal the tests directory from another ext.
Let’s create a new request. TXT with the following contents
GET/HTTP/1.0 Host: 127.0.0.1Copy the code
Note the HTTP protocol format, the following line feed is also content
--TEST--
When OutFd is SOCKET, InFd is STDIO
--SKIPIF--
stream_socket_client("TCP: / / 127.0.0.1:80", $errno, $errstr);
if ($errno) {
echo 'skip';
}
? >
--FILE--
$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)
Copy the code
Wow, I think we’re almost there. Good chicken action. Compile the four steps:
Phpize./configure make make test #Copy the code
I’m so glad it’s okay.
Sudo make install #Copy the code
Successful use of their own play in the project, copy completed.