搜索
首页后端开发php教程一次失败的PHP扩展开发之

一次失败的PHP 扩展开发之旅

By warezhou 2014.11.19


缘起

经过不断的持续迭代,我们部门的协程版网络框架(CoSvrFrame)终于出炉了!这本来是件喜大普奔的事情,但是随着新业务的不断接入,很多固有缺陷也逐渐浮出水面:

  • 不支持“TCP连接池”
  • 不支持“Dispatcher-Workers模型”
  • 不支持“过载保护”
  • 不支持“热重启”
  • 不支持“64Bit”
  • ... ...
  • 对于资深后台开发而言,上面罗列的问题大多数都难入法眼,之所以成为问题,很有点“温水煮青蛙”的味道:迭代过程缺乏宏观视野,引入过多业务特性,导致整体架构不合理。最近的“协程版本”最初也是我个人业余之作,仅仅为了能够愉快地写业务代码,为了快点出活,底层直接复用原有SvrFrame,结果可想而知:根基不牢,地动山摇!以最极端的64Bit为例,相信大家秒懂了。

    经过多番调研与讨论,最终我们给出了如下前进方向:

  • 引入公司内部开源的SPP3.0框架,吸收它的基础周边设施,进行业务二次开发
  • 对于SPP进行扩展,支持PHP作为脚本语言进行嵌入式编程,同时以C扩展形式给PHP提供协程能力(从此PHPer也可以愉快地书写异步代码了,妈妈再也不用担心我的callback了!)
  • 叨逼叨?嗦了这么久,下面可以切入主题了:如何实现C++/PHP混合编程?

    免责申明:由于本人属于半路出家,接触PHP扩展开发尚未足周,因此无法深入到WHY,仅能停留在HOW,仅作记录之用,望高手见谅!

    开场

    嵌入式PHP

    业内C++/PHP的结合,一般是出于“性能”考虑,在PHP代码里调用C/C++扩展,从而解决特定的性能瓶颈(如PB序列化等)。

    作为C/C++开发出身,“开发效率”相对于“性能”的诱惑显然更大,因此,我们的思路是:将PHP作为脚本语言,快速开发业务逻辑,插入到SPP框架运行。

    1. 以RTLD_GLOBAL方式打开php动态库

    void *php_handler = dlopen("libphp5.so", RTLD_LAZY | RTLD_GLOBAL);if (!php_handler) {    base->log_.LOG_P_PID(LOG_FATAL, "%s\n", dlerror());    return -1; }   dlclose(php_handler);

    2. 通过php_embed_init进行初始化

    php_embed_module.php_ini_path_override = "../php/php.ini";php_embed_init(0, NULL);

    3. 通过zend_eval_string引入PHP脚本

    zend_first_try {    char exec_str[256];    snprintf(exec_str, sizeof(exec_str), "include '%s';", "../php/demo_handler.php");    if (int ret = zend_eval_string(exec_str, NULL, exec_str TSRMLS_CC)) {        base->log_.LOG_P_PID(LOG_FATAL, "zend_eval_string fail. ret=%d\n", ret);        return -1;     }    base->log_.LOG_P_PID(LOG_DEBUG, "zend_eval_string succ.\n");} zend_catch {    base->log_.LOG_P_PID(LOG_FATAL, "zend_eval_string catch.\n");} zend_end_try ();

    4. 通过call_user_function回调PHP函数

    zval z_funcname;ZVAL_STRING(&z_funcname, "EchoDemo::init", 1);zval *zp_svr;MAKE_STD_ZVAL(zp_svr);ZVAL_LONG(zp_svr, (long)base);zval *zp_etc;MAKE_STD_ZVAL(zp_etc);ZVAL_STRING(zp_etc, etc, 1);zval z_retval;zval *z_params[] = {zp_svr, zp_etc};int call_ret = call_user_function(CG(function_table), NULL, &z_funcname, &z_retval, sizeof(z_params) / sizeof(z_params[0]), z_params TSRM convert_to_long(&z_retval);int func_ret = Z_LVAL_P(&z_retval);zval_ptr_dtor(&zp_etc);zval_dtor(&z_funcname);zval_dtor(&z_retval);if (call_ret log_.LOG_P_PID(LOG_FATAL, "call_user_function fail. call_ret=%d func_ret=%d\n", call_ret, func_ret);    return -1;}

    5. 通过php_embed_shutdown进行清理

    php_embed_shutdown(TSRMLS_C);

    PHP扩展

    网络上关于PHP的C扩展开发文章可以说已经到泛滥的地步了,有兴趣的读者可以深入阅读文末的附录。

    1. 下载php源码包,进行手动编译,为了配合上述嵌入式使用,需要打开?enable-embed选项

    ./configure --enable-embedmakemake install(可选)

    2. 进入php源码包的ext目录,借助ext_skel工具生成插件架子代码

    cd ext./ext_skel --extname=demo

    3. 编辑config.m4,打开PHP_ARG_WITH或者PHP_ARG_ENABLE选项(说实话区别仍没搞清楚,求达人指点),添加C++支持、依赖路径等

    PHP_ARG_ENABLE(demo, whether to enable demo support,    [  --enable-demo           Enable demo support])if test "$PHP_DEMO" != "no"; then  PHP_REQUIRE_CXX()  PHP_ADD_LIBRARY(stdc++, 1, EXTRA_LDFLAGS)  PHP_ADD_INCLUDE(/root/spp/module/include/)  PHP_ADD_INCLUDE(/root/spp/module/include/spp_incl/)  PHP_NEW_EXTENSION(demo, demo.cpp, $ext_shared)fi

    4. 编辑demo.cpp,添加扩展定义和实现(函数、类、变量 ...),这里仅仅给出函数定义示例,类相关的有兴趣的读者自行根据附录摸索。这里给出的sendrecv函数定义比较有代表性,其中第3个参数rsp为引用参数,负责将接收到的数据返回给PHP调用方

    ZEND_BEGIN_ARG_INFO_EX(arginfo_sendrecv, 0, 0, 7)    ZEND_ARG_INFO(0, req)    ZEND_ARG_INFO(0, req_len)    ZEND_ARG_INFO(1, rsp)    ZEND_ARG_INFO(0, rsp_len)    ZEND_ARG_INFO(0, ip)    ZEND_ARG_INFO(0, port)    ZEND_ARG_INFO(0, timeout)ZEND_END_ARG_INFO()PHP_FUNCTION(sendrecv){    char *req = NULL;    int req_str_len = 0;    long req_len = 0;    zval *rsp = NULL;    long rsp_len = 0;    char *ip = NULL;    int ip_str_len = 0;    long port = 0;    long timeout = 0;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slzlsll", &req, &req_str_len,&req_len, &rsp, &rsp_len, &ip, &ip_str_len, &port, &timeout) == FAILURE) {        return;}       struct sockaddr_in addr;    memset(&addr, 0, sizeof(addr));    addr.sin_family = AF_INET;    addr.sin_addr.s_addr = inet_addr(std::string(ip, ip_str_len).c_str());    addr.sin_port = htons(port);    char *rsp_buf = (char *)emalloc(rsp_len);    int rsp_buf_len = rsp_len;    if (int ret = mt_udpsendrcv(&addr, req, req_len > req_str_len ? req_str_len : req_len, rsp_buf, rsp_buf_len, timeout)) {        efree(rsp_buf);        RETURN_LONG(ret);    }    zval_dtor(rsp);    ZVAL_STRINGL(rsp, rsp_buf, rsp_buf_len, 0);    RETURN_LONG(0);}
    const zend_function_entry demo_functions[] = {    PHP_FE(sendrecv, arginfo_sendrecv)    PHP_FE_END  /* Must be the last line in demo_functions[] */};

    5. 一切准备就绪,可以编译扩展了,我个人比较喜欢动态编译(静态编译需要重新编译php源码,太耗时费力),生成的.so位于当前扩展的modules目录下

    /usr/local/bin/phpize./configure --with-php-config=/usr/local/bin/php-configmake

    6. 编辑php.ini文件,添加新的扩展,然后就可以愉快地在PHP代码中调用新扩展了

    extension_dir="/somewhere/modules"extension="demo.so"extension="xxxx.so"

    高潮

    终于到了组装成型的时刻了,通过telnet玩了几把EchoDemo,看到一行一行的回显,不禁心情大好。

    <?php class EchoDemo {        public static function init($server, $conf) {            log_debug($server, "init in php.\n");            return true;        }           public static function input($server, $req, $ext_info = array()) {            log_debug($server, "input in php.\n");            return strlen($req);        }           public static function route($server, $req, $ext_info = array()) {            log_debug($server, "route in php.\n");            return 1;        }           public static function process($server, $req, $ext_info = array()) {            log_debug($server, "process in php.\n");            $ret = sendrecv($req, strlen($req), $rsp, 65535, "127.0.0.1", 2345, 500);            if ($ret != 0) {                log_debug($server, "sendrecv fail. ret=$ret");                return false;            }               log_debug($server, "sendrecv finish. rsp=$rsp");            return true;        }           public static function fini($server) {            log_debug($server, "fini in php.\n");        }       }?>

    这里最值得赞叹的就是process函数对于sendrecv扩展调用,这里背后通过协程其实已经实现了一次异步网络交互:既能像同步CGI般书写逻辑代码,又能无痛地享受异步的高并发。

    愿望是美好的,现实是残酷的!

    我这时突然心血来潮:来压测一把性能吧,看看相比于原生C++代码有多大的性能衰减。单次请求1KB,施以1w/s的压力,压了一会coredump了。

    内存泄漏?协程栈溢出?...

    期间各种折腾:GDB,修改协程栈大小,Google,咨询PHPer ...

    很快到了晚上,该查的都查过了,该问的都问过了,实在没辙了,停下来喝杯茶:“call_user_function可重入么”?想到这一层,相信了解协程本质的兄弟又秒懂了:你妹的,人家实现Zend的时候怎么知道调用线程还会玩协程进行用户态调度啊,这个黑盒里面一切皆有可能啊!全局变量、静态变量 ...

    好吧,去掉sendrecv这类基于协程的扩展,重新压测,单worker对于3w/s的echo还是轻松无压力的。

    结局

    虽然这次最吸引人的一个Feature最终未能实现,不过我还是很开心,因为再次印证了一个观点:思考往往比蛮干高效百倍,尤其在处理棘手问题时,无头苍蝇般乱闯乱撞往往费力不讨好,此时,如果能够冷静下来,尽力搜集现有知识储备,说不定灵感就来光顾你了。

    未来可能的方向:PHP从5.5版本引入了yield,感觉如果挖掘出来Zend对于yield的支持细节,说不定有希望和我们的C框架很好的融合,但是总觉得是个填不平的大坑。如果抛开其它因素,也许我还是希望选择Golang一类语言直接享受goroutine的优势吧,哈哈!

    附录

    PHP扩展开发及内核应用

    http://www.walu.cc/phpbook/preface.md

    编译PHP扩展的两种方式

    http://521-wf.com/archives/227.html

    如何使用C++开发PHP扩展(上)

    http://521-wf.com/archives/241.html

    如何使用C++开发PHP扩展(下)

    http://521-wf.com/archives/245.html

    Wrapping C++ Classes in a PHP Extension

    http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension/

    声明
    本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
    优化PHP代码:减少内存使用和执行时间优化PHP代码:减少内存使用和执行时间May 10, 2025 am 12:04 AM

    TOOPTIMIZEPHPCODEFORDUSEMEMORYUSAGEAGEAGEAGEAGEAGEANDEXECUTITIEM,关注台词:1)USEREEREFERESCENCENCINCOPYINSTEADOFCOPYINGINATATASTRUCTURESTROUCTURESTOREDUCEMORYCONSUMPTION.2)杠杆phphppphpphp'sbuilt intimpunctionslikearray_mapforfunctionslikearray_mapforfforfforfforfasterapasterexecution.3)

    PHP电子邮件:分步发送指南PHP电子邮件:分步发送指南May 09, 2025 am 12:14 AM

    phpisusedforsendendemailsduetoitsignegrationwithservermailservicesand andexternalsmtpproviders,自动化notifications andMarketingCampaigns.1)设置设置yourphpenvironcormentswironmentswithaweberswithawebserverserverserverandphp,确保themailfunctionisenabled.2)useabasicscruct

    如何通过PHP发送电子邮件:示例和代码如何通过PHP发送电子邮件:示例和代码May 09, 2025 am 12:13 AM

    发送电子邮件的最佳方法是使用PHPMailer库。1)使用mail()函数简单但不可靠,可能导致邮件进入垃圾邮件或无法送达。2)PHPMailer提供更好的控制和可靠性,支持HTML邮件、附件和SMTP认证。3)确保正确配置SMTP设置并使用加密(如STARTTLS或SSL/TLS)以增强安全性。4)对于大量邮件,考虑使用邮件队列系统来优化性能。

    高级PHP电子邮件:自定义标题和功能高级PHP电子邮件:自定义标题和功能May 09, 2025 am 12:13 AM

    CustomHeadersheadersandAdvancedFeaturesInphpeMailenHanceFunctionalityAndreliability.1)CustomHeadersheadersheadersaddmetadatatatatataatafortrackingandCategorization.2)htmlemailsallowformattingandttinganditive.3)attachmentscanmentscanmentscanbesmentscanbestmentscanbesentscanbesentingslibrarieslibrarieslibrariesliblarikelikephpmailer.4)smtppapapairatienticationaltication enterticationallimpr

    使用PHP和SMTP发送电子邮件的指南使用PHP和SMTP发送电子邮件的指南May 09, 2025 am 12:06 AM

    使用PHP和SMTP发送邮件可以通过PHPMailer库实现。1)安装并配置PHPMailer,2)设置SMTP服务器细节,3)定义邮件内容,4)发送邮件并处理错误。使用此方法可以确保邮件的可靠性和安全性。

    使用PHP发送电子邮件的最佳方法是什么?使用PHP发送电子邮件的最佳方法是什么?May 08, 2025 am 12:21 AM

    ThebestapproachforsendingemailsinPHPisusingthePHPMailerlibraryduetoitsreliability,featurerichness,andeaseofuse.PHPMailersupportsSMTP,providesdetailederrorhandling,allowssendingHTMLandplaintextemails,supportsattachments,andenhancessecurity.Foroptimalu

    PHP中依赖注入的最佳实践PHP中依赖注入的最佳实践May 08, 2025 am 12:21 AM

    使用依赖注入(DI)的原因是它促进了代码的松耦合、可测试性和可维护性。1)使用构造函数注入依赖,2)避免使用服务定位器,3)利用依赖注入容器管理依赖,4)通过注入依赖提高测试性,5)避免过度注入依赖,6)考虑DI对性能的影响。

    PHP性能调整技巧和技巧PHP性能调整技巧和技巧May 08, 2025 am 12:20 AM

    phperformancetuningiscialbecapeitenhancesspeedandeffice,whatevitalforwebapplications.1)cachingwithapcureduccureducesdatabaseloadprovesrovesponsemetimes.2)优化

    See all articles

    热AI工具

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驱动的应用程序,用于创建逼真的裸体照片

    AI Clothes Remover

    AI Clothes Remover

    用于从照片中去除衣服的在线人工智能工具。

    Undress AI Tool

    Undress AI Tool

    免费脱衣服图片

    Clothoff.io

    Clothoff.io

    AI脱衣机

    Video Face Swap

    Video Face Swap

    使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

    热工具

    mPDF

    mPDF

    mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

    螳螂BT

    螳螂BT

    Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

    Atom编辑器mac版下载

    Atom编辑器mac版下载

    最流行的的开源编辑器

    SublimeText3 Mac版

    SublimeText3 Mac版

    神级代码编辑软件(SublimeText3)

    SublimeText3汉化版

    SublimeText3汉化版

    中文版,非常好用