Linux下C++實現PHP擴充

來源:互聯網
上載者:User
閱讀: 1

目前Bastion Host在資料庫中的密碼儲存為rc4加密的密文,以前PHP在讀取密碼後需要調用後台系統命令,通過讀取後台系統命令的stdout來擷取解密後的純文字密碼。

這種調用方式對於一次的加密或解密,這樣一次系統命令的調用時間成本可能在10ms,並不會產生太大的問題。而目前一個校正所有賬戶密碼複雜度的需求要求在載入一個頁面的時候校正所有帳號的密碼是否符合要求,如果帳號規模比較龐大的時候,問題來了,2w個帳號時,請求的回應時間達到了5m19s,而如果注釋掉解密的部分,回應時間在2-3s。為了最佳化這個問題,我們首先嘗試使用xmlrpc的方式調用python的c++擴充來提高響應的速度,效果也不錯,回應時間提高到了20s左右。經過排查後發現,2w條資料在xmlrpc調用時序列化和還原序列化的時間佔用了20s中的15s。為了進一步提高響應速度,而加解密的源碼又是c++的代碼實現,於是想到了使用c++來實現一個PHP的擴充,可能效果會比較好,於是開始動手實現這個rc4的PHP擴充。

使用C++編寫PHP擴充

1 準備工作

首先在開發機上擷取Bastion Hostphp對應版本的源碼並解壓。

yangshuofei@IPS-dev3 ~/src $ tarxvfphp-5.5.25.tar.bz2 

2 編寫config檔案

config.m4 檔案是編譯基礎中最核心的檔案,這個檔案主要是用autoconf 來產生configure 設定檔,繼而自動產生大家所熟悉的Makefile檔案。

可以自己書寫config.m4檔案,也可以由Shell指令碼ext_skel 來產生樣板,這裡我採用的是後者。

進入源碼的ext檔案夾,執行./ext_skel命令如下:

yangshuofei@IPS-dev3 ~/src $ cdphp-5.5.25/ext/yangshuofei@IPS-dev3 ~/src/php-5.5.25/ext $ ./ext_skel --extname=rc4Creatingdirectoryrc4Creatingbasicfiles: config.m4config.w32 .svnignorerc4.c php_rc4.h CREDITSEXPERIMENTALtests/001.phpt rc4.php [done]. To use yournew extension, youwillhaveto executethefollowingsteps: 1.  $ cd ..2.  $ viext/rc4/config.m43.  $ ./buildconf4.  $ ./configure --[with|enable]-rc45.  $ make6.  $ ./sapi/cli/php -f ext/rc4/rc4.php7.  $ viext/rc4/rc4.c8.  $ make Repeatsteps 3-6 untilyouaresatisfiedwithext/rc4/config.m4andstep 6 confirmsthatyourmoduleis compiledintoPHP. Then, startwritingcodeand repeatthelasttwostepsas oftenas necessary.

在ext目錄下多了一個叫rc4的目錄。進入該目錄,會發現目錄下有幾個檔案:

yangshuofei@IPS-dev3 ~/src/php-5.5.25/ext $ cdrc4yangshuofei@IPS-dev3 ~/src/php-5.5.25/ext/rc4 $ ls -ltotal 32-rw-r--r-- 1 yangshuofeiyangshuofei    3 Mar 14 17:27 CREDITS-rw-r--r-- 1 yangshuofeiyangshuofei    0 Mar 14 17:27 EXPERIMENTAL-rw-r--r-- 1 yangshuofeiyangshuofei 1954 Mar 14 17:27 config.m4-rw-r--r-- 1 yangshuofeiyangshuofei  275 Mar 14 17:27 config.w32-rw-r--r-- 1 yangshuofeiyangshuofei 2787 Mar 14 17:27 php_rc4.h-rw-r--r-- 1 yangshuofeiyangshuofei 5002 Mar 14 17:27 rc4.c-rw-r--r-- 1 yangshuofeiyangshuofei  493 Mar 14 17:27 rc4.phpdrwxr-xr-x 2 yangshuofeiyangshuofei 4096 Mar 14 17:27 tests

然後可以根據提示來修改config.m4檔案,這裡有幾個重要的宏命令如下:

  • dnl 是注釋;
  • PHP_ARG_WITH或者PHP_ARG_ENABLE指定了PHP擴充模組的工作方式,前者意味著不需要第三方庫,後者正好相反;
  • PHP_REQUIRE_CXX用於指定這個擴充用到了C++;
  • PHP_ADD_INCLUDE 指定PHP擴充模組用到的標頭檔目錄;
  • PHP_CHECK_LIBRARY指定PHP擴充模組PHP_ADD_LIBRARY_WITH_PATH定義以及庫串連錯誤資訊等;
  • PHP_ADD_LIBRARY(stdc++,””,EXTERN_NAME_LIBADD)用於將標準C++庫連結進入擴充;
  • PHP_SUBST(EXTERN_NAME_SHARED_LIBADD)用於說明這個擴充編譯成動態連結程式庫的形式;
  • PHP_NEW_EXTENSION 用於指定有哪些源檔案應該被編譯,檔案和檔案之間用空格隔開;

ext_skel預設產生的模組架構是針對C的,我們要使用C++進行PHP擴充,那除以上的PHP_REQUIRE_CXX,PHP_ADD_LIBRARY兩個宏必需外,還要把rc4.c改名成rc4.cc。

需要注意的是,在config.m4裡面也可以使用類似的Makefile文法,片段如下:

PHP_ARG_WITH(rc4, for rc4support,Makesurethatthecommentis aligned:[  --with-rc4            Include rc4support])...  COMMON_PATH="../../common"  PHP_REQUIRE_CXX()  PHP_SUBST(RC4_SHARED_LIBADD)  PHP_ADD_INCLUDE($COMMON_PATH/crypt)  PHP_ADD_LIBRARY(stdc++, 1, RC4_SHARED_LIBADD)  PHP_ADD_LIBRARY_WITH_PATH(SashCommon, $COMMON_PATH, RC4_SHARED_LIBADD)  CCFILES="rc4.cc"  PHP_NEW_EXTENSION(rc4, $CCFILES, $ext_shared)...

3 編寫.h檔案

修改php_rc4.h這個標頭檔。

由於TSRM.h這個檔案所包含的函數和類都是用純C語言寫的,故應該使用extern來說明如下:

extern "C" {#ifdef ZTS#include "TSRM.h"#endif}

添加php函式宣告如下:

PHP_FUNCTION(rc4_encrypt);  /* For encrypt string. */PHP_FUNCTION(rc4_decrypt);  /* For decrypt string. */ 

如果該php_rc4.h標頭檔或者rc4.cc檔案用到了C++語言中的一些容器或者一些函數,則需要在標頭檔中包含相應的c++庫的標頭檔,否則會出現找不到相應的C++函數錯誤。

4 編寫.cc檔案

修改rc4.cc這個檔案。

由於config.h、php.h、php_ini.h和ext/standard/info.h中包含的函數和類如TSRM.h一樣,都是用純C語言寫的,所以也需要用extern說明如下:

extern "C" {#ifdef HAVE_CONFIG_H#include "config.h"#endif #include "php.h"#include "php_ini.h"#include "ext/standard/info.h"}#include "php_rc4.h"

而#include “php_ext_name.h”這句則已經不需要包含在extern “C”內,另外,ZEND_GET_MODULE這個宏命令也是需要特別申明如下:

#ifdef COMPILE_DL_RC4BEGIN_EXTERN_C()ZEND_GET_MODULE(rc4)END_EXTERN_C()#endif

總之,把一些C寫的庫或h用相容的方式給解決。由於已有現成的c++實現,於是引入相關標頭檔:

 #include "Crypt.h" 

添加相關函數:

const zend_function_entryrc4_functions[] = {    PHP_FE(rc4_encrypt, NULL)      /* For encrypt string. */    PHP_FE(rc4_decrypt, NULL)      /* For decrypt string. */    PHP_FE_END  /* Must be the last line in rc4_functions[] */};

添加函數實現如下:

PHP_FUNCTION(rc4_encrypt){    char *arg = NULL;    int arg_len, len;    const char *key = "key";    int key_len = sizeof("key") - 1;     char *strg;    const char *result = NULL;     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",         &arg, &arg_len, &key, &key_len) == FAILURE) {        return;    }    result = SASH::Crypt::encryptWithKey(arg, key).c_str();     len = spprintf(&strg, 0, "%s", result);    RETURN_STRINGL(strg, len, 0);}

函數自身的定義使用了宏PHP_FUNCTION(),該宏可以產生一個適合於Zend引擎的函數原型。邏輯本身分成語義各部分,取得調用函數的參數和邏輯本身。

為了獲得函數傳遞的參數,可以使用zend_parse_parameters()API函數。下面是該函數的原型:

 zend_parse_parameters(int num_argsTSRMLS_CC, char *type_spec, …); 

zend_parse_parameters()函數大部分的代碼看起來幾乎都一樣。ZEND_NUM_ARGS()向Zend Engine提供關於接收到的參數數量,TSRMLS_CC被用來保證安全執行緒,最後函數會返回SUCCESS或者FAILURE。在普通情況下zend_parse_parameters()將會返回SUCCESS;如果一個呼叫指令碼傳遞的參數數量超過了函數定義的參數數量,或者傳遞的參數不能轉換成合適的資料類型,Zend將會自動的輸出一個錯誤資訊,然後函數會優雅地把控制權返回給呼叫指令碼。

可能指定的類型

表中的zval是用來儲存PHP中所有使用者空間變數的真實資料類型。三個“複雜”的資料類型,Resource, Array和Object,當他們的資料類型字母標示在zend_parse_parameters()中被使用的時候,Zend Engine會對其進行類型檢查,但是它們在C語言中沒有相對應的資料類型,所以不會有任何轉換會被實際執行。

由於解密有預設key,所以我在rc4_encrypt中採用了可變參數”s|s”,可以這樣來解讀:我首先需要一個字串類型的參數,然後一個管道字元說明接下來的參數列表是可選的(如果一個可選的參數在函數調用過程中沒有被傳遞,那麼zend_parse_parameters()不會改變已經傳給它的參數值),後面的s表示我需要一個可選的字串參數。後面的&arg, &arg_len, &key, &key_len以引用的方式傳遞進來,所以zend_parse_parameters()可以用參數值來填充它們。(擴充還支援可變參數 )

從C++函數向PHP傳回值,並不能使用通常的return語句,Zend將傳回值地址作為參數傳給我們,return_value是Zend為我們預先建立的一個標準zval結構,相當於一個局部變數,使用者獲得傳回值時就相當於對return_value進行賦值操作,我們只需填充它即可;return_value_used表明使用者是否使用了傳回值,0表明沒有使用傳回值,當函數結束後return_value的refcount將被減為0,並被銷毀,因此,這種情況下完全可以不處理傳回值;return_value_ptr用於返回引用,它需要和zend_function_entry.arg_info聯合使用,通常都是NULL。

用於填充return_value的一組宏

這些宏將在填充完return_value後,執行return語句。如果不想return,可以改用相應RETURN_xxx宏的RETVAL_xxx版本。在這裡我們使用了RETURN_STRINGL(strg, len, 0);來返回加密後的字串。

5 產生.so

使用phpize產生configure(由於和開發機php版本不同,所以這裡使用的是自己編譯的phpize):

yangshuofei@IPS-dev3 ~/src/php-5.5.25/ext/rc4 $ /home/yangshuofei/php/bin/phpize  Configuringfor:PHPApiVersion:        20121113ZendModuleApiNo:      20121212ZendExtensionApiNo:  220121212

產生Makefile(由於和開發機php版本不同,所以這裡使用的是自己編譯的php-config):

 yangshuofei@IPS-dev3 ~/src/php-5.5.25/ext/rc4 $ ./configure  --with-php-config=/home/yangshuofei/php/bin/php-config 

使用phpize產生configure執行檔案後,可以使用./configure –help查看協助資訊,修改config.m4檔案可以修改configure的協助資訊。每次修改了config.m4檔案,需要使用清除臨時檔案 命令phpize –clean來完成消除configure。

產生.so,產生後會放在modules檔案夾下:

yangshuofei@IPS-dev3 ~/src/php-5.5.25/ext/rc4 $ make...----------------------------------------------------------------------Librarieshavebeeninstalledin:  /home/yangshuofei/src/php-5.5.25/ext/rc4/modules If youeverhappento wantto linkagainstinstalledlibrariesin a givendirectory, LIBDIR, youmusteitheruse libtool, andspecifythefullpathnameofthelibrary, or use the `-LLIBDIR'flag during linking and do at least one of the following:   - add LIBDIR to the `LD_LIBRARY_PATH' environmentvariable    duringexecution  - addLIBDIRto the `LD_RUN_PATH' environment variable     during linking   - use the `-Wl,--rpath -Wl,LIBDIR' linkerflag  - haveyoursystemadministratoraddLIBDIRto `/etc/ld.so.conf' See any operating system documentation about shared libraries formore information, such as the ld(1) and ld.so(8) manual pages.---------------------------------------------------------------------- Build complete.Don't forgetto run 'make test'.

6 在裝置上使用

根據php.ini中的配置:

extension_dir = /etc/php/apache2-php5.3/ext 

將產生的rc4.so檔案放在裝置的放在/etc/php/apache2-php5.3/ext檔案夾下,

在/etc/php/apache2-php5.3/ext-active檔案夾添加rc4.ini內容如下:

extension= rc4.so 

然後重啟apache:

Develop>apache2ctlrestart 

接下來就可以在php檔案中調用so中的rc4_encrypt和rc4_decrypt函數來進行加解密了。

修改前後效能對比

使用擴充前回應時間5m19s

使用擴充前回應時間

使用擴充後回應時間2.73s

使用擴充後回應時間

如果您需要瞭解更多內容,可以

加入QQ群:486207500

直接詢問:010-68438880-8669

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.