백엔드 개발 PHP 튜토리얼 一次失败的PHP扩展开发之

一次失败的PHP扩展开发之

Jun 23, 2016 pm 01:44 PM

一次失败的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으로 문의하세요.

    핫 AI 도구

    Undresser.AI Undress

    Undresser.AI Undress

    사실적인 누드 사진을 만들기 위한 AI 기반 앱

    AI Clothes Remover

    AI Clothes Remover

    사진에서 옷을 제거하는 온라인 AI 도구입니다.

    Undress AI Tool

    Undress AI Tool

    무료로 이미지를 벗다

    Clothoff.io

    Clothoff.io

    AI 옷 제거제

    AI Hentai Generator

    AI Hentai Generator

    AI Hentai를 무료로 생성하십시오.

    뜨거운 도구

    메모장++7.3.1

    메모장++7.3.1

    사용하기 쉬운 무료 코드 편집기

    SublimeText3 중국어 버전

    SublimeText3 중국어 버전

    중국어 버전, 사용하기 매우 쉽습니다.

    스튜디오 13.0.1 보내기

    스튜디오 13.0.1 보내기

    강력한 PHP 통합 개발 환경

    드림위버 CS6

    드림위버 CS6

    시각적 웹 개발 도구

    SublimeText3 Mac 버전

    SublimeText3 Mac 버전

    신 수준의 코드 편집 소프트웨어(SublimeText3)

    JWT (JSON Web Tokens) 및 PHP API의 사용 사례를 설명하십시오. JWT (JSON Web Tokens) 및 PHP API의 사용 사례를 설명하십시오. Apr 05, 2025 am 12:04 AM

    JWT는 주로 신분증 인증 및 정보 교환을 위해 당사자간에 정보를 안전하게 전송하는 데 사용되는 JSON을 기반으로 한 개방형 표준입니다. 1. JWT는 헤더, 페이로드 및 서명의 세 부분으로 구성됩니다. 2. JWT의 작업 원칙에는 세 가지 단계가 포함됩니다. JWT 생성, JWT 확인 및 Parsing Payload. 3. PHP에서 인증에 JWT를 사용하면 JWT를 생성하고 확인할 수 있으며 사용자 역할 및 권한 정보가 고급 사용에 포함될 수 있습니다. 4. 일반적인 오류에는 서명 검증 실패, 토큰 만료 및 대형 페이로드가 포함됩니다. 디버깅 기술에는 디버깅 도구 및 로깅 사용이 포함됩니다. 5. 성능 최적화 및 모범 사례에는 적절한 시그니처 알고리즘 사용, 타당성 기간 설정 합리적,

    확실한 원칙과 PHP 개발에 적용되는 방법을 설명하십시오. 확실한 원칙과 PHP 개발에 적용되는 방법을 설명하십시오. Apr 03, 2025 am 12:04 AM

    PHP 개발에서 견고한 원칙의 적용에는 다음이 포함됩니다. 1. 단일 책임 원칙 (SRP) : 각 클래스는 하나의 기능 만 담당합니다. 2. Open and Close Principle (OCP) : 변경은 수정보다는 확장을 통해 달성됩니다. 3. Lisch의 대체 원칙 (LSP) : 서브 클래스는 프로그램 정확도에 영향을 미치지 않고 기본 클래스를 대체 할 수 있습니다. 4. 인터페이스 격리 원리 (ISP) : 의존성 및 사용되지 않은 방법을 피하기 위해 세밀한 인터페이스를 사용하십시오. 5. 의존성 반전 원리 (DIP) : 높고 낮은 수준의 모듈은 추상화에 의존하며 종속성 주입을 통해 구현됩니다.

    시스템 재시작 후 UnixSocket의 권한을 자동으로 설정하는 방법은 무엇입니까? 시스템 재시작 후 UnixSocket의 권한을 자동으로 설정하는 방법은 무엇입니까? Mar 31, 2025 pm 11:54 PM

    시스템이 다시 시작된 후 UnixSocket의 권한을 자동으로 설정하는 방법. 시스템이 다시 시작될 때마다 UnixSocket의 권한을 수정하려면 다음 명령을 실행해야합니다.

    PHP에서 늦은 정적 결합의 개념을 설명하십시오. PHP에서 늦은 정적 결합의 개념을 설명하십시오. Mar 21, 2025 pm 01:33 PM

    기사는 PHP 5.3에 도입 된 PHP의 LSB (Late STATIC BING)에 대해 논의하여 정적 방법의 런타임 해상도가보다 유연한 상속을 요구할 수있게한다. LSB의 실제 응용 프로그램 및 잠재적 성능

    phpstorm에서 CLI 모드를 디버그하는 방법은 무엇입니까? phpstorm에서 CLI 모드를 디버그하는 방법은 무엇입니까? Apr 01, 2025 pm 02:57 PM

    phpstorm에서 CLI 모드를 디버그하는 방법은 무엇입니까? PHPStorm으로 개발할 때 때때로 CLI (Command Line Interface) 모드에서 PHP를 디버그해야합니다 ...

    PHP의 CURL 라이브러리를 사용하여 JSON 데이터가 포함 된 게시물 요청을 보내는 방법은 무엇입니까? PHP의 CURL 라이브러리를 사용하여 JSON 데이터가 포함 된 게시물 요청을 보내는 방법은 무엇입니까? Apr 01, 2025 pm 03:12 PM

    PHP 개발에서 PHP의 CURL 라이브러리를 사용하여 JSON 데이터를 보내면 종종 외부 API와 상호 작용해야합니다. 일반적인 방법 중 하나는 컬 라이브러리를 사용하여 게시물을 보내는 것입니다 ...

    프레임 워크 보안 기능 : 취약점 보호. 프레임 워크 보안 기능 : 취약점 보호. Mar 28, 2025 pm 05:11 PM

    기사는 입력 유효성 검사, 인증 및 정기 업데이트를 포함한 취약점을 방지하기 위해 프레임 워크의 필수 보안 기능을 논의합니다.

    See all articles