大文件快速复制stream_copy_to_strea

大文件快速复制

今天看到一篇文章,说是大文件复制,使用该函数stream_copy_to_stream。然后看了一下手册,通过探究,其底层,实际上跟copy差不多。

有篇文章说过,socket_开头的函数,都是c语言底层库提供的函数,php只是简单进行封装了,保留它,只要是底层函数,有更强的描述能力;而stream类的函数,都是php内部,对其进行了抽象封装,提供了更方便的接口。

查看

stream_copy_to_stream

示例:

$f1 = fopen("txt.txt",'r');
$f2 = fopen("txt1.txt",'w+');
stream_copy_to_stream( $f1,$f2);
fclose($f1);
fclose($f2);

大概该函数,跟nodejs说的pipe,go语言中的,io.Copy差不多功能吧。

探究

由于都是标准库,所以代码都在./ext/standard目录下。

首先看,basic_functions.c文件,该文件里面,包含了绝大多数的函数原型。

第3151行左右,看看到了如下:

PHP_FE(copy,	arginfo_copy)

其上,有注释,说明该函数的实现是在file.c中。

第3181行:

PHP_FE(stream_copy_to_stream,arginfo_stream_copy_to_stream)

实现在ext\standard\streamsfuncs.c中。

  • copy

    PHP_FUNCTION(copy)
    {
    	char *source, *target;
    	size_t source_len, target_len;
    	zval *zcontext = NULL;
    	php_stream_context *context;
    
    	ZEND_PARSE_PARAMETERS_START(2, 3)
    		Z_PARAM_PATH(source, source_len)
    		Z_PARAM_PATH(target, target_len)
    		Z_PARAM_OPTIONAL
    		Z_PARAM_RESOURCE_EX(zcontext, 1, 0)
    	ZEND_PARSE_PARAMETERS_END();
    
    	if (php_check_open_basedir(source)) {
    		RETURN_FALSE;
    	}
    
    	context = php_stream_context_from_zval(zcontext, 0);
    
    	if (php_copy_file_ctx(source, target, 0, context) == SUCCESS) {
    		RETURN_TRUE;
    	} else {
    		RETURN_FALSE;
    	}
    }
    
    PHPAPI int php_copy_file_ctx(const char *src, const char *dest, int src_flg, php_stream_context *ctx)
    {
    	php_stream *srcstream = NULL, *deststream = NULL;
    	int ret = FAILURE;
    	php_stream_statbuf src_s, dest_s;
    
    	switch (php_stream_stat_path_ex(src, 0, &src_s, ctx)) {
    		case -1:
    			/* non-statable stream */
    			goto safe_to_copy;
    			break;
    		case 0:
    			break;
    		default: /* failed to stat file, does not exist? */
    			return ret;
    	}
    	if (S_ISDIR(src_s.sb.st_mode)) {
    		php_error_docref(NULL, E_WARNING, "The first argument to copy() function cannot be a directory");
    		return FAILURE;
    	}
    
    	switch (php_stream_stat_path_ex(dest, PHP_STREAM_URL_STAT_QUIET | PHP_STREAM_URL_STAT_NOCACHE, &dest_s, ctx)) {
    		case -1:
    			/* non-statable stream */
    			goto safe_to_copy;
    			break;
    		case 0:
    			break;
    		default: /* failed to stat file, does not exist? */
    			return ret;
    	}
    	if (S_ISDIR(dest_s.sb.st_mode)) {
    		php_error_docref(NULL, E_WARNING, "The second argument to copy() function cannot be a directory");
    		return FAILURE;
    	}
    	if (!src_s.sb.st_ino || !dest_s.sb.st_ino) {
    		goto no_stat;
    	}
    	if (src_s.sb.st_ino == dest_s.sb.st_ino && src_s.sb.st_dev == dest_s.sb.st_dev) {
    		return ret;
    	} else {
    		goto safe_to_copy;
    	}
    no_stat:
    	{
    		char *sp, *dp;
    		int res;
    
    		if ((sp = expand_filepath(src, NULL)) == NULL) {
    			return ret;
    		}
    		if ((dp = expand_filepath(dest, NULL)) == NULL) {
    			efree(sp);
    			goto safe_to_copy;
    		}
    
    		res =
    #ifndef PHP_WIN32
    			!strcmp(sp, dp);
    #else
    			!strcasecmp(sp, dp);
    #endif
    
    		efree(sp);
    		efree(dp);
    		if (res) {
    			return ret;
    		}
    	}
    safe_to_copy:
    
    	srcstream = php_stream_open_wrapper_ex(src, "rb", src_flg | REPORT_ERRORS, NULL, ctx);
    
    	if (!srcstream) {
    		return ret;
    	}
    
    	deststream = php_stream_open_wrapper_ex(dest, "wb", REPORT_ERRORS, NULL, ctx);
    
    	if (srcstream && deststream) {
    		ret = php_stream_copy_to_stream_ex(srcstream, deststream, PHP_STREAM_COPY_ALL, NULL);
    	}
    	if (srcstream) {
    		php_stream_close(srcstream);
    	}
    	if (deststream) {
    		php_stream_close(deststream);
    	}
    	return ret;
    }
  • stream_copy_to_stream

    PHP_FUNCTION(stream_copy_to_stream)
    {
    	php_stream *src, *dest;
    	zval *zsrc, *zdest;
    	zend_long maxlen = PHP_STREAM_COPY_ALL, pos = 0;
    	size_t len;
    	int ret;
    
    	ZEND_PARSE_PARAMETERS_START(2, 4)
    		Z_PARAM_RESOURCE(zsrc)
    		Z_PARAM_RESOURCE(zdest)
    		Z_PARAM_OPTIONAL
    		Z_PARAM_LONG(maxlen)
    		Z_PARAM_LONG(pos)
    	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
    
    	php_stream_from_zval(src, zsrc);
    	php_stream_from_zval(dest, zdest);
    
    	if (pos > 0 && php_stream_seek(src, pos, SEEK_SET) < 0) {
    		php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", pos);
    		RETURN_FALSE;
    	}
    
    	ret = php_stream_copy_to_stream_ex(src, dest, maxlen, &len);
    
    	if (ret != SUCCESS) {
    		RETURN_FALSE;
    	}
    	RETURN_LONG(len);
    }

上面代码,发现:

底层都是调用下面的函数。

php_stream_copy_to_stream_ex