gRPC-go 入門(1):Hello World

摘要

在這篇文章中,主要是跟你介紹一下gRPC這個東西。git

而後,我會建立一個簡單的練習項目,做爲gRPC的Hello World項目。github

在這個項目中,只有很簡單的一個RPC函數,用於說明gRPC的工做方式。golang

此外,我也會跟你分享一下我初次接觸gRPC所遇到的一些坑,主要是在protocol bufferproto-gen-go插件上面。web

1. 簡單介紹

在這一節的內容中,我將簡單的跟你介紹一下gRPC這個東西。
RPC的全稱是Remote Procedure Call,遠程過程調用。這是一種協議,是用來屏蔽分佈式計算中的各類調用細節,使得你能夠像是本地調用同樣直接調用一個遠程的函數。
gRPC又是什麼呢?用官方的話來講:編程

A high-performance, open-source universal RPC frameworkapi

gRPC是一個高性能的、開源的通用的RPC框架。服務器

gRPC中,咱們稱調用方爲client,被調用方爲server
跟其餘的RPC框架同樣,gRPC也是基於」服務定義「的思想。簡單的來說,就是咱們經過某種方式來描述一個服務,這種描述方式是語言無關的。在這個」服務定義「的過程當中,咱們描述了咱們提供的服務服務名是什麼,有哪些方法能夠被調用,這些方法有什麼樣的入參,有什麼樣的回參。數據結構

也就是說,在定義好了這些服務、這些方法以後,gRPC會屏蔽底層的細節,client只須要直接調用定義好的方法,就能拿到預期的返回結果。對於server端來講,還須要實現咱們定義的方法。一樣的,gRPC也會幫咱們屏蔽底層的細節,咱們只須要實現所定義的方法的具體邏輯便可。框架

你能夠發現,在上面的描述過程當中,所謂的」服務定義「,就跟定義接口的語義是很接近的。我更願意理解爲這是一種」約定「,雙方約定好接口,而後server實現這個接口,client調用這個接口的代理對象。至於其餘的細節,交給gRPCtcp

此外,gRPC仍是語言無關的。你能夠用C++做爲服務端,使用Golang、Java等做爲客戶端。爲了實現這一點,咱們在」定義服務「和在編碼和解碼的過程當中,應該是作到語言無關的

下面放一張官網上面的圖:

所以,gRPC使用了Protocol Buffers

在這裏我不會展開來說Protocol Buffers這個東西,你能夠把他當成一個代碼生成工具以及序列化工具。這個工具能夠把咱們定義的方法,轉換成特定語言的代碼。好比你定義了一種類型的參數,他會幫你轉換成Golang中的struct 結構體,你定義的方法,他會幫你轉換成func 函數。此外,在發送請求和接受響應的時候,這個工具還會完成對應的編碼和解碼工做,將你即將發送的數據編碼成gRPC可以傳輸的形式,又或者將即將接收到的數據解碼爲編程語言可以理解的數據格式。

gRPC的簡單介紹就到這裏,下面的內容咱們直接開始實踐。

2. 環境配置

在這一節中,可能不少內容會不那麼的適用。

可是限於篇幅,我沒有列舉全部的安裝方式。若是在安裝的過程當中你遇到了問題,能夠在網上搜索解決,也能夠在文章末尾找到個人聯繫方式,咱們一塊兒研究。

2.1 gRPC

go get google.golang.org/grpc

這一步安裝的是gRPC的核心庫,可是這一步是須要(特別的上網方式)的。因此若是在安裝過程當中出錯了,你能夠科學一波,也能夠找一找其餘的安裝方法。

2.2 protocol buffers

在Mac OS中,直接用brew安裝。

brew info protobuf

2.3 protoc-gen-go

上一步安裝的是protocol編譯器。而上文中咱們提到了能夠生成各類不一樣語言的代碼。所以,除了這個編譯器,咱們還須要配合各個語言的代碼生成工具。

對於Golang來講,稱爲protoc-gen-go

不過在這兒有個小小的坑,github.com/golang/protobuf/protoc-gen-gogoogle.golang.org/protobuf/cmd/protoc-gen-go是不一樣的。

區別在於前者是舊版本,後者是google接管後的新版本,他們之間的API是不一樣的,也就是說用於生成的命令,以及生成的文件都是不同的。

由於目前的gRPC-go源碼中的example用的是後者的生成方式,爲了與時俱進,本文也採起最新的方式。

你須要安裝兩個庫:

go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

由於這些文件在安裝grpc的時候,已經下載下來了,所以使用install命令就能夠了,而不須要使用get命令。

而後你看你的$GOPATH路徑,應該有標1和2的兩個文件:

至此,全部的準備工做已經完成。

3. proto文件建立

在開始開發以前,先說說咱們的目標。

在這個grpc-practice項目中,我但願實現一個功能,客戶端能夠發送消息給服務端,服務端收到消息後,返回響應給客戶端。

正如前面所說的,在開發serverclient以前,咱們須要先定義服務。

所以,在這一節的內容中,我將向你介紹proto文件的編寫。

3.1 項目結構

在這以前,先讓咱們看看整個項目的初始結構。

serverclient咱們先無論,在這一節內容中咱們先編寫`*.proto'文件。

在proto文件夾中建立message.proto文件。

在文件的第一行,咱們寫上:

syntax = "proto3";

這是在說明咱們使用的是proto3語法。

而後咱們應該寫上:

option go_package = ".;message";

這部分的內容是關於最後生成的go文件是處在哪一個目錄哪一個包中,.表明在當前目錄生成,message表明了生成的go文件的包名是message

而後咱們須要定義一個服務,在這個服務中須要有一個方法,這個方法能夠接受客戶端的參數,再返回服務端的響應。

那麼咱們能夠這麼寫:

service MessageSender {
  rpc Send(MessageRequest) returns (MessageResponse) {}
}

其實很容易能夠看出,咱們定義了一個service,稱爲MessageSender,這個服務中有一個rpc方法,名爲Send。這個方法會發送一個MessageRequest,而後返回一個MessageResponse

讓咱們在看看具體的MessageRequestMessageResponse

message MessageResponse {
  string responseSomething = 1;
}

message MessageRequest {
  string saySomething = 1;
}

message關鍵字,其實你能夠理解爲Golang中的結構體。這裏比較特別的是變量後面的「賦值」。注意,這裏並非賦值,而是在定義這個變量在這個message中的位置。更具體的內容我應該會在源碼分析部分講到。

在編寫完上面的內容後,在/grpc-practice/src/helloworld/proto目錄下執行以下命令:

protoc --go_out=. message.proto
protoc --go-grpc_out=. message.proto

這兩條命令會生成以下的兩個文件:

在這兩個文件中,包含了咱們定義方法的go語言實現,也包含了咱們定義的請求與相應的go語言實現。

簡單來說,就是protoc-gen-go已經把你定義的語言無關的message.proto轉換爲了go語言的代碼,以便serverclient直接使用。

注意,到了這一部分你可能會有一些疑惑。

在網上的一些教程中,有這樣的生成方式:

protoc --go_out=plugins=grpc:. helloworld.proto

這種生成方式,使用的就是github版本的protoc-gen-go,而目前這個項目已經由Google接管了。

而且,若是使用這種生成方式的話,並不會生成上圖中的xxx_grpc.pb.goxxx.pb.go兩個文件,只會生成xxx.pb.go這種文件。

此外,你也可能遇到這種錯誤:

protoc-gen-go-grpc: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--go-grpc_out: protoc-gen-go-grpc: Plugin failed with status code 1.

這是由於你沒有安裝protoc-gen-go-grpc這個插件,這個問題在本文中應該不會出現。

你還可能會遇到這種問題:

--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC

這是由於你安裝的是更新版本的protoc-gen-go,可是你卻用了舊版本的生成命令。

可是這兩種方法都是能夠完成目標的,只不過api不太同樣。本文是基於Google版本的protoc-gen-go進行示範。

至於其餘更詳細的資料,你能夠在這裏看到:https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.20.0#v1.20-generated-code

4. 服務端

4.1 註冊

咱們在server目錄下面建立一個server.go文件。

在main函數中加入以下的代碼:

srv := grpc.NewServer()
message.RegisterMessageSenderService(srv, &message.MessageSenderService{})

很容易能夠看出,咱們在這一部分建立了一個Server,而後註冊了咱們的Service。

在註冊函數的第二個參數中,咱們傳進去了一個MessageSenderService實例。

來看看這個實例有什麼樣的結構:

type MessageSenderService struct {
	Send func(context.Context, *MessageRequest) (*MessageResponse, error)
}

能夠看出,這個實例裏面有一個方法,這個方法就是咱們定義的send方法。也就是說,這一部分是須要咱們在Server端實現這個send方法的。

所以咱們建立這麼一個方法:

func handleSendMessage(ctx context.Context, req *message.MessageRequest) (*message.MessageResponse, error) {
	log.Println("receive message:", req.GetSaySomething())
	resp := &message.MessageResponse{}
	resp.ResponseSomething = "roger that!"
	return resp, nil
}

注意,「實現定義的方法」,並非說咱們須要建立一個同名的方法,而是說咱們須要建立一個有相同函數簽名的方法。也就是說,須要有相同的入參,出參。

而後咱們將這個方法寫進註冊函數中,變成了這樣:

message.RegisterMessageSenderService(srv, &message.MessageSenderService{
		Send: handleSendMessage,
	})

至此,咱們已經成功的在server端實現了咱們聲明的方法了。

4.2 監聽

其實這個過程跟golang的web服務器是很像的,也是建立Handler,而後對端口進行監聽。

那麼到了這一步也同樣。

listener, err := net.Listen("tcp", ":12345")
if err != nil {
	log.Fatalf("failed to listen: %v", err)
}

err = srv.Serve(listener)
if err != nil {
	log.Fatalf("failed to serve: %v", err)
}

監聽12345端口的TCP鏈接,而後啓動服務器。

至此,服務端開發完畢。

5. 客戶端

在客戶端中,咱們應該先與server端創建鏈接,而後纔可以調用各類方法。

conn, err := grpc.Dial("127.0.0.1:12345", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
	log.Fatalf("did not connect: %v", err)
}
defer conn.Close()

以上代碼,就是跟本地的12345端口創建鏈接。

而後,按照定義,咱們調用server端的方法,應該要像調用本地方法同樣方便。

那麼,咱們這麼作:

client := message.NewMessageSenderClient(conn)
resp, err := client.Send(context.Background(), &message.MessageRequest{SaySomething: "hello world!"})
if err != nil {
   log.Fatalf("could not greet: %v", err)
}

很容易能夠理解,咱們在本地建立了一個client,而後直接調用咱們以前定義好的Send方法,就能夠實現咱們須要的邏輯了。

簡單的來說,咱們在*.proto文件中定義了方法,而後在server端實現定義的rpc方法的具體邏輯,在client端調用這個方法。

對於其餘的部分,由proto buffer負責對Golang中存儲的數據結構與rpc傳輸中的數據進行轉換,grpc負責封裝全部的邏輯。

server端和client端都跑起來,你會看到這樣的畫面:

至此,成功Hello了個World。

寫在最後

首先,謝謝你能看到這裏!

在這篇文章中,主要是跟你介紹一下hello world的寫法,以及在say hello的過程當中可能遇到的一些坑。

我認爲最大的坑是在於protoc-gen-go這個插件這裏,由於兩種語法讓我迷惑了好久。

若是在這期間,你還有一些問題沒有解決,歡迎留言,或者直接公衆號找到我,咱們一塊兒研究。

若是在文章中有哪些錯誤,還請不吝指教,謝謝!

最後,再次感謝你能看到這裏!

按照慣例,甩個公衆號在這,無論有沒有問題,都歡迎來找我玩~

相關文章
相關標籤/搜索