tokuhirom's Blog

libkyototycoon をつかって 5分で C++ で RPC サーバーを書く方法

ちょっとした RPC サーバーを C/C++ でかきたいな、なんてケースはままあるわけですが、そんなときに便利なライブラリがあったので紹介します。

KyotoTycoon をつかうと、TSVRPC over HTTP な RPC サーバーが超簡単にかけて便利だった。libkyototycoon は GPL だけど、それが問題にならない場合なら、とてもいいとおもう。商用ライセンスかえるならそれもいいとおもう。

RPC サーバーとしては他にもいくつか実装があるんだけど、HTTP の上で実装されているから、デバッグが容易だったりとか、直接 telnet ではなしたりとか tcpdump できたりして、いろいろ便利なので、よいとおもう。

SConstruct でビルドルールをかいてから

e = Environment()
e.Program('ktechoserver', ['ktechoserver.cc'], parse_flags='-lkyototycoon -lkyotocabinet')

コードをかいて

// LICENSE: GPLv2
#include <ktrpc.h>
#include <string>

namespace kt = kyototycoon;
namespace kc = kyotocabinet;

static kt::RPCServer *g_serv = NULL;

// get the logger into the standard stream
inline kt::RPCServer::Logger* stdlogger(const char* prefix, std::ostream* strm) {
  class LoggerImpl : public kt::RPCServer::Logger {
  public:
    explicit LoggerImpl(std::ostream* strm, const char* prefix) :
      strm_(strm), prefix_(prefix) {}
    void log(Kind kind, const char* message) {
      const char* kstr = "MISC";
      switch (kind) {
        case kt::RPCServer::Logger::DEBUG: kstr = "DEBUG"; break;
        case kt::RPCServer::Logger::INFO: kstr = "INFO"; break;
        case kt::RPCServer::Logger::SYSTEM: kstr = "SYSTEM"; break;
        case kt::RPCServer::Logger::ERROR: kstr = "ERROR"; break;
      }
      *strm_ << prefix_ << ": [" << kstr << "]: " << message << std::endl;
    }
  private:
    std::ostream* strm_;
    const char* prefix_;
  };
  static LoggerImpl logger(strm, prefix);
  return &logger;
}

// stop the running server
static void stopserver(int signum) {
    if (g_serv) g_serv->stop();
    g_serv = NULL;
}

int main(int argc, char **argv) {
    kt::setkillsignalhandler(stopserver);

    class Worker : public kt::RPCServer::Worker {
    private:
        kt::RPCClient::ReturnValue process(kt::RPCServer* serv, kt::RPCServer::Session* sess,
                                        const std::string& name,
                                        const std::map<std::string, std::string>& inmap,
                                        std::map<std::string, std::string>& outmap) {
            if (name == "add") {
                serv->log(kt::RPCServer::Logger::SYSTEM, "concat");
                const char *a = kt::strmapget(inmap, "a");
                const char *b = kt::strmapget(inmap, "b");
                if (!a || !b) {
                    return kt::RPCClient::RVELOGIC;
                }
                serv->log(kt::RPCServer::Logger::SYSTEM, "%s + %s", a, b);
                outmap["result"] = kc::strprintf("%d", strtol(a, NULL , 10) + strtol(b, NULL, 10));
                return kt::RPCClient::RVSUCCESS;
            } else {
                return kt::RPCClient::RVENOIMPL;
            }
        }
    };

    Worker worker;

    kt::RPCServer::Logger* logger = stdlogger("ktechoserver", &std::cout);

    kt::RPCServer serv;
    serv.set_network("127.0.0.1:1987", 5); // 5 はタイムアウト
    serv.set_worker(&worker, 4); // 4 はワーカースレッドの数
    serv.set_logger(logger);
    serv.log(kt::RPCServer::Logger::SYSTEM, "================ [START]");
    g_serv = &serv;

    // start the server and block until its stop
    serv.start();

    // clean up connections and other resources
    serv.finish();

    serv.log(kt::RPCServer::Logger::SYSTEM, "================ [FINISH]");

    return 0;
}

動作確認は TSVRPC::Client っていう Perl のライブラリがあったので、それをつかった。

perl -E 'use TSVRPC::Client; use Data::Dumper; say Dumper(TSVRPC::Client->new(base => "http://127.0.0.1:1987/rpc/")->call("add", {a => 1, b => 2}));'

簡単だ!

MacBook Air 11インチ欲しい!