[原]PHP-yar拓展源碼解讀四-client篇

Yar拓展自帶了一個Yar的PHP客戶端,這一章先講yar的同步客戶端實現。

我們假設使用代碼如下:

//demo.php
$client = new Yar_Client("http://www.demoserver.com/rpc/s.php");
$client->SetOpt(YAR_OPT_CONNECT_TIMEOUT, 1000);
echo $client->getOrderMoney(123456);

Client構造器

從Yar_Client類的構造器看起,Yar_Client的源碼實現如下:

//yar_client.c
/* {{{ proto Yar_Client::__construct($uri[, array $options = NULL]) */
PHP_METHOD(yar_client, __construct) {
    zend_string *url;
    zval *options = NULL;

    //使用url,option變量來接收構造器的兩個參數,第一個為字符串,第二個為可選參數,可空,類型為數組
    if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "S|a!", &url, &options) == FAILURE) {
        return;
    }

    zend_update_property_str(yar_client_ce, getThis(), ZEND_STRL("_uri"), url);
    if (strncasecmp(ZSTR_VAL(url), "http://", sizeof("http://") - 1) == 0
            || strncasecmp(ZSTR_VAL(url), "https://", sizeof("https://") - 1) == 0) {
              //_protocol的默認值是 YAR_CLIENT_PROTOCOL_HTTP,無需處理
    } else if (strncasecmp(ZSTR_VAL(url), "tcp://", sizeof("tcp://") - 1) == 0) {
        zend_update_property_long(yar_client_ce, getThis(), ZEND_STRL("_protocol"), YAR_CLIENT_PROTOCOL_TCP);
    } else if (strncasecmp(ZSTR_VAL(url), "unix://", sizeof("unix://") - 1) == 0) {
        zend_update_property_long(yar_client_ce, getThis(), ZEND_STRL("_protocol"), YAR_CLIENT_PROTOCOL_UNIX);
    } else {
        php_yar_client_trigger_error(1, YAR_ERR_PROTOCOL, "unsupported protocol address %s", ZSTR_VAL(url));
        return;
    }

    if (options) {
        zend_update_property(yar_client_ce, getThis(), ZEND_STRL("_options"), options);
    }
}
/* }}} */

Yar_Client的構造器就是將uri,options參數,以及從$uri中解析出的通信協議寫入成員變量而已。

萬能的__call()

不像Java系的RpcClient實現,動不動來個動態(tài)代理設計,在PHP中一個__call()足以動態(tài)"生成"任意遠程方法。
對Yar_Client實例的所有調用,都直接通過__call()魔術方法對遠程Yar服務器執(zhí)行遠程調用。

/* {{{ proto Yar_Client::__call($method, $parameters = NULL) */
PHP_METHOD(yar_client, __call) {
    zval *params, *protocol, rv;
    zend_string *method;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa", &method, &params) == FAILURE) {
        return;
    }

    protocol = zend_read_property(yar_client_ce, getThis(), ZEND_STRL("_protocol"), 0, &rv);
    //Yar拓展目前客戶端支持UnixSocket Tcp Http三種協議,根據使用的uri隱式確認,不同的協議會使用不同的傳輸器
    switch (Z_LVAL_P(protocol)) {
        case YAR_CLIENT_PROTOCOL_TCP:
        case YAR_CLIENT_PROTOCOL_UNIX:
        case YAR_CLIENT_PROTOCOL_HTTP:
            if ((php_yar_client_handle(Z_LVAL_P(protocol), getThis(), method, params, return_value))) {
                return;
            }
            break;
        default:
            php_error_docref(NULL, E_WARNING, "unsupported protocol %ld", Z_LVAL_P(protocol));
            break;
    }

    RETURN_FALSE;
}
/* }}} */

Yar_Client->__call()的唯一有效操作是執(zhí)行php_yar_client_handle()

static int php_yar_client_handle(int protocol, zval *client, zend_string *method, zval *params, zval *retval) /* {{{ */ {
    char *msg;
    zval *uri, *options;
    zval rv;
    const yar_transport_t *factory;
    yar_transport_interface_t *transport;
    yar_request_t *request;
    yar_response_t *response;
    int flags = 0;

    uri = zend_read_property(yar_client_ce, client, ZEND_STRL("_uri"), 0, &rv);
    //根據協議(from uri)獲取傳輸器實例
    if (protocol == YAR_CLIENT_PROTOCOL_HTTP) {
        factory = php_yar_transport_get(ZEND_STRL("curl"));
    } else if (protocol == YAR_CLIENT_PROTOCOL_TCP || protocol == YAR_CLIENT_PROTOCOL_UNIX) {
        factory = php_yar_transport_get(ZEND_STRL("sock"));
    } else {
        return 0;
    }

    //傳輸器和傳輸器工廠的細節(jié)見傳輸器章節(jié)
    transport = factory->init();
 
    options = zend_read_property(yar_client_ce, client, ZEND_STRL("_options"), 1, &rv);

    if (IS_ARRAY != Z_TYPE_P(options)) {
        options = NULL;
    }
    //構造request
    if (!(request = php_yar_request_instance(method, params, options))) {
        transport->close(transport);
        factory->destroy(transport);
        return 0;
    }

    if (YAR_G(allow_persistent)) {
        if (options) {
            zval *flag = php_yar_client_get_opt(options, YAR_OPT_PERSISTENT);
            if (flag && (Z_TYPE_P(flag) == IS_TRUE || (Z_TYPE_P(flag) == IS_LONG && Z_LVAL_P(flag)))) {
                flags |= YAR_PROTOCOL_PERSISTENT;
            }
        }
    }

    /* This is tricky to pass options in, for custom headers*/
    msg = (char*)options;
    //調用傳輸器實例的open()接口方法 (具體內容見傳輸器章節(jié))
    if (!transport->open(transport, Z_STR_P(uri), flags, &msg)) {
        php_yar_client_trigger_error(1, YAR_ERR_TRANSPORT, msg);
        php_yar_request_destroy(request);
        efree(msg);
        transport->close(transport);
        factory->destroy(transport);
        return 0;
    }

    DEBUG_C(ZEND_ULONG_FMT": call api '%s' at (%c)'%s' with '%d' parameters",
            request->id, ZSTR_VAL(request->method), (flags & YAR_PROTOCOL_PERSISTENT)? 'p' : 'r', Z_STRVAL_P(uri), 
            zend_hash_num_elements(Z_ARRVAL(request->parameters)));

    //調用傳輸器實例的send()接口方法
    if (!transport->send(transport, request, &msg)) {
        php_yar_client_trigger_error(1, YAR_ERR_TRANSPORT, msg);
        php_yar_request_destroy(request);
        efree(msg);
        transport->close(transport);
        factory->destroy(transport);
        return 0;
    }

    //調用傳輸器實例的exec()接口方法,獲取響應對應的response變量
    response = transport->exec(transport, request);

    if (response->status != YAR_ERR_OKEY) {
        php_yar_client_handle_error(1, response);
        php_yar_request_destroy(request);
        php_yar_response_destroy(response);
        transport->close(transport);
        factory->destroy(transport);
        return 0;
    } else {
        //打印 遠程服務器的(如)標準輸出,事實上沒有人會糟糕到直接利用遠程服務的標準輸出
        //這個功能的主要使用場景有兩個 
        //1個是代替遠程服務器打印各種Notice,Warning信息
        //2是將RPC服務文檔從RPC Server的標準輸出轉移到RPC Client的標準輸出上。
        if (response->out && ZSTR_LEN(response->out)) {
            PHPWRITE(ZSTR_VAL(response->out), ZSTR_LEN(response->out));
        }
        //從responce中獲取遠程RPC方法的返回值作為PHP方法的返回值返回
        ZVAL_COPY(retval, &response->retval);
        //相關資源釋放
        php_yar_request_destroy(request);
        php_yar_response_destroy(response);
        transport->close(transport);
        factory->destroy(transport);
        return 1;
    }
} /* }}} */

整理來說還是比較容易理解的,傳輸細節(jié)在傳輸器章節(jié)中再講。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容