php协程curl,Swoole协程版curl源码剖析

< 一. 背景描述

2019年6月5日,Swoole作者宣布在4.4版本后开始初步支持协程版本的Curl。我在原来的研究分析中指出PHP内核对于curl部分的支持是建立在libcurl库上,swoole作者也表达因为PHP内核建立在libcurl基础上,造成了swoole无法直接对于libcurl内部的socket操作进行钩子化。那么,最新版的swoole是采用什么思路实现curl的协程化。

总体实现思路 : LibCurl的Socket操作不能Hook,那就Hook住PHP内核的curl函数组。

二. 知识背景

由于对于Swoole内核源码进行跟踪的过程中,会涉及到一些基础知识,如果在自行阅读源码中有阻碍的时候,可以补充一下相关知识:函数指针

位运算

哈希表

函数跳转表

三. 源码跟踪

3.1 PHP样例代码

为了带大家更好的进入这部分内核,我先提供一份swoole作者提供的PHP样例代码:

PHP

Copy

上面的代码执行结果如下:

177173 bytes

177173 bytes

177173 bytes

177173 bytes

177173 bytes

177173 bytes

177173 bytes

177173 bytes

177173 bytes

177173 bytes

Shell

Copy

通过查看上面的样例代码,我们可以看出Swoole运行时通过设置SWOOLE_HOOK_CURL常量来启动curl部分的运行时协程化。

3.2 Runtime初始化跟踪

我们首先跟踪SwooleRuntime模块里的enableCoroutine部分,这个函数的主要功能就是设置swoole运行时内部哪些部分启动协程化,我先提供这部分的关键代码:

1268 static PHP_METHOD(swoole_runtime, enableCoroutine)

1269 {

1270 zval *zflags = nullptr;

1271 /*TODO: enable SW_HOOK_CURL by default after curl handler completed */

1272 zend_long flags = SW_HOOK_ALL ^ SW_HOOK_CURL;

1273

1274 ZEND_PARSE_PARAMETERS_START(0, 2)

1275 Z_PARAM_OPTIONAL

1276 Z_PARAM_ZVAL(zflags) // or zenable

1277 Z_PARAM_LONG(flags)

1278 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);

1279

1280 if (zflags)

1281 {

1282 if (Z_TYPE_P(zflags) == IS_LONG)

1283 {

1284 flags = SW_MAX(0, Z_LVAL_P(zflags));

1285 }

1286 else if (ZVAL_IS_BOOL(zflags))

1287 {

1288 if (!Z_BVAL_P(zflags))

1289 {

1290 flags = 0;

1291 }

1292 }

1293 else

1294 {

1295 const char *space, *class_name = get_active_class_name(&space);

1296 zend_type_error(“%s%s%s() expects parameter %d to be %s, %s given”, class_name, space, get_active_function_name(), 1, “bool or long”, zend_zval_type_name(zflags));

1297 }

1298 }

1299

1300 RETURN_BOOL(PHPCoroutine::enable_hook(flags));

1301 }

C

Copy

这个函数的主要功能是准备需要被内核HOOK的模块常量,也就是代码中的flags参数,相关具体解释如下:

1272行:从SW_HOOK_ALL常量中去除SW_HOOK_CURL常量所标志的bit位,代表默认不开启Curl部分

1274-1278行:解析PHP代码中函数调用传进的参数,参数数量最小为0、最大为2,均为可选参数。

1280-1298行:解析zflags类型,如果是布尔的false则把flags设置为0从而关闭所有协程模块。

1300行:这是非常关键的一行,这一行是在准备好flags参数之后进行协程相关函数钩子的设置。

3.3 协程钩子跟踪

Swoole的协程是一个异步并发模型,但是PHP内核的很多函数是同步模式,而且不能在并发模型中达到线程安全,所以swoole底层设计了钩子机制来拦截这些内核函数,把这些内核函数做到无缝运行时替换,从而把一些同步操变成协程调度的异步IO。

这里,我先把关键的代码贴出来,目前只贴关于这次添加的Curl部分。

1209 if (flags & SW_HOOK_CURL)

1210 {

1211 if (!(hook_flags & SW_HOOK_CURL))

1212 {

1213 replace_internal_function(ZEND_STRL(“curl_init”));

1214 replace_internal_function(ZEND_STRL(“curl_setopt”));

1215 replace_internal_function(ZEND_STRL(“curl_exec”));

1216 replace_internal_function(ZEND_STRL(“curl_setopt_array”));

1217 replace_internal_function(ZEND_STRL(“curl_error”));

1218 replace_internal_function(ZEND_STRL(“curl_getinfo”));

1219 replace_internal_function(ZEND_STRL(“curl_errno”));

1220 replace_internal_function(ZEND_STRL(“curl_close”));

1221 replace_internal_function(ZEND_STRL(“curl_reset”));

1222 }

1223 }

C

Copy

上面的代码可以生动的展现,目前swoole针对了9种PHP内核的Curl函数做了内核替换,这些函数在运行时都将不再走PHP内核的函数模板,而是走Swoole内核自定义的内核函数。

3.4 内核钩子实现跟踪

在上一节中,我们已经看到swoole内核会把相关内核函数做runtime替换,接下来我来仔细分析这部分内核替换原理,我先贴出核心代码:

1665 static void replace_internal_function(const char *name, size_t l_name)

1666 {

1667 real_func *rf = (real_func *) zend_hash_str_find_ptr(function_table, name, l_name);

1668 if (rf)

1669 {

1670 rf->function->internal_function.handler = PHP_FN(_user_func_handler);

1671 return;

1672 }

1673

1674 zend_function *zf = (zend_function *) zend_hash_str_find_ptr(EG(function_table), name, l_name);

1675 if (zf == nullptr)

1676 {

1677 return;

1678 }

1679

1680 rf = (real_func *) emalloc(sizeof(real_func));

1681 char func[128];

1682 memcpy(func, ZEND_STRL(“swoole_”));

1683 memcpy(func + 7, zf->common.function_name->val, zf->common.function_name->len);

1684

1685 ZVAL_STRINGL(&rf->name, func, zf->common.function_name->len + 7);

1686

1687 char *func_name;

1688 zend_fcall_info_cache *func_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache));

1689 if (!sw_zend_is_callable_ex(&rf->name, NULL, 0, &func_name, NULL, func_cache, NULL))

1690 {

1691 swoole_php_fatal_error(E_ERROR, “function ‘%s’ is not callable”, func_name);

1692 return;

1693 }

1694 efree(func_name);

1695

1696 rf->function = zf;

1697 rf->ori_handler = zf->internal_function.handler;

1698 zf->internal_function.handler = PHP_FN(_user_func_handler);

1699 rf->fci_cache = func_cache;

1700

1701 zend_hash_add_ptr(function_table, zf->common.function_name, rf);

1702 }

1779 static PHP_FUNCTION(_user_func_handler)

1780 {

1781 zend_fcall_info fci;

1782 fci.size = sizeof(fci);

1783 fci.object = NULL;

1784 fci.function_name = {{0}};

1785 fci.retval = return_value;

1786 fci.param_count = ZEND_NUM_ARGS();

1787 fci.params = ZEND_CALL_ARG(execute_data, 1);

1788 fci.no_separation = 1;

1789

1790 real_func *rf = (real_func *) zend_hash_find_ptr(function_table, execute_data->func->common.function_name);

1791 zend_call_function(&fci, rf->fci_cache);

1792 }

1793

C

Copy

这部分代码比较复杂,所以我主要给大家阐述这种内核替换的主要流程,具体代码阐述如下:

swoole在runtime内部维护了一个函数哈希表,叫做function_table。

1667-1672行:swoole内核function_table如果已经存在函数,则构建函数句柄并返回。

1674-1678行:从PHP内核全局函数表中查找是否已经存在函数,如果不存在则返回。

1680-1700行:根据从内核函数表中查询到的函数名,构造函数模板,新的函数名已swoole_开头。

1701行:向swoole内核的函数表中添加函数模板,函数名为内核函数名,函数栈模板使用swoole内核创建的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值