在Go語言中使用 Protobuf-RPC
Go語言版本的Protobuf-RPC基本算完成了. 現在簡單說下使用方法.
安裝測試環境
先下載程式碼(不支援go get
):
hg clone https://bitbucket.org/chai2010/protorpc
然後下載後的目錄設定為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.pb
ok 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 main
import (
"encoding/protobuf/proto"
"errors"
"./arith.pb"
)
type Arith int
func (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 main
import (
"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")
}
然後就可以啟動服務, 並測試客戶端了.
PS: 看來圖靈社群這類地方不是很喜歡技術類的文章, 以後技術文章就不發這裡了. 以後來這裡只看別人八卦灌水 :)
補充: 目前專案已經移到googlecode, 可以直接用go命令安裝:
go get code.google.com/p/protorpc
更多Go文章請訪問: http://my.oschina.net/chai2010
相關文章
- 在 Go 語言中,我為什麼使用介面Go
- Go 語言中的 collect 使用Go
- 在Go語言中,怎樣使用Json的方法?GoJSON
- Go 語言中使用 ETCDGo
- 論go語言中goroutine的使用Go
- hash 表在 go 語言中的實現Go
- 在 go 語言中利用反射精簡程式碼Go反射
- 為什麼在Go語言中要慎用interface{}Go
- Go 語言中 defer 使用時有哪些陷阱?Go
- Go 語言中的方法Go
- Go語言中的InterfaceGo
- 在 Go 語言中增強 Cookie 的安全性GoCookie
- 【Go】四捨五入在go語言中為何如此困難Go
- Go語言中切片slice的宣告與使用Go
- 在PHP語言中使用JSONPHPJSON
- Go 語言中的外掛Go
- Go 語言中的 切片 --sliceGo
- Go語言中使用正則提取匹配的字串Go字串
- 如何在 Go 語言中使用 Redis 連線池GoRedis
- GO 語言中的物件導向Go物件
- Go語言中的併發模式Go模式
- Go 語言中 strings 包常用方法Go
- Go語言中的單元測試Go
- 認識 Go 語言中的陣列Go陣列
- Go語言中的變數作用域Go變數
- 【Go學習筆記7】go語言中的模組(包)Go筆記
- Go 語言中的格式化輸出Go
- Go 語言中的兩種 slice 表示式Go
- go語言中import不允許迴圈包含GoImport
- 詳細解讀go語言中的chnanelGoNaN
- Go語言中mysql資料庫操作(一)GoMySql資料庫
- Go語言中時間輪的實現Go
- Go語言中defer的一些坑Go
- 聊聊Go語言中的陣列與切片Go陣列
- 9.Go語言中的流程控制Go
- static在C語言中的作用C語言
- Go 語言中常見的幾種反模式Go模式
- go 語言中的 rune,獲取字元長度Go字元