EOS 源码分析 -- cleos

从 main 函数开始

首先注册 command, option 以及 subcommand ,每个 command 都是一个 App 对象,并为每个 App 对象设置回调set_callback
获取程序启动时的指定参数,解析参数,从 App 对象的树形结构中,找到指定的command 的,然后执行对应的回调函数。

设置命令及参数

  • 声名第一个App类的对象,相当于cleos,再看指针,通过App的成员函数add_subcommand()实现,成功地实现了链表
  • add_subcommand 函数 将 option 添加到 vector subcommands_ 里,返回一个 App对象, 返回的 App 对象可以继续 add_subcommand,形成一个树型结构。
  • require_subcommand 函数指定该 command 不是一个单独有效的命令,需要一个subcommand.
  • add_option 函数将 创建 Option 对象,并将指针保存在 vector options_ 中,每个对象可以无限扩展配置选项。
  • option 调用 required() 方法,表示这个option需要一个参数
  • add_flag 添加flag (不带参数),内部会调用 add_option 方法。
  • set_callback 函数给每个 App 对象设置一个回调.

option 对象

属性

  • expected_ :该 option 需要几个参数
  • required_ : 是否需要参数
  • pname_ : 参数名,且不含前缀---
  • snames_ : 参数名,以- 为前缀
  • lnames_ : 参数名,以-- 为前缀

解析参数

解析参数调用 app.parse(argc, argv);

parse

将命令行参数,存放在vector args 中,

std::vector parse(int argc, char **argv) {
        name_ = argv[0];
        std::vector args;
        for(int i = argc - 1; i > 0; i--)
            args.emplace_back(argv[i]);
        return parse(args);
    }

调用另一个parse方法

/// The real work is done here. Expects a reversed vector.
    /// Changes the vector to the remaining options.
    std::vector &parse(std::vector &args) {
        _validate();
        _parse(args);
        run_callback();
        return args;
    }

_validate

首先_validate这个方法里检查option选项有没有冲突的

/// Check the options to make sure there are no conficts.
    ///
    /// Currenly checks to see if mutiple positionals exist with -1 args
    void _validate() const {
        auto count = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
            return opt->get_expected() == -1 && opt->get_positional();
        });
        if(count > 1)
            throw InvalidError(name_ + ": Too many positional arguments with unlimited expected args");
        for(const App_p &app : subcommands_)
            app->_validate();
    }

expected 解释如下

/// The number of expected values, 0 for flag, -1 for unlimited vector
    int expected_{1};

即, 0 代表的是添加的 flag 选项, -1 代表无限制的vector 选项
如果有相同名字的 vector 类型参数被指定,回抛出异常。

_parse

  • 函数 void _parse(std::vector &args) 中, 首先循环调用 _parse_single 方法,处理参数所有参数。
  • 处理当前app对象的所有 option 对象,当该option被解析过后,调用该option的回调函数,将 解析后的 results_ 作为参数,如下
/// Process the callback
    void run_callback() const {
        if(!callback_(results_))
            throw ConversionError(get_name() + "=" + detail::join(results_));
        if(!validators_.empty()) {
            for(const std::string &result : results_)
                for(const std::function &vali : validators_)
                    if(!vali(result))
                        throw ValidationError(get_name() + "=" + result);
        }
    }

_parse_single

_parse_single 函数将参数分成POSITIONAL_MARK、SUBCOMMAND、LONG、SHORT、NONE五个种类。

  • SUBCOMMAND 代表解析的该参数在subcommands列表中
  • LONG 代表参数 --XXX
  • SHORT 代表参数 -XXX
  • NONE 代表参数 XXX
  • POSITIONAL_MARK 攒不知什么作用

_parse_subcommand

如果找到对应名字的 subcommand (com)对象,弹出最后一个参数,执行 com->_parse(args)

_parse_long

取出当前参数,--name=value 格式,找出对应的option,设置给result 属性

_parse_short

取出当前参数,-name 格式, 查找对应 option 对象,根据 option 对象的 expected 字段,接受剩余的 参数,并设置给 optionresult 属性

_parse_positional

如果当前已接受的option 参数个数还未到达expected的数量,添加到vector parse_order_ 中。

callback

每个 command 或者 option 都可以设置一个回调, 解析命令行的参数后,会依次调用这些 callback

以新建一个账户为例:

createAccount->set_callback([this] {
    if( !active_key_str.size() )
       active_key_str = owner_key_str;
    public_key_type owner_key, active_key;
    try {
       owner_key = public_key_type(owner_key_str);
    } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid owner public key: ${public_key}", ("public_key", owner_key_str));
    try {
       active_key = public_key_type(active_key_str);
    } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid active public key: ${public_key}", ("public_key", active_key_str));
    auto create = create_newaccount(creator, account_name, owner_key, active_key);
    if (!simple) {
       if ( buy_ram_eos.empty() && buy_ram_bytes_in_kbytes == 0) {
            .......
          send_actions( { create, buyram, delegate } );
       } else {
          send_actions( { create, buyram } );
       }
    } else {
       send_actions( { create } );
    }
});

这段回调最终走到 send_actions , 再到 push_actions

void send_actions(std::vector&& actions, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
   auto result = push_actions( move(actions), extra_kcpu, compression);

   if( tx_print_json ) {
      cout << fc::json::to_pretty_string( result ) << endl;
   } else {
      print_result( result );
   }
}

fc::variant push_actions(std::vector&& actions, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) {
   signed_transaction trx;
   trx.actions = std::forward(actions);

   return push_transaction(trx, extra_kcpu, compression);
}

打包交易数据后,然后会走到push_transaction

fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
   
   ......
   
   if (!tx_dont_broadcast) {
      return call(push_txn_func, packed_transaction(trx, compression));
   } else {
      return fc::variant(trx);
   }
}

最终会走到 call(push_txn_func, packed_transaction(trx, compression));
跟踪push_txn_func会发现是个字符串

   const string chain_func_base = "/v1/chain";
   const string push_txn_func = chain_func_base + "/push_transaction";

call 函数最终调用:

template
fc::variant call( const std::string& url,
                  const std::string& path,
                  const T& v ) {
   try {
      eosio::client::http::connection_param *cp = new eosio::client::http::connection_param(context, parse_url(url) + path,
              no_verify ? false : true, headers);

      return eosio::client::http::do_http_call( *cp, fc::variant(v), print_request, print_response );
   }
   catch(boost::system::system_error& e) {
        ......   
   }
}

do_http_call 函数就将打包好的参数,path 信息以http请求的形式发送出去了,返回值给cleos在命令行输出

你可能感兴趣的:(EOS 源码分析 -- cleos)