不得不懂系列(2)-玩轉Go中的RPC

說明

作項目中發現本身對不少東西的理解有誤差,因此決定作個總結,也分享給須要的朋友。若有錯誤和遺漏,歡迎溝通交流。git

GitHub示例源碼github

本文介紹了Go中原生和第三方RPC使用方法,環境搭建方法並提供了材料。golang

RPC

遠程過程調用(Remote Procedure Call),通俗的說,RPC能夠實現跨機器、跨語言調用其餘計算機的程序。舉個例子,我在機器A上用C語言封裝了某個功能的函數,我能夠經過RPC在機器B上用GO語言調用機器A上的指定函數。 RPC爲C/S模型,一般使用TCP或http協議。shell

Golang官方RPC

go RPC能夠利用tcp或http來傳遞數據,能夠對要傳遞的數據使用多種類型的編解碼方式。json

net/rpc庫

Golang官方的net/rpc庫能夠經過tcp或http傳遞數據,但net/rpc庫使用encoding/gob進行編解碼,支持tcp或http數據傳輸方式,因爲其餘語言不支持gob編解碼方式,因此使用net/rpc庫實現的RPC方法沒辦法進行跨語言調用。服務器

server端代碼

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)
}
複製代碼

client代碼

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)
}
複製代碼

運行結果

客戶端輸出:計算結果爲: 110markdown

net/rpc/jsonrpc庫

Go官方還提供了使用json編解碼的rpc庫:net/rpc/jsonrpc,可是使用tcp傳遞數據,不能用http。網絡

server代碼

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)
	}
}
複製代碼

client代碼

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)
}
複製代碼

運行結果

客戶端輸出:計算結果爲: 110tcp

gRPC

因此爲了真正實現跨主機、跨語言的遠程調用,須要使用第三方的RPC庫,推薦使用谷歌開源的gRPC。gRPC基於HTTP/2,採用protobuf進行數據編解碼,壓縮和傳輸效率更高。能夠參考本人的Go語言protobuf入門瞭解Go語言protobuf的環境搭建和使用。函數

gRPC安裝

因爲不能直接訪問golang官網,因此安裝gPRC和go擴展包比較麻煩,能夠從本人gRPC環境包安裝中獲取壓縮包。

unzip x.zip -d /$GOPATH/src/golang.org/x
unzip google.golang.org.zip -d /$GOPATH/src/google.golang.org
複製代碼

gRPC環境測試

啓動服務器端,

$ 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環境正常。

創建proto文件

//版本
syntax = "proto3";

//包名
package myproto;

//服務
service Hello {
    //這兒註釋纔有效
    rpc GetAdd(In)returns(Out);//這兒註釋無效
}

//傳入
message In {
    //此處1不是賦值,而是指參數序號
    int64 num = 1;
}

//傳出
message Out {
    //此處1不是賦值,而是指參數序號
    int64 size = 1;
}
複製代碼

生成go代碼

在.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,說明已經成功在客戶端調用服務端程序。

相關文章
相關標籤/搜索