[原]PHP-yar拓展源碼解讀五-server篇

Server模塊提供了一個(gè)基于Http的Yar協(xié)議的Server實(shí)現(xiàn)殉挽。
常見的使用方法如下

//隨手寫的demo.php
function serviceAction(){
  $service = new Yar_Server(new OrderServer());
  $service->handle();
}

在你所用的框架的action下執(zhí)行以上代碼,并為Ycf-Client提供能通過(guò)具體路由訪問(wèn)到以上代碼的url空凸,OrderServer即可為所有對(duì)Yar_Client的調(diào)用提供遠(yuǎn)程實(shí)現(xiàn)福贞。

Server構(gòu)造器

Yac_Server構(gòu)造器源碼如下:

//yar_server.c
PHP_METHOD(yar_server, __construct) {
    zval *obj;

    if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "o", &obj) == FAILURE) {
        return;
    }

    zend_update_property(yar_server_ce, getThis(), "_executor", sizeof("_executor")-1, obj);
}

僅僅是Yac_Server將new Yar_Server(new ServerObject())中的$serverObject寫入成員變量$_executor而已寡润。

Yar_Server->handle()

//yar_server.c
PHP_METHOD(yar_server, handle)
{
    //基于http的實(shí)現(xiàn)需要檢查http header頭是否已發(fā)送
    if (SG(headers_sent)) {
        php_error_docref(NULL, E_WARNING, "headers already has been sent");
        RETURN_FALSE;
    } else {
        const char *method;
        zval *executor, rv;

        //再次檢查下構(gòu)造器中存起來(lái)的那個(gè)_executor實(shí)例變量是否對(duì)象類型
        executor = zend_read_property(yar_server_ce, getThis(), ZEND_STRL("_executor"), 0, &rv);
        if (IS_OBJECT != Z_TYPE_P(executor)) {
            php_error_docref(NULL, E_WARNING, "executor is not a valid object");
            RETURN_FALSE;
        }


        //非post的情況下我注,根據(jù)yar.expose_info輸出自動(dòng)生成的RPC文檔或報(bào)錯(cuò)
        method = SG(request_info).request_method;
        if (!method || strncasecmp(method, "POST", 4)) {
            if (YAR_G(expose_info)) {
                php_yar_server_info(executor);
                RETURN_TRUE;
            } else {
                zend_throw_exception(yar_server_exception_ce, "server info is not allowed to access", YAR_ERR_FORBIDDEN);
                return;
            }
        }

        //執(zhí)行服務(wù)端rpc方法
        php_yar_server_handle(executor);
    }

    RETURN_TRUE;
}

handle()根據(jù)請(qǐng)求內(nèi)容返回 自動(dòng)生成的RPC文檔或者執(zhí)行遠(yuǎn)程方法

自動(dòng)生成文檔

由于Yar拓展在PHP Extension層面實(shí)現(xiàn),可以直接訪問(wèn)內(nèi)核API迟隅,所以RPC文檔生成 這塊 完全不需要使用反射但骨。
php_yar_server_info()通過(guò)直接遍歷zend_class_entry(內(nèi)核中用于表示一個(gè)類的信息)的function_table(類中的成員方法表)調(diào)用php_yar_print_info()來(lái)生成API文檔并打印,

//yar_server.c
static int php_yar_print_info(zval *ptr, void *argument) /* {{{ */ {
    zend_function *f = Z_FUNC_P(ptr);

    //僅展示可見性為public励七,且方法名不以“_”開頭的方法
    if (f->common.fn_flags & ZEND_ACC_PUBLIC 
        && f->common.function_name && *(ZSTR_VAL(f->common.function_name)) != '_') {
        char *prototype = NULL;
        //從zend_function獲取固定格式的方法聲明信息,php_yar_get_function_declaration()實(shí)現(xiàn)見下
        if ((prototype = php_yar_get_function_declaration(f))) {
            char *buf, *doc_comment = NULL;
            //只有用戶函數(shù)(PHP語(yǔ)言實(shí)現(xiàn)的)才能通過(guò)zend_function->op_array拿到注釋信息.
            //內(nèi)部函數(shù)(內(nèi)核或拓展實(shí)現(xiàn)的方法)對(duì)應(yīng)的Union成員zend_function->zend_internal_function沒(méi)有注釋信息
            if (f->type == ZEND_USER_FUNCTION) {
                if (f->op_array.doc_comment != NULL) {
                    doc_comment = (char *)ZSTR_VAL(f->op_array.doc_comment);
                }
            }
            //使用html模板format出接口文檔并打印
            spprintf(&buf, 0, HTML_MARKUP_ENTRY, prototype, doc_comment? doc_comment : "");
            efree(prototype);
            PHPWRITE(buf, strlen(buf));
            efree(buf);
        }
    }

    //內(nèi)核遍歷方法zend_hash_apply_with_argument()用的奔缠,表示繼續(xù)遍歷
    return ZEND_HASH_APPLY_KEEP;
} /* }}} */

php_yar_get_function_declaration()實(shí)現(xiàn)見下

//yar_server.c
static char * php_yar_get_function_declaration(zend_function *fptr) /* {{{ */ {
    char *offset, *buf;
    uint32_t length = 1024;

//重新分配內(nèi)存的快捷宏
#define REALLOC_BUF_IF_EXCEED(buf, offset, length, size) \
    if (offset - buf + size >= length) {    \
        length += size + 1;                 \
        buf = erealloc(buf, length);        \
    }

    //可見性檢查
    if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
        return NULL;
    }

    offset = buf = (char *)emalloc(length * sizeof(char));

    //方法返回值是否引用類型format
    if (fptr->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) {
        *(offset++) = '&';
        *(offset++) = ' ';
    }

    //類名format
    if (fptr->common.scope) {
        memcpy(offset, ZSTR_VAL(fptr->common.scope->name), ZSTR_LEN(fptr->common.scope->name));
        offset += ZSTR_LEN(fptr->common.scope->name);
        *(offset++) = ':';
        *(offset++) = ':';
    }

    //方法名format
    {
        size_t name_len = ZSTR_LEN(fptr->common.function_name);
        REALLOC_BUF_IF_EXCEED(buf, offset, length, name_len);
        memcpy(offset, ZSTR_VAL(fptr->common.function_name), name_len);
        offset += name_len;
    }
    //方法參數(shù)format
    *(offset++) = '(';
    if (fptr->common.arg_info) {
        uint32_t i, required;
        zend_arg_info *arg_info = fptr->common.arg_info;

        required = fptr->common.required_num_args;
        for (i = 0; i < fptr->common.num_args;) {
            //聲明了類型為對(duì)象的參數(shù)format
            if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
                const char *class_name;
                uint32_t class_name_len;
                zend_string *class_str = ZEND_TYPE_NAME(arg_info->type);
                class_name = ZSTR_VAL(class_str);
                class_name_len = ZSTR_LEN(class_str);
                if (strncasecmp(class_name, "self", sizeof("self")) && fptr->common.scope ) {
                    class_name = ZSTR_VAL(fptr->common.scope->name);
                    class_name_len = ZSTR_LEN(fptr->common.scope->name);
                } else if (strncasecmp(class_name, "parent", sizeof("parent")) && fptr->common.scope->parent) {
                    class_name = ZSTR_VAL(fptr->common.scope->parent->name);
                    class_name_len = ZSTR_LEN(fptr->common.scope->parent->name);
                }
                REALLOC_BUF_IF_EXCEED(buf, offset, length, class_name_len);
                memcpy(offset, class_name, class_name_len);
                offset += class_name_len;
                *(offset++) = ' ';
            } else if (ZEND_TYPE_IS_CODE(arg_info->type)) {
                //其他顯式聲明類型的參數(shù)format
                uint32_t type_name_len;
                char *type_name = zend_get_type_by_const(ZEND_TYPE_CODE(arg_info->type));
                type_name_len = strlen(type_name);
                REALLOC_BUF_IF_EXCEED(buf, offset, length, type_name_len);
                memcpy(offset, type_name, type_name_len);
                offset += type_name_len;
                *(offset++) = ' ';
            }

            //引用傳遞的參數(shù)format
            if (arg_info->pass_by_reference) {
                *(offset++) = '&';
            }
            *(offset++) = '$';

            //參數(shù)名format
            if (arg_info->name) {
                const char *name;
                uint32_t name_len;
                if (fptr->type == ZEND_INTERNAL_FUNCTION) {
                    name = ((zend_internal_arg_info*)arg_info)->name;
                    name_len = strlen(name);
                } else {
                    name = ZSTR_VAL(arg_info->name);
                    name_len = ZSTR_LEN(arg_info->name);
                }
                REALLOC_BUF_IF_EXCEED(buf, offset, length, name_len);
                memcpy(offset, name, name_len);
                offset += name_len;
            } else {
                uint32_t idx = i;
                memcpy(offset, "param", 5);
                offset += 5;
                do {
                    *(offset++) = (char) (idx % 10) + '0';
                    idx /= 10;
                } while (idx > 0);
            }
            //可選參數(shù)默認(rèn)值format
            if (i >= required) {
                *(offset++) = ' ';
                *(offset++) = '=';
                *(offset++) = ' ';
                //用戶方法
                if (fptr->type == ZEND_USER_FUNCTION) {
                    zend_op *precv = NULL;

                    //看懂這塊代碼需要點(diǎn)背景知識(shí)掠抬,稍微解釋下
                    //用戶方法(就是用PHP寫的那種),在內(nèi)核和其他PHP代碼一樣表現(xiàn)為一個(gè)典型的zend_op_array結(jié)構(gòu)校哎,由Zend編譯器生成
                    //zend_op_array包含一個(gè)zend_op鏈表,zend_op這玩意俗稱opline指令两波,是ZendVM的執(zhí)行指令
                    //方法的每個(gè)參數(shù)在zend_op_array 也會(huì)有相應(yīng)的 zend_op對(duì)應(yīng),這種zend_op的zend_op->opcode 為ZEND_RECV*
                    //除了zend_function->common里面能拿到的闷哆,用戶函數(shù)參數(shù)的其他信息要通過(guò)該zend_op獲取
                    //下面這段循環(huán)就是遍歷zend_op_array中的所有zend_op腰奋,找到第i個(gè)參數(shù)對(duì)應(yīng)的zend_op
                    //有興趣的可以看下zend_compile.c的zend_compile_params()
                    {
                        uint32_t idx  = i;
                        zend_op *op = ((zend_op_array *)fptr)->opcodes;
                        zend_op *end = op + ((zend_op_array *)fptr)->last;

                        ++idx;
                        while (op < end) {
                            if ((op->opcode == ZEND_RECV || op->opcode == ZEND_RECV_INIT)
                                    && op->op1.num == (long)idx
                                    ) {
                                precv = op;
                            }
                            ++op;
                        }
                    }
                    //ZEND_RECV_INIT對(duì)應(yīng)有默認(rèn)值
                    if (precv && precv->opcode == ZEND_RECV_INIT
                            && precv->op2_type != IS_UNUSED
                            ) {
                        zval zv, zv_copy;
                        int use_copy;
                        //默認(rèn)值format
                        ZVAL_DUP(&zv, RT_CONSTANT(&fptr->op_array, precv->op2));
#if PHP_VERSION_ID < 70100
                        zval_update_constant_ex(&zv, 1, fptr->common.scope);
#else
                        zval_update_constant_ex(&zv, fptr->common.scope);
#endif
                        if (Z_TYPE(zv) == IS_TRUE) {
                            memcpy(offset, "true", 4);
                            offset += 4;
                        } else if (Z_TYPE(zv) == IS_FALSE) {
                            memcpy(offset, "false", 5);
                            offset += 5;
                        } else if (Z_TYPE(zv) == IS_NULL) {
                            memcpy(offset, "NULL", 4);
                            offset += 4;
                        } else if (Z_TYPE(zv) == IS_STRING) {
                            *(offset++) = '\'';
                            REALLOC_BUF_IF_EXCEED(buf, offset, length, MIN(Z_STRLEN(zv), 10));
                            memcpy(offset, Z_STRVAL(zv), MIN(Z_STRLEN(zv), 10));
                            offset += MIN(Z_STRLEN(zv), 10);
                            if (Z_STRLEN(zv) > 10) {
                                *(offset++) = '.';
                                *(offset++) = '.';
                                *(offset++) = '.';
                            }
                            *(offset++) = '\'';
                        } else if (Z_TYPE(zv) == IS_ARRAY) {
                            memcpy(offset, "Array", 5);
                            offset += 5;
                        } else {
                            use_copy = zend_make_printable_zval(&zv, &zv_copy);
                            REALLOC_BUF_IF_EXCEED(buf, offset, length, Z_STRLEN(zv_copy));
                            memcpy(offset, Z_STRVAL(zv_copy), Z_STRLEN(zv_copy));
                            offset += Z_STRLEN(zv_copy);
                            if (use_copy) {
                                zval_dtor(&zv_copy);
                            }
                        }
                        zval_ptr_dtor(&zv);
                    }
                } else {
                    memcpy(offset, "NULL", 4);
                    offset += 4;
                }
            }

            if (++i < fptr->common.num_args) {
                *(offset++) = ',';
                *(offset++) = ' ';
            }
            arg_info++;
            REALLOC_BUF_IF_EXCEED(buf, offset, length, 32);
        }
    }
    *(offset++) = ')';
    *offset = '\0';

    return buf;
}

RPC遠(yuǎn)程服務(wù)的實(shí)現(xiàn)

RPC服務(wù)的核心方法php_yar_server_handle 如下:

static void php_yar_server_handle(zval *obj) /* {{{ */ {
    char *payload, *err_msg;
    char *pkg_name = NULL;
    size_t payload_len;
    zend_bool bailout = 0;
    zend_string *method;
    zval *post_data = NULL, output, func, rret;
    zend_class_entry *ce;
    yar_response_t *response;
    yar_request_t  *request = NULL;
    yar_header_t *header;
    php_stream *s;
    smart_str raw_data = {0};
    //構(gòu)造request實(shí)例
    response = php_yar_response_instance();
    //打開http輸入流,獲取所有數(shù)據(jù)
    s = SG(request_info).request_body;
    if (!s || FAILURE == php_stream_rewind(s)) {
        php_yar_error(response, YAR_ERR_PACKAGER, "empty request");
        DEBUG_S("0: empty request");
        goto response_no_output;
    }

    while (!php_stream_eof(s)) {
        char buf[512];
        size_t len = php_stream_read(s, buf, sizeof(buf));

        if (len && len != (size_t) -1) {
            smart_str_appendl(&raw_data, buf, len);
        }
    }
    //從smart_str獲得payload的zend_string
    if (raw_data.s) {
        smart_str_0(&raw_data);
        payload = ZSTR_VAL(raw_data.s);
        payload_len = ZSTR_LEN(raw_data.s);
    } else {
        php_yar_error(response, YAR_ERR_PACKAGER, "empty request body");
        DEBUG_S("0: empty request '%s'");
        goto response_no_output;
    }

    //從數(shù)據(jù)中解析出協(xié)議頭信息(見header章節(jié))
    if (!(header = php_yar_protocol_parse(payload))) {
        php_yar_error(response, YAR_ERR_PACKAGER, "malformed request header '%.10s'", payload);
        DEBUG_S("0: malformed request '%s'", payload);
        goto response_no_output;
    }


    DEBUG_S("%ld: accpect rpc request form '%s'",
            header->id, header->provider? (char *)header->provider : "Yar PHP " PHP_YAR_VERSION);

    //去掉協(xié)議頭 獲取載荷
    payload += sizeof(yar_header_t);
    payload_len -= sizeof(yar_header_t);

    //通過(guò)打包器將載荷反序列化成 PHP變量到rret/post_data
    if (!(post_data = php_yar_packager_unpack(payload, payload_len, &err_msg, &rret))) {
        php_yar_error(response, YAR_ERR_PACKAGER, err_msg);
        efree(err_msg);
        goto response_no_output;
    }

    pkg_name = payload;

    //IMP數(shù)組轉(zhuǎn)回request 
    request = php_yar_request_unpack(post_data);
    zval_ptr_dtor(post_data);


    if (!php_yar_request_valid(request, response, &err_msg)) {
        php_yar_error(response, YAR_ERR_REQUEST, "%s", err_msg);
        efree(err_msg);
        goto response_no_output;
    }

    //打開輸出緩沖
    if (php_output_start_user(NULL, 0, PHP_OUTPUT_HANDLER_STDFLAGS) == FAILURE) {
        php_yar_error(response, YAR_ERR_OUTPUT, "start output buffer failed");
        goto response_no_output;
    }

    ce = Z_OBJCE_P(obj);
    method = zend_string_tolower(request->method);
    if (!zend_hash_exists(&ce->function_table, method)) {
        zend_string_release(method);
        php_yar_error(response, YAR_ERR_REQUEST, "call to undefined api %s::%s()", ce->name, ZSTR_VAL(request->method));
        goto response;
    }
    zend_string_release(method);

    //用sigsetjmp()模擬的try catch
    zend_try {
        int count;
        zval *func_params;
        zval *tmp_zval;
        zval retval;
        HashTable *func_params_ht;

        func_params_ht = Z_ARRVAL(request->parameters);
        count = zend_hash_num_elements(func_params_ht);

        if (count) {
            int i = 0;
            func_params = safe_emalloc(sizeof(zval), count, 0);

            ZEND_HASH_FOREACH_VAL(func_params_ht, tmp_zval) {
                ZVAL_COPY(&func_params[i++], tmp_zval);
            } ZEND_HASH_FOREACH_END();
        } else {
            func_params = NULL;
        }
        //調(diào)用executor的對(duì)應(yīng)方法
        //這里是RPC-server的核心抱怔,遠(yuǎn)程服務(wù)方法的真正調(diào)用點(diǎn)
        ZVAL_STR(&func, request->method);
        if (FAILURE == call_user_function_ex(NULL, obj, &func, &retval, count, func_params, 0, NULL)) {
            if (count) {
                int i = 0;
                for (; i < count; i++) {
                    zval_ptr_dtor(&func_params[i]);
                }
            }
            php_yar_error(response, YAR_ERR_REQUEST, "call to api %s::%s() failed", ce->name, request->method);
            goto response;
        }
        //有返回設(shè)置response->retval
        if (!Z_ISUNDEF(retval)) {
            php_yar_response_set_retval(response, &retval);
            zval_ptr_dtor(&retval);
        }

        if (count) {
            int i = 0;
            for (; i < count; i++) {
                zval_ptr_dtor(&func_params[i]);
            }
        }
    } zend_catch {
        bailout = 1;
    } zend_end_try();

    //設(shè)置response->err字段
    if (EG(exception)) {
        php_yar_response_set_exception(response, EG(exception));
        EG(exception) = NULL;
    }

//正常返回跳轉(zhuǎn)點(diǎn)
response:
    //關(guān)閉緩沖區(qū)劣坊,并將緩沖區(qū)內(nèi)容填充到response->out字段
    if (php_output_get_contents(&output) == FAILURE) {
        php_output_end();
        php_yar_error(response, YAR_ERR_OUTPUT, "unable to get ob content");
        goto response_no_output;
    }

    php_output_discard();
    php_yar_response_alter_body(response, Z_STR(output), YAR_RESPONSE_REPLACE);

//錯(cuò)誤跳轉(zhuǎn)處
response_no_output:
    php_yar_server_response(request, response, pkg_name);
    if (request) {
        php_yar_request_destroy(request);
    }

    smart_str_free(&raw_data);

    php_yar_response_destroy(response);

    if (bailout) {
        zend_bailout();
    }

    return;
} /* }}} */

整體來(lái)說(shuō)還是很好懂的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屈留,一起剝皮案震驚了整個(gè)濱河市局冰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灌危,老刑警劉巖康二,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異勇蝙,居然都是意外死亡沫勿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門浅蚪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)藕帜,“玉大人,你說(shuō)我怎么就攤上這事惜傲∏⒐剩” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵盗誊,是天一觀的道長(zhǎng)时甚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)哈踱,這世上最難降的妖魔是什么荒适? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮开镣,結(jié)果婚禮上刀诬,老公的妹妹穿的比我還像新娘。我一直安慰自己邪财,他們只是感情好陕壹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布质欲。 她就那樣靜靜地躺著,像睡著了一般糠馆。 火紅的嫁衣襯著肌膚如雪嘶伟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天又碌,我揣著相機(jī)與錄音九昧,去河邊找鬼。 笑死毕匀,一個(gè)胖子當(dāng)著我的面吹牛铸鹰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播期揪,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼掉奄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了凤薛?” 一聲冷哼從身側(cè)響起姓建,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缤苫,沒(méi)想到半個(gè)月后速兔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡活玲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年涣狗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舒憾。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镀钓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镀迂,到底是詐尸還是另有隱情丁溅,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布探遵,位于F島的核電站窟赏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏箱季。R本人自食惡果不足惜涯穷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藏雏。 院中可真熱鬧拷况,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蚤告,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間服爷,已是汗流浹背杜恰。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仍源,地道東北人心褐。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像笼踩,于是被迫代替她去往敵國(guó)和親逗爹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理嚎于,服務(wù)發(fā)現(xiàn)掘而,斷路器,智...
    卡卡羅2017閱讀 134,701評(píng)論 18 139
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,947評(píng)論 2 89
  • Awesome PHP 一個(gè)PHP資源列表于购,內(nèi)容包括:庫(kù)袍睡、框架、模板肋僧、安全斑胜、代碼分析、日志嫌吠、第三方庫(kù)止潘、配置工具、W...
    guanguans閱讀 5,763評(píng)論 0 47
  • Composer Repositories Composer源 Firegento - Magento模塊Comp...
    零一間閱讀 3,960評(píng)論 1 66
  • 傳輸器結(jié)構(gòu) yar底層用一個(gè)_yar_transport_interface結(jié)構(gòu)表示一個(gè)傳輸器辫诅,處理網(wǎng)絡(luò)IO相關(guān)事...
    bromine閱讀 984評(píng)論 0 0