這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go語言版本的Protobuf-RPC基本算完成了. 現在簡單說下使用方法.
安裝測試環境
先下載代碼(不支援go get):
hg clone https://bitbucket.org/chai2010/gopath
然後下載後的目錄設定為GOPATH, 並添加$GOPATH/bin到PATH環境變數.
在$GOPATH/bin中已經包含了Windows下的2.4.1版本的protoc.exe. 如果是Linux等系統, 請自行下載並安裝protoc程式.
安裝protoc.exe的Go語言外掛程式:
go install encoding/protobuf/protoc-gen-go
該外掛程式是基於code.google.com/p/goprotobuf/protoc-gen-go實現, 主要增加了encoding/protobuf/protoc-gen-go/generator/service.go檔案, 用於RPC的代碼產生. 產生的RPC代碼依賴net/rpc/protorpc, 這個包是Protobuf-RPC的底層實現, 可以單獨使用.
現在可以運行一下測試程式:
C:\>go test net/rpc/protorpc/service.pbok net/rpc/protorpc/service.pb 2.123s
測試通過, 繼續.
編譯 proto 檔案
建立一個名為pbrpc的工作目錄, 再建立pbrpc/arith.pb的子目錄.
將net/rpc/protorpc/service.pb/service.proto檔案複製到pbrpc/arith.pb的子目錄.
包名字改為arith, 檔案arith.proto的內容如下:
package arith;option cc_generic_services = true;option java_generic_services = true;option py_generic_services = true;message ArithRequest { optional int32 a = 1; optional int32 b = 2;}message ArithResponse { optional int32 c = 1;}service ArithService { rpc add (ArithRequest) returns (ArithResponse); rpc mul (ArithRequest) returns (ArithResponse); rpc div (ArithRequest) returns (ArithResponse); rpc error (ArithRequest) returns (ArithResponse);}
主要是定義了一個ArithService介面. 要注意的是cc_generic_services/java_generic_services, py_generic_services幾個選項.
我前提提到的protoc-gen-go在產生代碼的時候, 這3個選項至少要有一個為true, 才會產生RPC的代碼.
當然, 如果不產生RPC代碼的話, 也是可以單獨使用net/rpc/protorpc包的. 不過protoc-gen-go產生的程式碼會簡便很多.
進入pbrpc/arith.pb的子目錄, 編譯arith.proto檔案:
protoc --go_out=. arith.proto
產生 arith.pb.go 檔案, 其中RPC的代碼主要是下面這些:
type ArithService interface { Add(in *ArithRequest, out *ArithResponse) error Mul(in *ArithRequest, out *ArithResponse) error Div(in *ArithRequest, out *ArithResponse) error Error(in *ArithRequest, out *ArithResponse) error}// RegisterArithService publish the given ArithService implementation on the server.func RegisterArithService(srv *rpc.Server, x ArithService) error { if err := srv.RegisterName("ArithService", x); err != nil { return err } return nil}// ServeArithService serves the given ArithService implementation on conn.func ServeArithService(conn io.ReadWriteCloser, x ArithService) error { srv := rpc.NewServer() if err := srv.RegisterName("ArithService", x); err != nil { return err } srv.ServeCodec(protorpc.NewServerCodec(conn)) return nil}// ListenAndServeArithService listen announces on the local network address laddr// and serves the given ArithService implementation.func ListenAndServeArithService(network, addr string, x ArithService) error { clients, err := net.Listen(network, addr) if err != nil { return err } srv := rpc.NewServer() if err := srv.RegisterName("ArithService", x); err != nil { return err } for { conn, err := clients.Accept() if err != nil { return err } go srv.ServeCodec(protorpc.NewServerCodec(conn)) } panic("unreachable")}type rpcArithServiceStub struct { *rpc.Client}func (c *rpcArithServiceStub) Add(in *ArithRequest, out *ArithResponse) error { return c.Call("ArithService.Add", in, out)}func (c *rpcArithServiceStub) Mul(in *ArithRequest, out *ArithResponse) error { return c.Call("ArithService.Mul", in, out)}func (c *rpcArithServiceStub) Div(in *ArithRequest, out *ArithResponse) error { return c.Call("ArithService.Div", in, out)}func (c *rpcArithServiceStub) Error(in *ArithRequest, out *ArithResponse) error { return c.Call("ArithService.Error", in, out)}// DialArithService connects to an ArithService at the specified network address.func DialArithService(network, addr string) (*rpc.Client, ArithService, error) { conn, err := net.Dial(network, addr) if err != nil { return nil, nil, err } c, srv := NewArithServiceClient(conn) return c, srv, nil}// NewArithServiceClient returns a ArithService rpc.Client and stub to handle// requests to the set of ArithService at the other end of the connection.func NewArithServiceClient(conn io.ReadWriteCloser) (*rpc.Client, ArithService) { c := rpc.NewClientWithCodec(protorpc.NewClientCodec(conn)) return c, &rpcArithServiceStub{c}}// NewArithServiceStub returns a ArithService stub to handle rpc.Client.func NewArithServiceStub(c *rpc.Client) ArithService { return &rpcArithServiceStub{c}}
其中產生的伺服器端的代碼有: ListenAndServeArithService, ServeArithService, RegisterArithService.
產生的用戶端的介面有: DialArithService, NewArithServiceClient, NewArithServiceStub.
其中RPC介面對應ArithService介面.
編寫測試代碼
在pbrpc目錄建立rpc_server.go檔案, 代碼如下:
package mainimport ( "encoding/protobuf/proto" "errors" "./arith.pb")type Arith intfunc (t *Arith) Add(args *arith.ArithRequest, reply *arith.ArithResponse) error { reply.C = proto.Int32(args.GetA() + args.GetB()) return nil}func (t *Arith) Mul(args *arith.ArithRequest, reply *arith.ArithResponse) error { reply.C = proto.Int32(args.GetA() * args.GetB()) return nil}func (t *Arith) Div(args *arith.ArithRequest, reply *arith.ArithResponse) error { if args.GetB() == 0 { return errors.New("divide by zero") } reply.C = proto.Int32(args.GetA() / args.GetB()) return nil}func (t *Arith) Error(args *arith.ArithRequest, reply *arith.ArithResponse) error { return errors.New("ArithError")}func main() { arith.ListenAndServeArithService("tcp", ":1234", new(Arith))}
最關鍵的是arith.ListenAndServeArithService("tcp", ":1234", new(Arith)). 當然, 也可以使用RegisterArithService或ServeArithService等介面進行定製.
然後在pbrpc建立rpc_client.go對應用戶端, 代碼如下:
package mainimport ( "encoding/protobuf/proto" "log" "./arith.pb")func main() { // client client, stub, err := arith.DialArithService("tcp", "127.0.0.1:1234") if err != nil { log.Fatalf(`arith.DialArithService("tcp", "127.0.0.1:1234"): %v`, err) } defer client.Close() var args arith.ArithRequest var reply arith.ArithResponse // Add args.A = proto.Int32(1) args.B = proto.Int32(2) if err = stub.Add(&args, &reply); err != nil { log.Fatalf(`arith.Add: %v`, err) } if reply.GetC() != 3 { log.Fatalf(`arith.Add: expected = %d, got = %d`, 3, reply.GetC()) } // Mul args.A = proto.Int32(2) args.B = proto.Int32(3) if err = stub.Mul(&args, &reply); err != nil { log.Fatalf(`arith.Mul: %v`, err) } if reply.GetC() != 6 { log.Fatalf(`arith.Mul: expected = %d, got = %d`, 6, reply.GetC()) } // Div args.A = proto.Int32(13) args.B = proto.Int32(5) if err = stub.Div(&args, &reply); err != nil { log.Fatalf(`arith.Div: %v`, err) } if reply.GetC() != 2 { log.Fatalf(`arith.Div: expected = %d, got = %d`, 2, reply.GetC()) } // Div zero args.A = proto.Int32(1) args.B = proto.Int32(0) if err = stub.Div(&args, &reply); err.Error() != "divide by zero" { log.Fatalf(`arith.Error: expected = %s, got = %s`, "divide by zero", err.Error()) } // Error args.A = proto.Int32(1) args.B = proto.Int32(2) if err = stub.Error(&args, &reply); err.Error() != "ArithError" { log.Fatalf(`arith.Error: expected = %s, got = %s`, "ArithError", err.Error()) } log.Printf("Done")}
然後就可以啟動服務, 並測試用戶端了.