<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 9.2 PHP中的資源類型 # 9.2 PHP中的資源類型 通常情況下,像{資源}這類復合類型的數據都會占用大量的硬件資源,比如內存、CPU以及網絡帶寬。對于使用頻率超級高的數據庫鏈接,我們可以獲取一個長鏈接,使其不會在腳本結束后自動銷毀,一旦創建便可以在各個請求中直接使用,從而減少每次創建它的消耗。Mysql的長鏈接在PHP內核中其實就是一種持久{資源}。 Memory Allocation 前面的章節里我們接觸了emalloc()之類的以e開頭的內存管理函數,通過它們申請的內存都會被內核自動的進行垃圾回收的操作。而對于一個持久{資源}來說,我們是絕對不希望它在腳本結束后被回收的。 假設我們需要在我們的{資源}中同時保存文件名和文件句柄兩個數據,現在我們就需要自己定義個結構了: ``` typedef struct _php_sample_descriptor_data { char *filename; FILE *fp; }php_sample_descriptor_data; ``` 當然,因為結構變了(之前是個FILE\*),我們之前的代碼也需要跟著改動。這里還沒有涉及到持久{資源},僅僅是換了一種{資源}結構 ``` static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) { php_sample_descriptor_data *fdata = (php_sample_descriptor_data*)rsrc->ptr; fclose(fdata->fp); efree(fdata->filename); efree(fdata); } PHP_FUNCTION(sample_fopen) { php_sample_descriptor_data *fdata; FILE *fp; char *filename, *mode; int filename_len, mode_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE) { RETURN_NULL(); } if (!filename_len || !mode_len) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length"); RETURN_FALSE; } fp = fopen(filename, mode); if (!fp) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode); RETURN_FALSE; } fdata = emalloc(sizeof(php_sample_descriptor_data)); fdata->fp = fp; fdata->filename = estrndup(filename, filename_len); ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor); } PHP_FUNCTION(sample_fwrite) { php_sample_descriptor_data *fdata; zval *file_resource; char *data; int data_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) == FAILURE ) { RETURN_NULL(); } ZEND_FETCH_RESOURCE(fdata, php_sample_descriptor_data*,&file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor); RETURN_LONG(fwrite(data, 1, data_len, fdata->fp)); } ``` 我們這里沒有重寫sample\_fclose()函數,你可以嘗試著自己實現它。 現在編譯運行,所有代碼的結果都非常正確,我們還可以在內核中獲取每個{資源}對應的文件名稱了。 ``` PHP_FUNCTION(sample_fname) { php_sample_descriptor_data *fdata; zval *file_resource; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r",&file_resource) == FAILURE ) { RETURN_NULL(); } ZEND_FETCH_RESOURCE(fdata, php_sample_descriptor_data*,&file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor); RETURN_STRING(fdata->filename, 1); } ``` 現在,Persistent Resources來了! ### Delayed Destruction 在前面我們刪除一個{資源}的時候,其實是去EG(regular\_list)中將其刪掉,EG(regular\_list)存儲著所有的只用在當前請求的{資源}。 持久{資源},存儲在另一個HashTable中:EG(persistent\_list)。其與EG(regular\_list)有個明顯的區別,那就是它每個值的索引都是字符串類型的,而且它的每個值也不會在每次請求結束后被釋放掉,只能我們手動通過zend\_hash\_del()來刪除,或者在進程結束后類似于MSHUTDOWN階段將EG(persistent\_list)整體清除,最常見的情景便是操作系統關閉了Web Server。 EG(persistent\_list)對其元素也有自己的dtor回調函數,和EG(regular\_list)一樣,它將根據其值的類型去調用不同的回調函數,我們這一次注冊回調函數的時候,需要用到zend\_register\_list\_destructors\_ex()函數的第二個參數,第一個則被賦成NULL。 在底層的實現中,持久的和regular{資源}是分別在不同的地方存儲的,也分別擁有各自不同的釋放函數。但在我們為腳本提供的函數中,卻希望能夠封裝這種差異,從而使我們的用戶使用起來更加方便快捷。 ``` static int le_sample_descriptor_persist; static void php_sample_descriptor_dtor_persistent(zend_rsrc_list_entry *rsrc TSRMLS_DC) { php_sample_descriptor_data *fdata = (php_sample_descriptor_data*)rsrc->ptr; fclose(fdata->fp); pefree(fdata->filename, 1); pefree(fdata, 1); } PHP_MINIT_FUNCTION(sample) { le_sample_descriptor = zend_register_list_destructors_ex(php_sample_descriptor_dtor, NULL,PHP_SAMPLE_DESCRIPTOR_RES_NAME, module_number); le_sample_descriptor_persist =zend_register_list_destructors_ex(NULL, php_sample_descriptor_dtor_persistent,PHP_SAMPLE_DESCRIPTOR_RES_NAME, module_number); return SUCCESS; } ``` 我們并沒有為這兩種{資源}起不同的名字,以防使用戶產生疑惑。 現在我們的PHP擴展中引進了一種新的{資源},所以我們需要改寫一下上面的函數,**盡量使**用戶使用時感覺不到這種差異。 ``` //sample_fopen() PHP_FUNCTION(sample_fopen) { php_sample_descriptor_data *fdata; FILE *fp; char *filename, *mode; int filename_len, mode_len; zend_bool persist = 0; //類比一下mysql_connect函數的最后一個參數。 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"ss|b",&filename, &filename_len, &mode, &mode_len,&persist) == FAILURE) { RETURN_NULL(); } if (!filename_len || !mode_len) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length"); RETURN_FALSE; } fp = fopen(filename, mode); if (!fp) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode); RETURN_FALSE; } if (!persist) { fdata = emalloc(sizeof(php_sample_descriptor_data)); fdata->filename = estrndup(filename, filename_len); fdata->fp = fp; ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor); } else { list_entry le; char *hash_key; int hash_key_len; fdata =pemalloc(sizeof(php_sample_descriptor_data),1); fdata->filename = pemalloc(filename_len + 1, 1); memcpy(fdata->filename, filename, filename_len + 1); fdata->fp = fp; //在EG(regular_list中存一份) ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor_persist); //在EG(persistent_list)中再存一份 le.type = le_sample_descriptor_persist; le.ptr = fdata; hash_key_len = spprintf(&hash_key, 0,"sample_descriptor:%s:%s", filename, mode); zend_hash_update(&EG(persistent_list),hash_key, hash_key_len + 1,(void*)&le, sizeof(list_entry), NULL); efree(hash_key); } } ``` 在持久{資源}時,因為我們在EG(regular\_list)中也保存了一份,所以腳本中我們資源類型的變量在實現中仍然是保存著一個resource ID,我們可以用它來進行之前章節所做的工作。 將其添加到EG(persistent\_list)中時,我們進行的操作流程幾乎和ZEND\_REGISTER\_RESOURCE()宏函數一樣,唯一的不同便是索引由之前的數字類型換成了字符串類型。 當一個保存在EG(regular\_list)中的持久{資源}被腳本釋放時,內核會在EG(regular\_list)尋找它對應的dtor函數,但它找到的是NULL,因為我們在使用zend\_register\_list\_destructors\_ex()函數聲明這種資源類型時,第一個參數的值為NULL。所以此時這個{資源}不會被任何dtor函數調用,可以繼續存在于內存中,任腳本流逝,請求更迭。 當web server的進程執行完畢后,內核會掃描EG(persistent\_list)的dtor,并調用我們已經定義好的釋放函數。在我們定義的釋放函數中,一定要記得使用pfree函數來釋放內存,而不是efree。 ### Reuse 創建持久{資源}的目的是為了使用它,而不是讓它來浪費內存的,我們再次重寫一下sample\_open()函數,這一次我們將檢測需要創建的資源是否已經在persistent\_list中存在了。 ``` PHP_FUNCTION(sample_fopen) { php_sample_descriptor_data *fdata; FILE *fp; char *filename, *mode, *hash_key; int filename_len, mode_len, hash_key_len; zend_bool persist = 0; list_entry *existing_file; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"ss|b",&filename, &filename_len, &mode, &mode_len,&persist) == FAILURE) { RETURN_NULL(); } if (!filename_len || !mode_len) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length"); RETURN_FALSE; } //看看是否已經存在,如果已經存在就直接使用,不再創建 hash_key_len = spprintf(&hash_key, 0,"sample_descriptor:%s:%s", filename, mode); if (zend_hash_find(&EG(persistent_list), hash_key,hash_key_len + 1, (void **)&existing_file) == SUCCESS) { //存在一個,直接使用! ZEND_REGISTER_RESOURCE(return_value,existing_file->ptr, le_sample_descriptor_persist); efree(hash_key); return; } fp = fopen(filename, mode); if (!fp) { php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode); RETURN_FALSE; } if (!persist) { fdata = emalloc(sizeof(php_sample_descriptor_data)); fdata->filename = estrndup(filename, filename_len); fdata->fp = fp; ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor); } else { list_entry le; fdata =pemalloc(sizeof(php_sample_descriptor_data),1); fdata->filename = pemalloc(filename_len + 1, 1); memcpy(fdata->filename, filename, filename_len + 1); fdata->fp = fp; ZEND_REGISTER_RESOURCE(return_value, fdata,le_sample_descriptor_persist); /* Store a copy in the persistent_list */ le.type = le_sample_descriptor_persist; le.ptr = fdata; //hash_key在上面已經被創建了 zend_hash_update(&EG(persistent_list),hash_key, hash_key_len + 1,(void*)&le, sizeof(list_entry), NULL); } efree(hash_key); } ``` 因為所有的PHP擴展都共用同一個HashTable來保存持久{資源},所以我們在為{資源}的索引起名時,一定要唯一,同時必須簡單,方便我們在其它的函數中構造出來。 ### Liveness Checking and Early Departure 一旦我們打開一個本地文件,便可以一直占有它的操作句柄,保證隨時可以打開它。但是對于一些存在于遠程計算機上的資源,比如mysql鏈接、http鏈接,雖然我們仍然握著與服務器的鏈接,但是這個鏈接在服務器端可能已經被關閉了,在本地我們就無法再用它來做一些有價值的工作了。 所以,當我們使用{資源},尤其是持久{資源}時,一定要保證獲取出來的{資源}仍然是有效的、可以使用的。如果它失效了,我們必須將其從persistent list中移除。下面就是一個檢測socket有效性的例子: ``` if (zend_hash_find(&EG(persistent_list), hash_key,hash_key_len + 1, (void**)&socket) == SUCCESS) { if (php_sample_socket_is_alive(socket->ptr)) { ZEND_REGISTER_RESOURCE(return_value,socket->ptr, le_sample_socket); return; } zend_hash_del(&EG(persistent_list),hash_key, hash_key_len + 1); } ``` 如你所見,{資源}失效后,我們只要把它從HashTable中刪除就行了,這一步操作同樣會激活我們設置的回調函數。On completion of this code block, the function will be in the same state it would have been if no resource had been found in the persistent list. ### Agnostic Retrieval 現在我們已經可以創建資源類型并生成新的資源,還能將持久{資源}與平常{資源}使用的差異性封裝起來。但是如果用戶對一個持久{資源}調用sample\_fwrite()時候并不會正常工作,先想一下內核是如何通過一個數字所以在regular\_list中獲取最終資源的。 ``` ZEND_FETCH_RESOURCE( fdata, php_sample_descriptor_data*, &file_resource, -1, PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor ); ``` le\_sample\_descriptor可以保證你獲取到的資源確實是這種類型的,絕不會出現你想要一個文件句柄,卻返回給你一個mysql鏈接的情況。這種驗證是必須的,但有時你又想繞過這種驗證,因為我們放在persistenst\_list中的{資源}是le\_sample\_descruotor\_persist類型的,所以當我們把它復制到regular\_list中時,它也是le\_sample\_descructor\_persist的,所以如果我們想獲取它,貌似只有兩種方法,要么修改類型,要么再寫一個新的sample\_write\_persistent函數的實現。或者極端一些,在sample\_write函數里進行復雜的判斷。但是如果sample\_write()函數能同時接收它們兩種類型的{資源}多好啊.... 事情沒有這么復雜,我們確實可以在sample\_write()函數里獲取{資源}時候同時指定兩種類型。那就是使用ZEND\_FETCH\_RESOURCE2()宏函數,它與ZEND\_FETCH\_RESOURCE()宏函數的唯一區別就是它可以接收兩種類型參數。 ``` ZEND_FETCH_RESOURCE2( fdata, php_sample_descriptor_data*, &file_resource, -1, PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor, le_sample_descriptor_persist ); ``` 現在,只要resource ID對應的最終資源類型是persistent或者non-persistent的一種便可以正常通過驗證了。 什么,你想設置三種甚至更多的類型?!!那你只能直接使用zend\_fetch\_resource()函數了。 ``` //一種類型的 fp = (FILE*) zend_fetch_resource( &file_descriptor TSRMLS_CC, -1, PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL, 1, le_sample_descriptor ); ZEND_VERIFY_RESOURCE(fp); ``` 想看看ZEND\_FETCH\_RESOURCE2()宏函數的實現么? ``` //兩種類型的 fp = (FILE*) zend_fetch_resource( &file_descriptor TSRMLS_CC, -1, PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL, 2, le_sample_descriptor, le_sample_descriptor_persist ); ZEND_VERIFY_RESOURCE(fp); ``` 再給力一些,三種類型的: ``` fp = (FILE*) zend_fetch_resource( &file_descriptor TSRMLS_CC, -1, PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL, 3, le_sample_descriptor, le_sample_descriptor_persist, le_sample_othertype ); ZEND_VERIFY_RESOURCE(fp); ``` 話都說到這份上了,你肯定知道四種、五種、更多種類型的應該怎么調用了。 ## links - 9.1 [復合類型的數據——{資源}](9.1.html) - 9.3 [{資源}自有的引用計數](9.3.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看