grpc基本實踐(一)

        此篇中主要簡單實現了,grpc的4種交互,包括普通請求,客戶端流,服務端流,和雙向流。其中服務端採用go語言實現,客戶端將採用java實現。

        在此篇中基本可以瞭解到protobuf的基本知識,以及多文件編譯上的一些問題。以及在grpc請求上,對一些基本數據的發送和返回。其中你可以通過兩種形式拿到自己想要的list數據。

        需要注意的是此篇中用到的grpc爲版本1.10。這個版本的context依然用的是golang.org/x/net/context中的,所以大家想用grpc一定要翻牆把這個下好。

        首先我們先看一下proto文件。

        

        此次練習實踐,主要包含了兩個proto文件,包含user_server.proto和user_vo.proto。一方面是對數據方法和服務方法進行分離,另一方面也是爲了可以用到多文件編譯。這樣做還能在編譯安卓用到的文件時遇到的不支持服務的問題(當然你也可以直接用java的生成代碼)。

        接下來讓我們看一下, 在user_vo.proto中所定義的一些基本數據:

syntax = "proto3";

package testg;

message Empty{}

message UserList{
    repeated User u = 1;
}

message User{
    string user_id = 1;
    string user_name = 2;
    int32 age = 3;
    Msg msg = 4;
}

message Msg{
    string msg = 1;
}

            首先頭部的syntax="proto3"是必須的,否則他將按proto2編譯。其次因爲grpc無論是否有數據要發送返回,你都必須定義發送數據和返回數據。所以我們在這裏定義了一個空的數據以方便我們在不需要傳數據時使用。

            protobuf在使用中只有基本類型,如果數據時一個list,你則需要用到repeatred這個關鍵字,他代表你的這個數據是不定的,可以是0個,1個或者更多。

            oneof :可以讓你包裹的幾個字段只能有一個被賦值。


            在proto2中的關鍵字,在proto3中已經刪除:

            required : 表示此字段必填。此字段需要小心設置因爲如果忘記設置帶有此關鍵字的字段,你的數據將被拒絕解析。

            optional :  可選字段。(在proto3中默認就是可選的)


        接下來讓我們看看user_server.proto:

syntax = "proto3";

package testg;

import "testg/user_vo.proto";

service UserServer {
    rpc GetUserById(User) returns (User);
    rpc GetList(Empty) returns (UserList);
    rpc GetListStream(Empty) returns (stream User);
    rpc SetUserStream(stream User) returns (UserList);
    rpc Chat(stream User) returns (stream User);
}

        在這個文件中,主要是定義了grpc的服務和方法,其中我們通過使用import來引用我們要使用的其他文件的方法數據。在這裏我們引用了user_vo.proto的數據類型。

        數據和服務都定義好後,我們就可以開始編譯了,在編譯上,單文件和多文件編譯時一樣的,但是這裏會有個小坑,如果不注意還是很頭疼的。在編譯一個文件時,我們基本不會遇到什麼問題。但是多文件編譯我們必須把所引用到的文件都一起編譯,多個proto文件使用空格隔開。

        如果我們一個一個編譯的話,在go中你會遇到找不到某方法或數據的問題,而在java中會發現缺少某文件。

        在此篇中go的編譯爲:

        protoc --go_out=plugins=grpc:.   ./user_vo.proto  ./user_server.proto

        在java的編譯爲:

        1.通過 protoc --java_out=./ ./user_vo.proto  ./user_server.proto生成proto文件的java版

        2.通過 protoc --plugin=protoc-gen-grpc-java.exe --grpc-java_out=./ ./user_vo.proto  ./user_server.proto生成grpc服務的和客戶端文件

         在多文件編譯中,java版會額外生成一個UserServerOuterClass.java,即對某個文件的額外引用文件。如果一個一個編譯你將會缺少這個文件。

         所以大家在編譯的時候一定要記住把所以引用到的proto文件都編譯上,不要分開編譯。

         

        編譯成功後,我們就可以開始代碼的正式編寫了。

        作爲服務端,我們首先需要實現所以 的接口。否則你將不能使用grpc服務。

        其中具體要實現那些接口,大家可以在自己生成的grpc服務文件中找到,在此次中我們需要實現的接口有:

        

        接下來就是定義一個空結構體來實現這五個方法:

func (u *UserServer) GetUserById(c context.Context,user *testg.User) (*testg.User, error) {
	userId := user.UserId
	if userId == "" {
		return nil, nil
	}
	fmt.Println("userId:", userId)
	ul := testListFunc()
	for i:=0;i<len(ul.U);i++{
		if ul.U[i].UserId == user.UserId {
			return ul.U[i], nil
		}
	}
	return nil, nil
}

           這個方法通過客戶端拿到傳過來的參數,返回一個user的對象。

    

func (u *UserServer) GetList(c context.Context,e *testg.Empty) (*testg.UserList, error) {
	return testListFunc(),nil
}

            在這個方法中我們返回了list,這也是第一種返回list的方法,主要是利用了定義數據中的repeatred,讓我們可以把user對象拿到一個list。

            同時這兩個方法都是對基礎grpc服務的實現,即沒有用到流,都是一問一答。在客戶端發來請求後,服務端給客戶端一個應答。

           

func (u *UserServer) GetListStream(e *testg.Empty, us testg.UserServer_GetListStreamServer) error {
	ul := testListFunc()
	for i:=0;i<len(ul.U);i++{
		us.Send(ul.U[i])
	}
	return nil
}

            這個方法中,我們實現了服務端流,即在客戶端發來一個請求後,服務端以流的形式持續返回數據給客戶端,通過方法看到,服務端流中有一個send的發送方法, 它允許我們不斷給客戶端發送數據。通過這個方法我們就實現第二種獲得list的方法,每次 發送一個user對象,不間斷髮送直到沒有數據可發。

                  

func (u *UserServer) SetUserStream(us testg.UserServer_SetUserStreamServer) error {
	for {
		user,err := us.Recv()
		if err == io.EOF {
			return us.SendAndClose(testListFunc())
		}
		if err != nil {
			return err
		}
		fmt.Println("user", user)
	}
	return nil
}

          這個方法中我們實現了客戶端流,即客戶端會持續發送數據,當發送完成後,客戶端會給服務端一個eof錯誤,服務端在拿到eof就關閉通道併發送返回數據給客戶端。其中通過us.Recv()拿到客戶端發來的數據。

            

func (u *UserServer) Chat(us testg.UserServer_ChatServer) error {
	for {
		user,err := us.Recv()
		if err == io.EOF {
			return nil
		}
		fmt.Println("user",user)
		us.Send(user)
	}
	return nil
}

        這個方法就是實現雙向流的,我們通過Recv()方法拿到客戶端數據,服務端通過Send()方法發送數據給客戶端。這個基本就是客戶端流和服務端流的結合。


        在下一篇中,我們將講關於客戶端的基本實現。


        大家也可以去GitHub下載我的練習源碼:

        https://github.com/aixinaxc/grpcserver (服務端)

        https://github.com/aixinaxc/grpcclient-golang (客戶端)

            





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章