One failed PHP extension development
One failedPHP
Extended development journey
By warezhou 2014.11.19
Origin
After continuous iteration, our department's coroutine network framework (CoSvrFrame) was finally released! This was originally a great thing, but as new businesses continue to access, many inherent defects gradually emerge:
"TCP connection pool" is not supported"
"Dispatcher-Workers model" is not supported"
"Overload protection" is not supported"
"Hot restart" is not supported"
"64Bit" is not supported"
......
For senior background development, most of the problems listed above are difficult to understand. the reason for this problem is a bit like "warm boiled frog": the iteration process lacks a macro perspective and introduces too many business features, the overall architecture is unreasonable. Recently, the "coroutine version" was originally my personal work in my spare time. I only used the original SvrFrame at the underlying layer to write business code happily and make it easy to get out of work, the earth is shaking! Take the most extreme 64Bit as an example. I believe everyone understands it in seconds.
After many surveys and discussions, we finally gave the following forward directions:
Introducing the company's open-source SPP3.0 framework, absorbing its infrastructure and surrounding facilities for secondary business development
Extensions of SPP support embedded programming using PHP as a scripting language, and provide PHP with coroutine capabilities in the form of C extensions (from now on, PHPer can also happily write asynchronous code, mom no longer needs to worry about my callback !)
What do you mean? After so long, we can start with the topic: how to implement C ++/PHP hybrid programming?
Disclaimer:As I am a programmer and have not been familiar with PHP extension development for weeks, I can't go deep into WHY, just stay at HOW, just for record purposes, please forgive me!
Opening
EmbeddedPHP
The combination of C ++/PHP in the industry is generally due to "performance" considerations. C/C ++ extensions are called in PHP code, to solve specific performance bottlenecks (such as PB serialization ).
As a C/C ++ developer, "development efficiency" is obviously more attractive than "performance". Therefore, our idea is to use PHP as a script language to quickly develop business logic, insert to SPP framework for running.
1. open the php dynamic library in RTLD_GLOBAL mode
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. initialize with php_embed_init
php_embed_module.php_ini_path_override = "../php/php.ini";php_embed_init(0, NULL);
3. introduce the PHP script through zend_eval_string
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 back the PHP function through call_user_function
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 < 0 || func_ret < 0) { base->log_.LOG_P_PID(LOG_FATAL, "call_user_function fail. call_ret=%d func_ret=%d\n", call_ret, func_ret); return -1;}
5. clean up through php_embed_shutdown
php_embed_shutdown(TSRMLS_C);
PHPExtension
Php c extension development articles on the Internet can be said to have reached the flood level. interested readers can read the appendix at the end of the article.
1. download the php source code package and manually compile it. in order to work with the above embedded application, do you need to open it? Enable-embed option
./Configure -- enable-embedmakemake install (optional)
2. go to the ext Directory of the php source code package and use the ext_skel tool to generate the plug-in shelf code
cd ext./ext_skel --extname=demo
3. edit config. m4, open the PHP_ARG_WITH or PHP_ARG_ENABLE option (tell the truth the difference is still unclear, ask someone to give advice), add C ++ support, dependency path, etc.
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. edit demo. cpp, add extension definitions and implementations (functions, classes, variables ...), the function definition example is provided here. readers who are interested in the class will explore the function according to the appendix. The sendrecv function is typically defined here. among them, the 3rd rsp parameter is a reference parameter and is responsible for returning the received data to the PHP caller.
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. everything is ready and can be compiled and extended. I personally prefer dynamic compilation (static compilation requires re-compilation of php source code, which is too time-consuming and laborious) to generate. so is located in the current extended modules Directory
/usr/local/bin/phpize./configure --with-php-config=/usr/local/bin/php-configmake
6. edit the php. ini file, add new extensions, and then you can happily call the new extensions in the PHP code.
extension_dir="/somewhere/modules"extension="demo.so"extension="xxxx.so"
Climax
Finally, it was time to assemble and form. I played several echodemos via telnet, and I was in a good mood when I saw a line of Echo.
The most amazing thing here is the extended call of the process function for sendrecv. here, the coroutine has actually implemented an asynchronous network interaction: it can write logic code like synchronizing CGI, it is also easy to enjoy asynchronous high concurrency.
The desire is beautiful, and the reality is cruel!
I was suddenly excited: Let's test the performance to see how much performance degrades compared to the native C ++ code. 1 KB for a single request, apply the pressure of 1 w/s, and press coredump for a while.
Memory leakage? Coroutine stack overflow ?...
Various tossing during the period: GDB, modify the coroutine stack size, Google, consult PHPer...
Soon after the evening, I checked all the items I checked and asked all the questions. I did not know. I stopped to have a cup of tea: "Can call_user_function be reloaded "? Think of this layer, I believe that the brother who understands the essence of coroutine understands it in seconds: Your sister's, how do people know that the calling thread will also play with coroutine for user-State scheduling when implementing Zend, everything in this black box is possible! Global variables, static variables...
All right, remove the coroutine-based extension such as sendrecv and re-stress test. a single worker is easy and stress-free for 3 w/s echo.
Conclusion
Although the most attractive Feature was not implemented in the end, I was very happy because it once again confirmed a point of view:Thinking is often a hundred times more efficient than doing it.Especially when dealing with difficult problems, it is often hard and thankless to break into the chaos without a head fly. at this time, if you can calm down and try your best to collect existing knowledge reserves, you may be inspired to patronize you.
Possible future direction: PHP introduced yield from version 5.5. I feel that Zend may be well integrated with our C framework if it finds out the details of yield support, but I always think it is a big pitfall. If you leave aside other factors, maybe I still want to use Golang to enjoy goroutine's advantages. haha!
Appendix
PHP extension development and kernel applications
Http://www.walu.cc/phpbook/preface.md
Two methods for compiling PHP extensions
Http://521-wf.com/archives/227.html
How to use C ++ to develop PHP extensions (I)
Http://521-wf.com/archives/241.html
How to use C ++ to develop PHP extensions (below)
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/