作項目中發現本身對不少東西的理解有誤差,因此決定作個總結,也分享給須要的朋友。若有錯誤和遺漏,歡迎溝通交流。git
GitHub示例源碼github
本文介紹了Go中原生和第三方RPC使用方法,環境搭建方法並提供了材料。golang
遠程過程調用(Remote Procedure Call),通俗的說,RPC能夠實現跨機器、跨語言調用其餘計算機的程序。舉個例子,我在機器A上用C語言封裝了某個功能的函數,我能夠經過RPC在機器B上用GO語言調用機器A上的指定函數。 RPC爲C/S模型,一般使用TCP或http協議。shell
go RPC能夠利用tcp或http來傳遞數據,能夠對要傳遞的數據使用多種類型的編解碼方式。json
Golang官方的net/rpc庫能夠經過tcp或http傳遞數據,但net/rpc庫使用encoding/gob進行編解碼,支持tcp或http數據傳輸方式,因爲其餘語言不支持gob編解碼方式,因此使用net/rpc庫實現的RPC方法沒辦法進行跨語言調用。服務器
package main import ( "net/rpc" "net" "fmt" "net/http" ) type Chen struct { } //rcp方法 //func (t *T) MethodName(argType T1, replyType *T2) error func (this *Chen) GetAdd(data int, sum *int) error { *sum = data + 100 return nil } func main() { //1.對象實例化 pd := new(Chen) //2. rpc註冊 rpc.Register(pd) //3. rpc網絡 rpc.HandleHTTP() //4. 監聽網絡 ln, err := net.Listen("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("net.Listen error:", err) return } //5. 等待鏈接 http.Serve(ln, nil) } 複製代碼
package main import ( "net/rpc" "fmt" ) func main() { //1. 鏈接服務器 cln, err := rpc.DialHTTP("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("rpc.Dial error:", err) return } defer cln.Close() //2. 調用服務器函數 var data int err = cln.Call("Chen.GetAdd",10, &data) if err != nil { fmt.Println("cln.Call error:", err) return } //3. 打印輸出 fmt.Println("計算結果爲:", data) } 複製代碼
客戶端輸出:計算結果爲: 110
markdown
Go官方還提供了使用json編解碼的rpc庫:net/rpc/jsonrpc,可是使用tcp傳遞數據,不能用http。網絡
package main import ( "net/rpc" "net" "fmt" "net/rpc/jsonrpc" ) type Chen struct { } //rcp方法 //func (t *T) MethodName(argType T1, replyType *T2) error func (this *Chen) GetAdd(data int, sum *int) error { *sum = data + 100 return nil } func main() { //1.對象實例化 pd := new(Chen) //2. rpc註冊 rpc.Register(pd) //3. 監聽網絡 ln, err := net.Listen("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("net.Listen error:", err) return } //4. 處理客戶端請求 for { conn, err := ln.Accept() if err != nil { fmt.Println("Accept error:", err) continue } go func(conn net.Conn) { jsonrpc.ServeConn(conn) }(conn) } } 複製代碼
package main import ( "fmt" "net/rpc/jsonrpc" ) func main() { //1. 鏈接服務器 cln, err := jsonrpc.Dial("tcp", "127.0.0.1:12306") if err != nil { fmt.Println("jsonrpc.Dial error:", err) return } defer cln.Close() //2. 調用服務器函數 var data int err = cln.Call("Chen.GetAdd",10, &data) if err != nil { fmt.Println("cln.Call error:", err) return } //3. 打印輸出 fmt.Println("計算結果爲:", data) } 複製代碼
客戶端輸出:計算結果爲: 110
tcp
因此爲了真正實現跨主機、跨語言的遠程調用,須要使用第三方的RPC庫,推薦使用谷歌開源的gRPC。gRPC基於HTTP/2,採用protobuf進行數據編解碼,壓縮和傳輸效率更高。能夠參考本人的Go語言protobuf入門瞭解Go語言protobuf的環境搭建和使用。函數
因爲不能直接訪問golang官網,因此安裝gPRC和go擴展包比較麻煩,能夠從本人gRPC環境包安裝中獲取壓縮包。
unzip x.zip -d /$GOPATH/src/golang.org/x
unzip google.golang.org.zip -d /$GOPATH/src/google.golang.org
複製代碼
啓動服務器端,
$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_server $ go run main.go 複製代碼
啓動客戶端,
$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_client $ go run main.go 複製代碼
若是客戶端打印出2019/06/10 15:26:12 Greeting: Hello world
字樣即表示gRPC環境正常。
//版本 syntax = "proto3"; //包名 package myproto; //服務 service Hello { //這兒註釋纔有效 rpc GetAdd(In)returns(Out);//這兒註釋無效 } //傳入 message In { //此處1不是賦值,而是指參數序號 int64 num = 1; } //傳出 message Out { //此處1不是賦值,而是指參數序號 int64 size = 1; } 複製代碼
在.proto文件所在目錄執行下面的指令,
protoc --go_out=plugins=grpc:./ *.proto
複製代碼
生成go代碼時要指定plugins=grpc表示生成的是gRPC代碼。
package main import ( "fmt" pb "gRPC/myproto" //給package起別名 "context" "net" "google.golang.org/grpc" ) //1. 結構體 type Chen struct { } //2. 該結構體實現HelloServer interface的方法 func (this *Chen)GetAdd(ctx context.Context, In *pb.In)(*pb.Out,error) { return &pb.Out{Size:In.Num+100},nil } func main() { fmt.Println("server runing...") //3. 建立網絡 ln, err := net.Listen("tcp", "127.0.0.1:12345") if err != nil { fmt.Println("net.Listen error:", err) return } defer ln.Close() //4. 建立gRPC句柄 srv := grpc.NewServer() //5. 註冊server pb.RegisterHelloServer(srv, &Chen{}) //6. 等待網絡鏈接 err = srv.Serve(ln) if err != nil { fmt.Println("srv.Serve error:", err) return } } 複製代碼
package main import ( "google.golang.org/grpc" "fmt" pb "gRPC/myproto" "context" ) func main() { //1 鏈接服務器 conn, err := grpc.Dial("127.0.0.1:12345",grpc.WithInsecure())//grpc.WithInsecure()指定後纔不會報錯 if err != nil { fmt.Println("grpc.Dial error:", err) return } defer conn.Close() //2 建立客戶端句柄 cln := pb.NewHelloClient(conn) //3 調用服務器函數(RPC) out,err := cln.GetAdd(context.Background(), &pb.In{Num:10}) if err != nil { fmt.Println("grpc.Dial error:", err) return } //4 打印 fmt.Println("獲得數據:", out.Size) } 複製代碼
前後運行服務器和客戶端代碼,可在客戶端打印輸出獲得數據: 110
,說明已經成功在客戶端調用服務端程序。