作項目中發現本身對不少東西的理解有誤差,因此決定作個總結,也分享給須要的朋友。若有錯誤和遺漏,歡迎溝通交流。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方法沒辦法進行跨語言調用。bash
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
服務器
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
,說明已經成功在客戶端調用服務端程序。