在資源變數中儲存的複雜的資料類型通常在初始化時需要一些記憶體配置,CPU時間或網路通訊。但是在請求之間保留類似於資料庫連接這種資源,必須要做到持久。資源是否持久是一個必須要考慮到的因素。
首先看記憶體配置的問題: 在使用php的時候,偏向使用emalloc因為它是malloc的帶回收的版本。但是持久化的資源必須在請求間都存在。對於一個檔案控制代碼類的資源來說,如果要加入一個隱藏檔名的需求,那麼必須在標頭檔中加入如下的代碼:
typedef struct _php_sample_descriptor_data { char *filename; FILE *fp;} php_sample_descriptor_data;
利用這個結構可以隱藏檔名和檔案控制代碼資源,從而能夠在不同的請求之間進行共用。
對應的,要在源檔案中進行相應的更改:
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) //修改後的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); // 註冊資源}
對於檔案寫入函數fwrite同樣需要修改:
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);}
在完成了記憶體配置之後,由於必須保持持久化,所以必須
延遲析構:
對於非持久的資源來說,一旦存放著資源id的變數被unset或fallen out of scope了,那麼它們就被從EG(regular_list)中去除掉了。而EG(persistent_list)中使用的索引是鍵值類的,元素在請求的最後不會不會被自動的去除掉。只有在zend_hash_del()調用或線程/進程完全關閉的情況下才會消除。 EG(persistent_list)也有dtor方法,但是是zend_register_list_descructors_ex()的第二個參數。一般來說,非持久和持久的資源會被註冊成兩種類型,有的時候也可以合二為一。現在在sample.c中添加一個持久的資源類型。
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;}
下面的這個fopen函數就相容了持久與非持久的兩個資源類型:
PHP_FUNCTION(sample_fopen){ php_sample_descriptor_data *fdata; FILE *fp; char *filename, *mode; int filename_len, mode_len; zend_bool persist = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"ssb", &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; ZEND_REGISTER_RESOURCE(return_value, fdata, le_sample_descriptor_persist); /* Store a copy in the persistent_list 在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); }}
對於非持久化的資源,給定了一個數位索引,並存放在了跟請求依存的list中。 對於持久化的資源,給定了一個鍵值類型,這個hashkey可以在接下來的請求中被重新得到。然後把資源放進了persistentlist中。當一個持久的資源out of scope的時候,EG(regular_list)的解構函式會為le_sample_descriptro_persist檢查registerlist析構。發現是NULL的話不會有任何的操作。從而也就保證了持久的資源不會被釋放掉。當資源被從EG(persistent_list)中去除的時候,要麼是線程進程結束了,要麼是故意刪除掉了。這時候就會去找持久化的解構函式。
資源被申請為持久化的原因就是為了在其他的請求中可以
複用:
如果想要複用持久化的資源,那就一定要用到hash_key,當sample_fopen被調用的時候,函數會利用請求的檔案名稱和模式重新建立hash_key,然後嘗試在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,"ssb", &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嘗試尋找一個已經開啟的檔案 */ 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(data->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 has already been created by now */ zend_hash_update(&EG(persistent_list), hash_key, hash_key_len + 1, (void*)&le, sizeof(list_entry), NULL); } efree(hash_key);}
注意由於所有的擴充都使用相同的雜湊表單去儲存資源,所以命名很重要。一般都是用擴充和資源類型名作為首碼。
檢查資源可用性:
儘管像檔案這種資源可以長期開啟,但是類似遠程網路資源這種如果在請求之間長期不用的話就有問題。所以在使用一個persistent資源之前,要先確定可用性。
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); //這裡會去調用之前註冊好的解構函式}