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(¶m, 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, ¶m);
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分析的七七八八的.
如有错误欢迎大家指正.(^▽^)