PHP源码分析-流的实现之普通http流的处理

PHP源码分析-流的实现之普通http协议的解析


前一章处理说完标输入流的处理,这章看下php是如何针对普通的http网络流建立连接的.

php_register_url_stream_wrapper("http", &php_stream_http_wrapper);

类似上一章的方法搜索

//首先是
PHPAPI const php_stream_wrapper php_stream_http_wrapper = {
	&http_stream_wops,
	NULL,
	1 /* is_url */
};
//其次,这可以忽略php_stream_http_stream_stat,因为其定义
static const php_stream_wrapper_ops http_stream_wops = {
	php_stream_url_wrap_http,
	NULL, /* stream_close */
	php_stream_http_stream_stat,
	NULL, /* stat_url */
	NULL, /* opendir */
	"http",
	NULL, /* unlink */
	NULL, /* rename */
	NULL, /* mkdir */
	NULL, /* rmdir */
	NULL
};
//php_stream_http_stream_stat的定义
static int php_stream_http_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb) /* {{{ */
{
	/* one day, we could fill in the details based on Date: and Content-Length:
	 * headers.  For now, we return with a failure code to prevent the underlying
	 * file's details from being used instead. */
	return -1;
}

上面部分追踪到php_stream_url_wrap_http,php_stream_url_wrap_http的实现如下

/* }}} */

php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
{
	php_stream *stream;
	zval headers;
	ZVAL_UNDEF(&headers);

	stream = php_stream_url_wrap_http_ex(
		wrapper, path, mode, options, opened_path, context,
		PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT, &headers STREAMS_CC);

	if (!Z_ISUNDEF(headers)) {
		if (FAILURE == zend_set_local_var_str(
				"http_response_header", sizeof("http_response_header")-1, &headers, 1)) {
			zval_ptr_dtor(&headers);
		}
	}

	return stream;
}
/* }}} */

可以很明显的看出其中的关键是php_stream_url_wrap_http_ex,
这是一个很冗长的函数,其中的php_url_parse在php源码中涉及到url的解析非常通用,下一章专门介绍下这个函数,这个函数的功能是分析url并返回结果(好像说的是废话…)

static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
		const char *path, const char *mode, int options, zend_string **opened_path,
		php_stream_context *context, int redirect_max, int flags,
		zval *response_header STREAMS_DC) /* {{{ */
{
	//省略一堆定义
	resource = php_url_parse(path);
	if (resource == NULL) {
		return NULL;
	}
  //这里直接判断
  if (!zend_string_equals_literal_ci(resource->scheme, "http") &&
		!zend_string_equals_literal_ci(resource->scheme, "https")) {
		//先不关心这里
	}else{
		//常规的http处理
		//省略一些判断处理
		/*此处的函数spprintf由于笔者的编译器问题这个函数无法定位到实现,
		但是可以推测这个函数的意思是将后续的内容格式化并赋值给transport_string,并返回长度.*/
		transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", ZSTR_VAL(resource->host), resource->port);
	}
	//省略一些其他的内容
	//出现了出现了就是这里
	stream = php_stream_xport_create(transport_string, transport_len, options,
			STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
			NULL, &timeout, context, &errstr, NULL);
	//省略其他的很多很多的内容
}

在spprintf函数出也可以看出http最终是被转换成了ssl或者tcp来处理了,以Linux为例socket的函数簇创建和连接网络的函数分别是socket和connect,我们的这次的目的就是找到应用这两个函数的地方.

#define php_stream_xport_create(name, namelen, options, flags, persistent_id, timeout, context, estr, ecode) \
	_php_stream_xport_create(name, namelen, options, flags, persistent_id, timeout, context, estr, ecode STREAMS_CC)

PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, int options,
		int flags, const char *persistent_id,
		struct timeval *timeout,
		php_stream_context *context,
		zend_string **error_string,
		int *error_code
		STREAMS_DC)
{
	//省略其他内容,
	//寻找到适合tcp的实例,这可以看到xport_hash和url_stream_wrappers_hash变量有着异曲同工的处理方式
	if (protocol) {
			if (NULL == (factory = zend_hash_str_find_ptr(&xport_hash, protocol, n))) {
				char wrapper_name[32];
	
				if (n >= sizeof(wrapper_name))
					n = sizeof(wrapper_name) - 1;
				PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
	
				ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
						wrapper_name);
	
				return NULL;
			}
		}
	//省略其他
	//创建流
	stream = (factory)(protocol, n,
			(char*)name, namelen, persistent_id, options, flags, timeout,
			context STREAMS_REL_CC);
	//省略其他
	//以客户端模式连接流
			if (-1 == php_stream_xport_connect(stream, name, namelen,
							flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
							timeout, &error_text, error_code)) {

					ERR_RETURN(error_string, error_text, "connect() failed: %s");

					failed = 1;
				}
	//继续省略其他
}

这里看到了流实现的查找创建和连接均在这里了
继续深挖代码

//xport_hash的注册实现
PHPAPI int php_stream_xport_register(const char *protocol, php_stream_transport_factory factory)
{
	zend_string *str = zend_string_init_interned(protocol, strlen(protocol), 1);

	zend_hash_update_ptr(&xport_hash, str, factory);
	zend_string_release_ex(str, 1);
	return SUCCESS;
}
//xport_hash的注册初始化
int php_init_stream_wrappers(int module_number)
{
	le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
	le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);

	/* Filters are cleaned up by the streams they're attached to */
	le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);

	zend_hash_init(&url_stream_wrappers_hash, 8, NULL, NULL, 1);
	zend_hash_init(php_get_stream_filters_hash_global(), 8, NULL, NULL, 1);
	zend_hash_init(php_stream_xport_get_hash(), 8, NULL, NULL, 1);

	return (php_stream_xport_register("tcp", php_stream_generic_socket_factory) == SUCCESS
			&&
			php_stream_xport_register("udp", php_stream_generic_socket_factory) == SUCCESS
#if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__))
			&&
			php_stream_xport_register("unix", php_stream_generic_socket_factory) == SUCCESS
			&&
			php_stream_xport_register("udg", php_stream_generic_socket_factory) == SUCCESS
#endif
		) ? SUCCESS : FAILURE;
}

//初始化调用
int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint32_t num_additional_modules)
{
	if (php_init_stream_wrappers(module_number) == FAILURE)	{
			php_printf("PHP:  Unable to initialize stream url wrappers.\n");
			return FAILURE;
		}
}

这里xport_hash的初始化已经完成,而我们需要关心的则是php_stream_generic_socket_factory的初始化

PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen,
		const char *resourcename, size_t resourcenamelen,
		const char *persistent_id, int options, int flags,
		struct timeval *timeout,
		php_stream_context *context STREAMS_DC)
{
	//省略

	/* tcp流的实现 */
	if (strncmp(proto, "tcp", protolen) == 0) {
		ops = &php_stream_socket_ops;
	} 
	//省略
	//此处的一些stream的赋值没有实际的处理暂时可以不关心
	//在省略
}
//关注php_stream_socket_ops这里是tcp流实现的核心部分
const php_stream_ops php_stream_socket_ops = {
	php_sockop_write, php_sockop_read,
	php_sockop_close, php_sockop_flush,
	"tcp_socket",
	NULL, /* seek */
	php_sockop_cast,
	php_sockop_stat,
	php_tcp_sockop_set_option,
};

在见文知意这方面php做的可能不是很好也可能是抽象的需要,
一眼望去这里并没有初始化和连接远程服务的方法,那么php转来转去,到底是怎么实现的呢.
答案就在php_tcp_sockop_set_option里边
回头看php_stream_xport_connect

PHPAPI int php_stream_xport_connect(php_stream *stream,
		const char *name, size_t namelen,
		int asynchronous,
		struct timeval *timeout,
		zend_string **error_text,
		int *error_code
		)
{
	php_stream_xport_param param;
	int ret;

	memset(&param, 0, sizeof(param));
	param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
	param.inputs.name = (char*)name;
	param.inputs.namelen = namelen;
	param.inputs.timeout = timeout;

	param.want_errortext = error_text ? 1 : 0;

	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);

	if (ret == PHP_STREAM_OPTION_RETURN_OK) {
		if (error_text) {
			*error_text = param.outputs.error_text;
		}
		if (error_code) {
			*error_code = param.outputs.error_code;
		}
		return param.outputs.returncode;
	}

	return ret;

}

这个函数里没有任何其他明显的初始化除去php_stream_set_option,继续看php_stream_set_option,请留意这里的

/*param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;*/
/*PHP_STREAM_OPTION_XPORT_API*/

这两个地方

#define php_stream_set_option(stream, option, value, ptrvalue)	_php_stream_set_option((stream), (option), (value), (ptrvalue))

PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam)
{
	int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
//**就是这里这里调用了tcp的php_tcp_sockop_set_option
	if (stream->ops->set_option) {
		ret = stream->ops->set_option(stream, option, value, ptrparam);
	}

	if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) {
		switch(option) {
			case PHP_STREAM_OPTION_SET_CHUNK_SIZE:
				/* XXX chunk size itself is of size_t, that might be ok or not for a particular case*/
				ret = stream->chunk_size > INT_MAX ? INT_MAX : (int)stream->chunk_size;
				stream->chunk_size = value;
				return ret;

			case PHP_STREAM_OPTION_READ_BUFFER:
				/* try to match the buffer mode as best we can */
				if (value == PHP_STREAM_BUFFER_NONE) {
					stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
				} else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) {
					stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
				}
				ret = PHP_STREAM_OPTION_RETURN_OK;
				break;

			default:
				;
		}
	}

	return ret;
}

就是其中注释的那个地方 这里调用了tcp工厂实例的php_tcp_sockop_set_option
再来看php_tcp_sockop_set_option

static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam)
{
	php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract;
	php_stream_xport_param *xparam;

	switch(option) {
		case PHP_STREAM_OPTION_XPORT_API:
			xparam = (php_stream_xport_param *)ptrparam;

			switch(xparam->op) {
				//op的值STREAM_XPORT_OP_CONNECT
				case STREAM_XPORT_OP_CONNECT:
				case STREAM_XPORT_OP_CONNECT_ASYNC:
					xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam);
					return PHP_STREAM_OPTION_RETURN_OK;

				case STREAM_XPORT_OP_BIND:
					xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam);
					return PHP_STREAM_OPTION_RETURN_OK;


				case STREAM_XPORT_OP_ACCEPT:
					xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC);
					return PHP_STREAM_OPTION_RETURN_OK;
				default:
					/* fall through */
					;
			}
	}
	return php_sockop_set_option(stream, option, value, ptrparam);
}

继续往下找

static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock,
		php_stream_xport_param *xparam)
{
	char *host = NULL, *bindto = NULL;
	int portno, bindport = 0;
	int err = 0;
	int ret;
	zval *tmpzval = NULL;
	long sockopts = STREAM_SOCKOP_NONE;

#ifdef AF_UNIX
	if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {
		struct sockaddr_un unix_addr;
		//出现了出现了期待的socket出现了
		sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);

		if (sock->socket == SOCK_ERR) {
			if (xparam->want_errortext) {
				xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket");
			}
			return -1;
		}

		parse_unix_address(xparam, &unix_addr);
		//链接的地方
		ret = php_network_connect_socket(sock->socket,
				(const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
				xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout,
				xparam->want_errortext ? &xparam->outputs.error_text : NULL,
				&err);

		xparam->outputs.error_code = err;

		goto out;
	}
#endif
}

这里就是初始化网络指针(还是不想用句柄这个概念)的地方

socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0);

接着找到连接的地方

PHPAPI int php_network_connect_socket(php_socket_t sockfd,
		const struct sockaddr *addr,
		socklen_t addrlen,
		int asynchronous,
		struct timeval *timeout,
		zend_string **error_string,
		int *error_code)
{
	php_non_blocking_flags_t orig_flags;
	int n;
	int error = 0;
	socklen_t len;
	int ret = 0;

	SET_SOCKET_BLOCKING_MODE(sockfd, orig_flags);
	//就是这里了
	if ((n = connect(sockfd, addr, addrlen)) != 0) {
		error = php_socket_errno();

		if (error_code) {
			*error_code = error;
		}

		if (error != EINPROGRESS) {
			if (error_string) {
				*error_string = php_socket_error_str(error);
			}

			return -1;
		}
		if (asynchronous && error == EINPROGRESS) {
			/* this is fine by us */
			return 0;
		}
	}

	if (n == 0) {
		goto ok;
	}
	//省略其他
}

绕来绕去总归又回到了socket和connect,其他几种操作这里不写了有兴趣的可以自己看看源码,
tcp的经典四层在这里也不介绍了,大家可以百度其他人的文章或者自己看看<<TCP/IP协议详解 卷一>>这本书,这本书很经典30年前的技术,构成了当今互联网的琳琅满目.笔者把这本书至少重头到尾看了4次.受益匪浅.
不出意外后续还会有许多章节,争取把php分析的七七八八的.
如有错误欢迎大家指正.(^^)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值