标准库的RPC

RPC是远程调用的简称, 简单的说就是要像调用本地函数一样调用服务器的函数.

Go语言的标准库已经提供了RPC框架和不同的RPC实现.

下面是一个服务器的例子:

type
 Echo intfunc
 (t *Echo) Hi(args string, reply *string) error {    *reply
 = "echo:"
+ args    return
nil}func
 main() {    rpc.Register(new(Echo))    rpc.HandleHTTP()    l,
 e := net.Listen("tcp", ":1234")    if
e != nil {        log.Fatal("listen
 error:",
 e)    }    http.Serve(l,
 nil)}
rpc.RegisterEchorpc.RegisterName

被注册对象的类型所有满足以下规则的方法会被导出到RPC服务接口:

func
 (t *T) MethodName(argType T1, replyType *T2) error

被注册对应至少要有一个方法满足这个特征, 否则可能会注册失败.

rpc.HandleHTTPrpc.ServeConn
Echo.Hi
func
 main() {    client,
 err := rpc.DialHTTP("tcp", "127.0.0.1:1234")    if
err != nil {        log.Fatal("dialing:",
 err)    }    var
 args = "hello
 rpc"    var
 reply string    err
 = client.Call("Echo.Hi",
 args, &reply)    if
err != nil {        log.Fatal("arith
 error:",
 err)    }    fmt.Printf("Arith:
 %d*%d=%d\n",
 args.A, args.B, reply)}
rpc.DialHTTP
clientclient.Call
"127.0.0.1:1234/debug/rpc"

基于 JSON 的 RPC 调用

在上面的RPC例子中, 我们采用了默认的HTTP协议作为RPC调用的传输载体.

net/rpcjsonrpcrpc.DialHTTPjsonrpcrpc.DialHTTP
DialHTTP
gob
gob
"net/rpc/jsonrpc"
rpc.ServeCodec
func
 main() {    lis,
 err := net.Listen("tcp", ":1234")    if
err != nil {        return
err    }    defer
 lis.Close()    srv
 := rpc.NewServer()    if
err := srv.RegisterName("Echo", new(Echo));
 err != nil {        return
err    }    for
{        conn,
 err := lis.Accept()        if
err != nil {            log.Fatalf("lis.Accept():
 %v\n",
 err)        }        go
 srv.ServeCodec(jsonrpc.NewServerCodec(conn))    }}
jsonrpc.Dialrpc.Dial
func
 main() {    client,
 err := jsonrpc.DialHTTP("tcp", "127.0.0.1:1234")    if
err != nil {        log.Fatal("dialing:",
 err)    }    ...}
jsonrpcjsonrpc
jsonrpc

基于 Protobuf 的 RPC 调用

Protobuf 是 Google 公司开发的编码协议. 它的优势是编码后的数据体积比较小(并不是压缩算法), 比较适合用于命令的传输编码.

Protobuf 官方团队提供 Java/C++/Python 几个语言的支持, Go语言的版本由Go团队提供支持, 其他语言由第三方支持.

Protobuf 的语言规范中可以定义RPC接口. 但是在Go语言和C++版本的Protobuf中都没有生成RPC的实现.

protoc-gen-go

该实现支持Go语言和C++语言, 在Protobuf官方wiki的第三方RPC实现列表中有介绍:https://code.google.com/p/protobuf/wiki/ThirdPartyAddOns#RPC_Implementations

arith.pb/arith.proto
package
arith;//
 go use cc_generic_services optionoption
 cc_generic_services = true;message
 ArithRequest {    optional
 int32 a = 1;    optional
 int32 b = 2;}message
 ArithResponse {    optional
 int32 val = 1;    optional
 int32 quo = 2;    optional
 int32 rem = 3;}service
 ArithService {    rpc
 multiply (ArithRequest) returns (ArithResponse);    rpc
 divide (ArithRequest) returns (ArithResponse);}
cc_generic_servicescc_generic_servicestrue
protoc.exe
protoc-gen-go
go
 get code.google.com/p/protorpcgo
 get code.google.com/p/protorpc/protoc-gen-go
protoc.exeprotoc-gen-go.exe$PATH
cd
 arith.pb && protoc --go_out=. arith.proto
arith.pb/arith.pb.go

下面是基于 Protobuf-RPC 的服务器:

package
mainimport
(    "errors"    "code.google.com/p/goprotobuf/proto"    "./arith.pb")type
 Arith intfunc
 (t *Arith) Multiply(args *arith.ArithRequest, reply *arith.ArithResponse) error {    reply.Val
 = proto.Int32(args.GetA() * args.GetB())    return
nil}func
 (t *Arith) Divide(args *arith.ArithRequest, reply *arith.ArithResponse) error {    if
args.GetB() == 0
{        return
errors.New("divide
 by zero")    }    reply.Quo
 = proto.Int32(args.GetA() / args.GetB())    reply.Rem
 = proto.Int32(args.GetA() % args.GetB())    return
nil}func
 main() {    arith.ListenAndServeArithService("tcp", ":1984", new(Arith))}
"./arith.pb"aritharith.pb/arith.proto
arith.ArithRequestarith.ArithResponsearith.pb/arith.proto
arith.ListenAndServeArithServicearith.EchoService
arith.DialArithService
stub,
 client, err := arith.DialArithService("tcp", "127.0.0.1:1984")if
err != nil {    log.Fatal(`arith.DialArithService("tcp", "127.0.0.1:1984"):`,
 err)}defer
 client.Close()
arith.DialArithServicestub
var
 args ArithRequestvar
 reply ArithResponseargs.A
 = proto.Int32(7)args.B
 = proto.Int32(8)if
err = stub.Multiply(&args, &reply); err != nil {    log.Fatal("arith
 error:",
 err)}fmt.Printf("Arith:
 %d*%d=%d",
 args.GetA(), args.GetB(), reply.GetVal())

相比标准的RPC的库, protorpc 由以下几个优点:

protoc-gen-gointerface{}snappy

不足之处是使用流程比标准RPC要繁复(需要将proto转换为Go代码).

C++ 调用 Go 提供的 Protobuf-RPC 服务

protorpc 同时也提供了 C++ 语言的实现.

C++版本的安装如下:

hg clone https://code.google.com/p/protorpc.cxx/cd protorpc.cxx
protoc.exe--cxx_out
${protorpc_root}/protobuf/bin/protoc
 --cxx_out=. arith.proto
--cxx_out

下面是 C++ 的客户端链接 Go 语言版本的 服务器:

#include "arith.pb.h"#include
 #include
 int
main() {  ::google::protobuf::rpc::Client
 client("127.0.0.1", 1234);  service::ArithService::Stub
 arithStub(&client);  ::service::ArithRequest
 arithArgs;  ::service::ArithResponse
 arithReply;  ::google::protobuf::rpc::Error
 err;  //
 EchoService.mul  arithArgs.set_a(3);  arithArgs.set_b(4);  err
 = arithStub.multiply(&arithArgs, &arithReply);  if(!err.IsNil())
 {    fprintf(stderr, "arithStub.multiply:
 %s\n",
 err.String().c_str());    return
-1;  }  if(arithReply.c()
 != 12)
 {    fprintf(stderr, "arithStub.multiply:
 expected = %d, got = %d\n", 12,
 arithReply.c());    return
-1;  }  printf("Done.\n");  return
0;}

详细的使用说明请参考: README.md . 
更多的例子请参考: rpcserver.cc 
和 rpcclient.cc

总结

Go语言的RPC客户端是一个使用简单, 而且功能强大的RPC库. 基于标准的RPC库我们可以方便的定制自己的RPC实现(传输协议和串行化协议都可以定制).

net/rpc
HTTPHTTPrpc.Serverrpc.Clientrpc.Serverrpc.ServerCodecjsonrpc.NewServerCodecconnHTTPgobrpc.Clientrpc.Server

因为Go1需要保证API的兼容性, 因此上述的问题只能希望在未来的Go2能得到改善.


有疑问加站长微信联系(非本文作者)