失敗した PHP 拡張機能開発の旅
By warezhou 2014.11.19
継続的な反復の後、私たちの部門のコルーチンバージョンネットワークフレームワーク(CoSvr)フレーム)がついにリリースされました!これは元々は素晴らしいことでしたが、新しいサービスが継続的にアクセスされるにつれて、多くの固有の欠陥が徐々に表面化しました。 「過負荷保護」をサポートしていません
「ホットリスタート」をサポートしていません
社内オープンソース SPP3.0 フレームワークを導入し、基本的な周辺機能を吸収し、二次的なビジネス開発を行う
SPP を拡張し、PHP をサポートする埋め込みプログラミング用のスクリプト言語であり、C 拡張機能の形で PHP にコルーチン機能を提供します (今後、PHPer は非同期コードを喜んで作成できるようになり、母はもう私のコールバックについて心配する必要がなくなりました!)
難しい? 長くなりましたが、本題に入りましょう: C++/PHP 混合プログラミングを実装するにはどうすればよいでしょうか?
オープニング
組み込みPHP 業界における C++/PHP の組み合わせは一般に「パフォーマンス」の考慮事項に基づいており、特定のパフォーマンスのボトルネック (PB シリアル化など) を解決するために PHP コード内で C/C++ 拡張機能を呼び出します。 )。
C/C++ 開発者としては、「パフォーマンス」よりも「開発効率」の誘惑が明らかに大きいため、私たちのアイデアは、PHP をスクリプト言語として使用して、ビジネス ロジックを迅速に開発し、それを SPP フレームワークに挿入して実行することです。 。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 スクリプトを導入します
すごいです4. call_user_function による 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 ();
5. php_embed_shutdown によるクリーンアップ
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;}
PHP
PHP C 拡張機能の開発に関する記事はインターネット上にあります。興味のある読者は記事末尾の付録を詳しく読んでみてください。
php_embed_shutdown(TSRMLS_C);
2 をオンにする必要があります。 PHP ソース コード パッケージの ext ディレクトリに入り、ext_skel ツールがプラグイン シェルフ コードを生成します
./configure --enable-embedmakemake install(可选)
3. config.m4 を編集し、PHP_ARG_WITH または PHP_ARG_ENABLE オプションをオンにします (正直に言うと、違いはまだ明確ではありません。アドバイスをお願いします)、C++ サポート、依存関係パスなどを追加します。
cd ext./ext_skel --extname=demo
4.demo.cpp を編集し、拡張定義と実装 (関数、クラス、変数など) を追加します。 ) ここに挙げるのは関数定義の例だけです。クラスに興味のある読者は、付録に従って調べてください。ここで示した sendrecv 関数の定義は比較的代表的なもので、3 番目のパラメーター rsp は、受信したデータを PHP 呼び出し元に返す責任があります
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
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);}
5。私は個人的には動的コンパイルを好みます (静的コンパイルには php ソース コードの再コンパイルが必要ですが、これは非常に時間がかかり、手間がかかります)。 6. php.ini ファイルを編集し、新しい拡張機能を追加すると、PHP コードで新しい拡張機能を呼び出すことができます
クライマックス
いよいよ、EchoDemo をいくつかプレイしてみました。 telnetとsaw エコーが一行ずつ表示されるので非常に気持ちいいです。
const zend_function_entry demo_functions[] = { PHP_FE(sendrecv, arginfo_sendrecv) PHP_FE_END /* Must be the last line in demo_functions[] */};
ここで最も優れているのは、プロセス関数の sendrecv への拡張呼び出しです。非同期ネットワーク インタラクションは、バックグラウンドでコルーチンを介して実装されています。同期 CGI のようなロジック コードを作成できるだけでなく、それを簡単に楽しむこともできます。非同期の高い同時実行性。
願いは美しいけど現実は残酷!
私は突然アイデアを思いつきました。パフォーマンスのストレス テストを行って、ネイティブ C++ コードと比較してどの程度パフォーマンスが低下するかを確認してみましょう。 1KB の単一リクエストが適用され、1w/s の圧力が適用されました。しばらくすると、コアダンプが抑制されました。
メモリリーク?コルーチンスタックオーバーフロー? ...
期間中色々紆余曲折あり、GDB、コルーチンのスタックサイズ変更、Google、PHPerの相談…
すぐに夜になり、確認すべきことは確認した、質問は全て完了それは尋ねる必要がありますが、私にできることは何もありません。わかりました。お茶を飲みに立ち寄ってください。「call_user_function は再入可能ですか?」このレベルを考えると、コルーチンの性質を理解している兄弟ならすぐに理解できると思います。「くそー、Zend を実装するとき、呼び出し元のスレッドがユーザー モード スケジューリングのためにコルーチンを実行することをどのようにして知ることができるのでしょうか。これではすべてが可能です。」ブラックボックス。 !グローバル変数、静的変数...
さて、sendrecv などのコルーチンベースの拡張機能を削除して、テストを再負荷します。単一のワーカーは、ストレスなく 3w/s のエコーを処理できます。
エンディング
今回の最大の魅力の一つは結局実現には至りませんでしたが、考えることは一人で行動するよりも100倍効率的であるという視点を改めて確認できたのでとても満足しています
特に難しい問題に対処する場合、頭のないハエが飛び回るのは報われないことがよくあります。このとき、落ち着いて既存の知識の蓄えを収集することに全力を尽くすことができれば、もしかしたらインスピレーションが訪れるかもしれません。今後の可能性のある方向性: PHP はバージョン 5.5 から yield を導入しました。Zend の yield サポートの詳細を掘り下げれば、それを C フレームワークとうまく統合できるかもしれないと感じていますが、これには大きな穴があるといつも感じています。それは埋めることができません。他の要因を差し置いても、私はやはり Golang のような言語を選択して goroutine の利点を直接享受したいと思うかもしれません (笑)。 付録
PHP 拡張機能の開発とカーネル アプリケーション
http://www.walu.cc/phpbook/preface.md
PHP 拡張機能をコンパイルする 2 つの方法
http://521-wf.com/archives/ 227 .html
C++ を使用して PHP 拡張機能を開発する方法 (パート 1)
http://521-wf.com/archives/241.html
C++ を使用して PHP 拡張機能を開発する方法 (パート 2)
http ://521-wf.com/archives/245.html
PHP 拡張機能での C++ クラスのラップ
http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension /
🎜