ithewei преди 4 години
родител
ревизия
fd7c9fe417

+ 3 - 0
.gitignore

@@ -40,6 +40,9 @@ tags
 cscope*
 .ycm*
 
+# generated
+examples/protorpc/generated
+
 # output
 *.pid
 *.log

+ 16 - 0
Makefile

@@ -133,6 +133,22 @@ jsonrpc_server: prepare
 	$(RM) examples/jsonrpc/*.o
 	$(MAKEF) TARGET=$@ SRCDIRS=". base ssl event" SRCS="examples/jsonrpc/jsonrpc_server.c examples/jsonrpc/jsonrpc.c examples/jsonrpc/cJSON.c"
 
+protorpc: protorpc_client protorpc_server
+
+protorpc_protoc:
+	bash examples/protorpc/proto/protoc.sh
+
+protorpc_client: prepare protorpc_protoc
+	$(MAKEF) TARGET=$@ SRCDIRS=". base ssl event cpputil evpp examples/protorpc/generated" \
+		SRCS="examples/protorpc/protorpc_client.cpp examples/protorpc/protorpc.c" \
+		LIBS="protobuf"
+
+protorpc_server: prepare protorpc_protoc
+	$(RM) examples/protorpc/*.o
+	$(MAKEF) TARGET=$@ SRCDIRS=". base ssl event cpputil evpp examples/protorpc/generated" \
+		SRCS="examples/protorpc/protorpc_server.cpp examples/protorpc/protorpc.c" \
+		LIBS="protobuf"
+
 unittest: prepare
 	$(CC)  -g -Wall -O0 -std=c99   -I. -Ibase            -o bin/mkdir_p           unittest/mkdir_test.c         base/hbase.c
 	$(CC)  -g -Wall -O0 -std=c99   -I. -Ibase            -o bin/rmdir_p           unittest/rmdir_test.c         base/hbase.c

+ 1 - 0
README-CN.md

@@ -251,6 +251,7 @@ ab -c 100 -n 100000 http://127.0.0.1:8080/
 - HTTP客户端: [examples/http_client_test.cpp](examples/http_client_test.cpp)
 - WebSocket服务端: [examples/websocket_server_test.cpp](examples/websocket_server_test.cpp)
 - WebSocket客户端: [examples/websocket_client_test.cpp](examples/websocket_client_test.cpp)
+- protobufRPC示例: [examples/protorpc](examples/protorpc)
 
 ### 模拟实现著名的命令行工具
 - 网络连接工具: [examples/nc](examples/nc.c)

+ 1 - 0
README.md

@@ -248,6 +248,7 @@ ab -c 100 -n 100000 http://127.0.0.1:8080/
 - [examples/http_client_test.cpp](examples/http_client_test.cpp)
 - [examples/websocket_server_test.cpp](examples/websocket_server_test.cpp)
 - [examples/websocket_client_test.cpp](examples/websocket_client_test.cpp)
+- [examples/protorpc](examples/protorpc)
 
 ### simulate well-known command line tools
 - [examples/nc](examples/nc.c)

+ 79 - 0
examples/protorpc/handler/calc.h

@@ -0,0 +1,79 @@
+#ifndef HV_PROTO_RPC_HANDLER_CALC_H_
+#define HV_PROTO_RPC_HANDLER_CALC_H_
+
+#include "../router.h"
+
+#include "../generated/calc.pb.h"
+
+void calc_add(const protorpc::Request& req, protorpc::Response* res) {
+    // params
+    if (req.params_size() != 2) {
+        return bad_request(req, res);
+    }
+    protorpc::CalcParam param1, param2;
+    if (!param1.ParseFromString(req.params(0)) ||
+        !param2.ParseFromString(req.params(1))) {
+        return bad_request(req, res);
+    }
+
+    // result
+    protorpc::CalcResult result;
+    result.set_num(param1.num() + param2.num());
+    res->set_result(result.SerializeAsString());
+}
+
+void calc_sub(const protorpc::Request& req, protorpc::Response* res) {
+    // params
+    if (req.params_size() != 2) {
+        return bad_request(req, res);
+    }
+    protorpc::CalcParam param1, param2;
+    if (!param1.ParseFromString(req.params(0)) ||
+        !param2.ParseFromString(req.params(1))) {
+        return bad_request(req, res);
+    }
+
+    // result
+    protorpc::CalcResult result;
+    result.set_num(param1.num() - param2.num());
+    res->set_result(result.SerializeAsString());
+}
+
+void calc_mul(const protorpc::Request& req, protorpc::Response* res) {
+    // params
+    if (req.params_size() != 2) {
+        return bad_request(req, res);
+    }
+    protorpc::CalcParam param1, param2;
+    if (!param1.ParseFromString(req.params(0)) ||
+        !param2.ParseFromString(req.params(1))) {
+        return bad_request(req, res);
+    }
+
+    // result
+    protorpc::CalcResult result;
+    result.set_num(param1.num() * param2.num());
+    res->set_result(result.SerializeAsString());
+}
+
+void calc_div(const protorpc::Request& req, protorpc::Response* res) {
+    // params
+    if (req.params_size() != 2) {
+        return bad_request(req, res);
+    }
+    protorpc::CalcParam param1, param2;
+    if (!param1.ParseFromString(req.params(0)) ||
+        !param2.ParseFromString(req.params(1))) {
+        return bad_request(req, res);
+    }
+    if (param2.num() == 0) {
+        return bad_request(req, res);
+    }
+
+    // result
+    protorpc::CalcResult result;
+    result.set_num(param1.num() / param2.num());
+    res->set_result(result.SerializeAsString());
+}
+
+#endif // HV_PROTO_RPC_HANDLER_CALC_H_

+ 19 - 0
examples/protorpc/handler/handler.h

@@ -0,0 +1,19 @@
+#ifndef HV_PROTO_RPC_HANDLER_H_
+#define HV_PROTO_RPC_HANDLER_H_
+
+#include "../router.h"
+
+void error_response(protorpc::Response* res, int code, const std::string& message) {
+    res->mutable_error()->set_code(code);
+    res->mutable_error()->set_message(message);
+}
+
+void not_found(const protorpc::Request& req, protorpc::Response* res) {
+    error_response(res, 404, "Not Found");
+}
+
+void bad_request(const protorpc::Request& req, protorpc::Response* res) {
+    error_response(res, 400, "Bad Request");
+}
+
+#endif // HV_PROTO_RPC_HANDLER_H_

+ 25 - 0
examples/protorpc/handler/login.h

@@ -0,0 +1,25 @@
+#ifndef HV_PROTO_RPC_HANDLER_LOGIN_H_
+#define HV_PROTO_RPC_HANDLER_LOGIN_H_
+
+#include "../router.h"
+
+#include "../generated/login.pb.h"
+
+void login(const protorpc::Request& req, protorpc::Response* res) {
+    // params
+    if (req.params_size() == 0) {
+        return bad_request(req, res);
+    }
+    protorpc::LoginParam param;
+    if (!param.ParseFromString(req.params(0))) {
+        return bad_request(req, res);
+    }
+
+    // result
+    protorpc::LoginResult result;
+    result.set_user_id(123456);
+    result.set_token(param.username() + ":" + param.password());
+    res->set_result(result.SerializeAsString());
+}
+
+#endif // HV_PROTO_RPC_HANDLER_LOGIN_H_

+ 20 - 0
examples/protorpc/proto/base.proto

@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+package protorpc;
+
+message Error {
+    int32   code    = 1;
+    string  message = 2;
+}
+
+message Request {
+    uint64  id      = 1;
+    string  method  = 2;
+    repeated bytes params  = 3;
+}
+
+message Response {
+    uint64  id      = 1;
+    optional bytes  result  = 2;
+    optional Error  error   = 3;
+}

+ 11 - 0
examples/protorpc/proto/calc.proto

@@ -0,0 +1,11 @@
+syntax = "proto3";
+
+package protorpc;
+
+message CalcParam {
+    int64   num     = 1;
+}
+
+message CalcResult {
+    int64   num     = 1;
+}

+ 13 - 0
examples/protorpc/proto/login.proto

@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+package protorpc;
+
+message LoginParam {
+    string  username    = 1;
+    string  password    = 2;
+}
+
+message LoginResult {
+    uint64  user_id     = 1;
+    string  token       = 2;
+}

+ 18 - 0
examples/protorpc/proto/protoc.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+cd `dirname $0`
+
+PROTOC=`which protoc`
+if [ $? -ne 0 ]; then
+    echo "Not found command protoc!"
+    echo "Please install libprotobuf first!"
+    exit 1
+fi
+
+CPP_OUT_DIR=../generated
+if [ ! -d "${CPP_OUT_DIR}" ]; then
+    mkdir -p ${CPP_OUT_DIR}
+fi
+
+set -x
+${PROTOC} --cpp_out=${CPP_OUT_DIR} *.proto

+ 53 - 0
examples/protorpc/protorpc.c

@@ -0,0 +1,53 @@
+#include "protorpc.h"
+
+#include <string.h> // import memcpy
+
+int protorpc_pack(const protorpc_message* msg, void* buf, int len) {
+    if (!msg || !buf || !len) return -1;
+    const protorpc_head* head = &(msg->head);
+    unsigned int packlen = protorpc_package_length(head);
+    // Check is buffer enough
+    if (len < packlen) {
+        return -2;
+    }
+    unsigned char* p = (unsigned char*)buf;
+    // flags
+    *p++ = head->flags;
+    // hton length
+    unsigned int length = head->length;
+    *p++ = (length >> 24) & 0xFF;
+    *p++ = (length >> 16) & 0xFF;
+    *p++ = (length >>  8) & 0xFF;
+    *p++ =  length        & 0xFF;
+    // memcpy body
+    if (msg->body && head->length) {
+        memcpy(p, msg->body, head->length);
+    }
+
+    return packlen;
+}
+
+int protorpc_unpack(protorpc_message* msg, const void* buf, int len) {
+    if (!msg || !buf || !len) return -1;
+    if (len < PROTORPC_HEAD_LENGTH) return -2;
+    protorpc_head* head = &(msg->head);
+    const unsigned char* p = (const unsigned char*)buf;
+    // flags
+    head->flags = *p++;
+    // ntoh length
+    head->length  = ((unsigned int)*p++) << 24;
+    head->length |= ((unsigned int)*p++) << 16;
+    head->length |= ((unsigned int)*p++) << 8;
+    head->length |= *p++;
+    // Check is buffer enough
+    unsigned int packlen = protorpc_package_length(head);
+    if (len < packlen) {
+        return -3;
+    }
+    // NOTE: just shadow copy
+    if (len > PROTORPC_HEAD_LENGTH) {
+        msg->body = (const char*)buf + PROTORPC_HEAD_LENGTH;
+    }
+
+    return packlen;
+}

+ 35 - 0
examples/protorpc/protorpc.h

@@ -0,0 +1,35 @@
+#ifndef HV_PROTO_RPC_H_
+#define HV_PROTO_RPC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// flags:1byte + length:4bytes = 5bytes
+#define PROTORPC_HEAD_LENGTH  5
+typedef struct {
+    unsigned char   flags;
+    unsigned int    length;
+} protorpc_head;
+
+typedef const char* protorpc_body;
+
+typedef struct {
+    protorpc_head   head;
+    protorpc_body   body;
+} protorpc_message;
+
+static inline unsigned int protorpc_package_length(const protorpc_head* head) {
+    return PROTORPC_HEAD_LENGTH + head->length;
+}
+
+// @retval >0 package_length, <0 error
+int protorpc_pack(const protorpc_message* msg, void* buf, int len);
+// @retval >0 package_length, <0 error
+int protorpc_unpack(protorpc_message* msg, const void* buf, int len);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // HV_PROTO_RPC_H_

+ 261 - 0
examples/protorpc/protorpc_client.cpp

@@ -0,0 +1,261 @@
+/*
+ * proto rpc client
+ *
+ * @build   make protorpc
+ * @server  bin/protorpc_server 1234
+ * @client  bin/protorpc_client 127.0.0.1 1234 add 1 2
+ *
+ */
+
+#include "TcpClient.h"
+
+#include <mutex>
+#include <condition_variable>
+
+using namespace hv;
+
+#include "protorpc.h"
+#include "generated/base.pb.h"
+#include "generated/calc.pb.h"
+#include "generated/login.pb.h"
+
+namespace protorpc {
+typedef std::shared_ptr<protorpc::Request>  RequestPtr;
+typedef std::shared_ptr<protorpc::Response> ResponsePtr;
+
+enum ProtoRpcResult {
+    kRpcSuccess     = 0,
+    kRpcTimeout     = -1,
+    kRpcError       = -2,
+    kRpcNoResult    = -3,
+    kRpcParseError  = -4,
+};
+
+class ProtoRpcContext {
+public:
+    protorpc::RequestPtr    req;
+    protorpc::ResponsePtr   res;
+private:
+    std::mutex              _mutex;
+    std::condition_variable _cond;
+
+public:
+    void wait(int timeout_ms) {
+        std::unique_lock<std::mutex> locker(_mutex);
+        _cond.wait_for(locker, std::chrono::milliseconds(timeout_ms));
+    }
+
+    void notify() {
+        _cond.notify_one();
+    }
+};
+typedef std::shared_ptr<ProtoRpcContext>    ContextPtr;
+
+class ProtoRpcClient : public TcpClient {
+public:
+    ProtoRpcClient() : TcpClient()
+    {
+        connect_state = kInitialized;
+
+        setConnectTimeout(5000);
+
+        ReconnectInfo reconn;
+        reconn.min_delay = 1000;
+        reconn.max_delay = 10000;
+        reconn.delay_policy = 2;
+        setReconnect(&reconn);
+
+        // init protorpc_unpack_setting
+        unpack_setting_t protorpc_unpack_setting;
+        memset(&protorpc_unpack_setting, 0, sizeof(unpack_setting_t));
+        protorpc_unpack_setting.mode = UNPACK_BY_LENGTH_FIELD;
+        protorpc_unpack_setting.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
+        protorpc_unpack_setting.body_offset = PROTORPC_HEAD_LENGTH;
+        protorpc_unpack_setting.length_field_offset = 1;
+        protorpc_unpack_setting.length_field_bytes = 4;
+        protorpc_unpack_setting.length_field_coding = ENCODE_BY_BIG_ENDIAN;
+        setUnpack(&protorpc_unpack_setting);
+
+        onConnection = [this](const SocketChannelPtr& channel) {
+            std::string peeraddr = channel->peeraddr();
+            if (channel->isConnected()) {
+                connect_state = kConnected;
+                printf("connected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
+            } else {
+                connect_state = kDisconnectd;
+                printf("disconnected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
+            }
+        };
+
+        onMessage = [this](const SocketChannelPtr& channel, Buffer* buf) {
+            // protorpc_unpack
+            protorpc_message msg;
+            memset(&msg, 0, sizeof(msg));
+            int packlen = protorpc_unpack(&msg, buf->data(), buf->size());
+            if (packlen < 0) {
+                printf("protorpc_unpack failed!\n");
+                return;
+            }
+            assert(packlen == buf->size());
+            // Response::ParseFromArray
+            protorpc::ResponsePtr res(new protorpc::Response);
+            if (!res->ParseFromArray(msg.body, msg.head.length)) {
+                return;
+            }
+            // id => res
+            calls_mutex.lock();
+            auto iter = calls.find(res->id());
+            if (iter == calls.end()) {
+                return;
+            }
+            auto ctx = iter->second;
+            calls_mutex.unlock();
+            ctx->res = res;
+            ctx->notify();
+        };
+    }
+
+    int connect(int port, const char* host = "127.0.0.1") {
+        createsocket(port, host);
+        connect_state = kConnecting;
+        start();
+        return 0;
+    }
+
+    protorpc::ResponsePtr call(protorpc::RequestPtr& req, int timeout_ms = 10000) {
+        if (connect_state != kConnected) {
+            return NULL;
+        }
+        static std::atomic<uint64_t> s_id = ATOMIC_VAR_INIT(0);
+        req->set_id(++s_id);
+        req->id();
+        auto ctx = new protorpc::ProtoRpcContext;
+        ctx->req = req;
+        calls[req->id()] = protorpc::ContextPtr(ctx);
+        // Request::SerializeToArray + protorpc_pack
+        protorpc_message msg;
+        memset(&msg, 0, sizeof(msg));
+        msg.head.length = req->ByteSizeLong();
+        int packlen = protorpc_package_length(&msg.head);
+        unsigned char* writebuf = NULL;
+        HV_ALLOC(writebuf, packlen);
+        packlen = protorpc_pack(&msg, writebuf, packlen);
+        if (packlen > 0) {
+            printf("%s\n", req->DebugString().c_str());
+            req->SerializeToArray(writebuf + PROTORPC_HEAD_LENGTH, msg.head.length);
+            channel->write(writebuf, packlen);
+        }
+        HV_FREE(writebuf);
+        // wait until response come or timeout
+        ctx->wait(timeout_ms);
+        auto res = ctx->res;
+        calls_mutex.lock();
+        calls.erase(req->id());
+        calls_mutex.unlock();
+        if (res == NULL) {
+            printf("RPC timeout!\n");
+        } else if (res->has_error()) {
+            printf("RPC error:\n%s\n", res->error().DebugString().c_str());
+        }
+        return res;
+    }
+
+    int calc(const char* method, int num1, int num2, int& out) {
+        protorpc::RequestPtr req(new protorpc::Request);
+        // method
+        req->set_method(method);
+        // params
+        protorpc::CalcParam param1, param2;
+        param1.set_num(num1);
+        param2.set_num(num2);
+        req->add_params()->assign(param1.SerializeAsString());
+        req->add_params()->assign(param2.SerializeAsString());
+
+        auto res = call(req);
+
+        if (res == NULL) return kRpcTimeout;
+        if (res->has_error()) return kRpcError;
+        if (!res->has_result()) return kRpcNoResult;
+        protorpc::CalcResult result;
+        if (!result.ParseFromString(res->result())) return kRpcParseError;
+        out = result.num();
+        return kRpcSuccess;
+    }
+
+    int login(const protorpc::LoginParam& param, protorpc::LoginResult* result) {
+        protorpc::RequestPtr req(new protorpc::Request);
+        // method
+        req->set_method("login");
+        // params
+        req->add_params()->assign(param.SerializeAsString());
+
+        auto res = call(req);
+
+        if (res == NULL) return kRpcTimeout;
+        if (res->has_error()) return kRpcError;
+        if (!res->has_result()) return kRpcNoResult;
+        if (!result->ParseFromString(res->result())) return kRpcParseError;
+        return kRpcSuccess;
+    }
+
+    enum {
+        kInitialized,
+        kConnecting,
+        kConnected,
+        kDisconnectd,
+    } connect_state;
+    std::map<uint64_t, protorpc::ContextPtr> calls;
+    std::mutex calls_mutex;
+};
+}
+
+int main(int argc, char** argv) {
+    if (argc < 6) {
+        printf("Usage: %s host port method param1 param2\n", argv[0]);
+        printf("method = [add, sub, mul, div]\n");
+        printf("Examples:\n");
+        printf("  %s 127.0.0.1 1234 add 1 2\n", argv[0]);
+        printf("  %s 127.0.0.1 1234 div 1 0\n", argv[0]);
+        return -10;
+    }
+    const char* host = argv[1];
+    int port = atoi(argv[2]);
+    const char* method = argv[3];
+    const char* param1 = argv[4];
+    const char* param2 = argv[5];
+
+    protorpc::ProtoRpcClient cli;
+    cli.connect(port, host);
+    while (cli.connect_state == protorpc::ProtoRpcClient::kConnecting) hv_msleep(1);
+    if (cli.connect_state == protorpc::ProtoRpcClient::kDisconnectd) {
+        return -20;
+    }
+
+    // test login
+    {
+        protorpc::LoginParam param;
+        param.set_username("admin");
+        param.set_password("123456");
+        protorpc::LoginResult result;
+        if (cli.login(param, &result) == protorpc::kRpcSuccess) {
+            printf("login success!\n");
+            printf("%s\n", result.DebugString().c_str());
+        } else {
+            printf("login failed!\n");
+        }
+    }
+
+    // test calc
+    {
+        int num1 = atoi(param1);
+        int num2 = atoi(param2);
+        int result = 0;
+        if (cli.calc(method, num1, num2, result) == protorpc::kRpcSuccess) {
+            printf("calc success!\n");
+            printf("%d %s %d = %d\n", num1, method, num2, result);
+        } else {
+            printf("calc failed!\n");
+        }
+    }
+    return 0;
+}

+ 130 - 0
examples/protorpc/protorpc_server.cpp

@@ -0,0 +1,130 @@
+/*
+ * proto rpc server
+ *
+ * @build   make protorpc
+ * @server  bin/protorpc_server 1234
+ * @client  bin/protorpc_client 127.0.0.1 1234 add 1 2
+ *
+ */
+
+#include "hloop.h"
+#include "hbase.h"
+#include "hsocket.h"
+
+#include "TcpServer.h"
+
+using namespace hv;
+
+#include "protorpc.h"
+#include "router.h"
+#include "handler/handler.h"
+#include "handler/calc.h"
+#include "handler/login.h"
+
+protorpc_router router[] = {
+    {"add", calc_add},
+    {"sub", calc_sub},
+    {"mul", calc_mul},
+    {"div", calc_div},
+    {"login", login},
+};
+#define PROTORPC_ROUTER_NUM  (sizeof(router)/sizeof(router[0]))
+
+class ProtoRpcServer : public TcpServer {
+public:
+    ProtoRpcServer() : TcpServer()
+    {
+        onConnection = [](const SocketChannelPtr& channel) {
+            std::string peeraddr = channel->peeraddr();
+            if (channel->isConnected()) {
+                printf("%s connected! connfd=%d\n", peeraddr.c_str(), channel->fd());
+            } else {
+                printf("%s disconnected! connfd=%d\n", peeraddr.c_str(), channel->fd());
+            }
+        };
+        onMessage = handleMessage;
+        // init protorpc_unpack_setting
+        unpack_setting_t protorpc_unpack_setting;
+        memset(&protorpc_unpack_setting, 0, sizeof(unpack_setting_t));
+        protorpc_unpack_setting.mode = UNPACK_BY_LENGTH_FIELD;
+        protorpc_unpack_setting.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
+        protorpc_unpack_setting.body_offset = PROTORPC_HEAD_LENGTH;
+        protorpc_unpack_setting.length_field_offset = 1;
+        protorpc_unpack_setting.length_field_bytes = 4;
+        protorpc_unpack_setting.length_field_coding = ENCODE_BY_BIG_ENDIAN;
+        setUnpack(&protorpc_unpack_setting);
+    }
+
+    int listen(int port) { return createsocket(port); }
+
+private:
+    static void handleMessage(const SocketChannelPtr& channel, Buffer* buf) {
+        // unpack -> Request::ParseFromArray -> router -> Response::SerializeToArray -> pack -> Channel::write
+        // protorpc_unpack
+        protorpc_message msg;
+        memset(&msg, 0, sizeof(msg));
+        int packlen = protorpc_unpack(&msg, buf->data(), buf->size());
+        if (packlen < 0) {
+            printf("protorpc_unpack failed!\n");
+            return;
+        }
+        assert(packlen == buf->size());
+
+        // Request::ParseFromArray
+        protorpc::Request req;
+        protorpc::Response res;
+        if (req.ParseFromArray(msg.body, msg.head.length)) {
+            printf("> %s\n", req.DebugString().c_str());
+            res.set_id(req.id());
+            // router
+            const char* method = req.method().c_str();
+            bool found = false;
+            for (int i = 0; i < PROTORPC_ROUTER_NUM; ++i) {
+                if (strcmp(method, router[i].method) == 0) {
+                    found = true;
+                    router[i].handler(req, &res);
+                    break;
+                }
+            }
+            if (!found) {
+                not_found(req, &res);
+            }
+        } else {
+            bad_request(req, &res);
+        }
+
+        // Response::SerializeToArray + protorpc_pack
+        memset(&msg, 0, sizeof(msg));
+        msg.head.length = res.ByteSizeLong();
+        packlen = protorpc_package_length(&msg.head);
+        unsigned char* writebuf = NULL;
+        HV_ALLOC(writebuf, packlen);
+        packlen = protorpc_pack(&msg, writebuf, packlen);
+        if (packlen > 0) {
+            printf("< %s\n", res.DebugString().c_str());
+            res.SerializeToArray(writebuf + PROTORPC_HEAD_LENGTH, msg.head.length);
+            channel->write(writebuf, packlen);
+        }
+        HV_FREE(writebuf);
+    }
+};
+
+int main(int argc, char** argv) {
+    if (argc < 2) {
+        printf("Usage: %s port\n", argv[0]);
+        return -10;
+    }
+    int port = atoi(argv[1]);
+
+    ProtoRpcServer srv;
+    int listenfd = srv.listen(port);
+    if (listenfd < 0) {
+        return -20;
+    }
+    printf("protorpc_server listen on port %d, listenfd=%d ...\n", port, listenfd);
+    srv.setThreadNum(4);
+    srv.start();
+
+    while (1) hv_sleep(1);
+    return 0;
+}

+ 24 - 0
examples/protorpc/router.h

@@ -0,0 +1,24 @@
+#ifndef HV_PROTO_RPC_ROUTER_H_
+#define HV_PROTO_RPC_ROUTER_H_
+
+#include "generated/base.pb.h"
+
+typedef void (*protorpc_handler)(const protorpc::Request& req, protorpc::Response* res);
+
+typedef struct {
+    const char*      method;
+    protorpc_handler handler;
+} protorpc_router;
+
+void error_response(protorpc::Response* res, int code, const std::string& message);
+void not_found(const protorpc::Request& req, protorpc::Response* res);
+void bad_request(const protorpc::Request& req, protorpc::Response* res);
+
+void calc_add(const protorpc::Request& req, protorpc::Response* res);
+void calc_sub(const protorpc::Request& req, protorpc::Response* res);
+void calc_mul(const protorpc::Request& req, protorpc::Response* res);
+void calc_div(const protorpc::Request& req, protorpc::Response* res);
+
+void login(const protorpc::Request& req, protorpc::Response* res);
+
+#endif // HV_PROTO_RPC_ROUTER_H_