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.