分別以google.golang.org/grpc和net/rpc簡單理解RPC

                    篳路藍縷,以啓山林。撫有蠻夷,以屬華夏。不鳴則已,一鳴驚人。
                                                                                                          ——《左傳`宣公十二年》

 

rpc:遠程過程調用,是一個思想,一個概念。核心是分佈式應用間通信,屏蔽不同語言,解耦(個人認爲)。

net/rpc包是一個go自帶的rpc實現方式,可以基於tcp、http、json rpc等方式進行調用。

gRpc基於Netty HTTP/2協議棧封裝底層通信,啓動時啓動一個Netty server。

特別提醒(踩坑後可以返回來再看):如果沒有用go module模式,那麼需要在src下手動創建google.golang.org目錄並進入該目錄手動執行git clone https://e.coding.net/robinqiwei/googleprotobuf.git protobuf,否則包不能正確拉取(報找不到google.golang.org/protobuf/proto...等一堆錯);如果用的go module模式,那麼這個是自動完成的,無需手動創建。

目錄

net/rpc

google.golang.org/grpc


net/rpc

實踐目標:server端與client端可以實現通信。此實例採用TCP方式調用。項目目錄結構:

pub.go

package base1_public

import (
	"fmt"
	"net/rpc"
)

const HelloServiceName = "pkg.HelloService"

type HelloServiceInterface interface {
	HelloS(request string, reply *string) error // 之所以命名爲HelloS,是爲了驗證與其對應之處
}

func RegisterHelloService(svc HelloServiceInterface) error {
	return rpc.RegisterName(HelloServiceName, svc)
}

type HelloService struct{}

func (p *HelloService) HelloS(request string, reply *string) error {
	*reply = "hello:" + request
	fmt.Println("服務端回覆:", *reply)
	return nil
}

服務端

package main

import (
	"log"
	"net"
	"net/rpc"
	"rpc-test/base1_public"
)

func main() {
	base1_public.RegisterHelloService(new(base1_public.HelloService))

	listener, err := net.Listen("tcp", ":3234")
	if err != nil {
		log.Fatal("ListenTCP error:", err)
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal("Accept error:", err)
		}

		go rpc.ServeConn(conn) // 此次server被終止時纔會執行下次循環繼續開啓新的server,否則一直阻塞等待
	}
}

客戶端

package main

import (
	"fmt"
	"log"
	"net/rpc"
	"rpc-test/base1_public"
)

type HelloServiceClient struct {
	*rpc.Client
}


func DialHelloService(network, address string) (*HelloServiceClient, error) {
	c, err := rpc.Dial(network, address)
	if err != nil {
		return nil, err
	}
	return &HelloServiceClient{Client: c}, nil
}

func (p *HelloServiceClient) start(request string, reply *string) error {
	// 方式1
	//return p.Client.Call(base1_public.HelloServiceName+".HelloS", request, reply) // HelloS與接口的方法名對應即可

	// 方式2
	helloCall := p.Client.Go(base1_public.HelloServiceName+".HelloS", request, reply, nil)
	//helloCall = <-helloCall.Done
	return (<-helloCall.Done).Error
	//if err := helloCall.Error; err != nil {
	//	log.Fatal(err)
	//}

}

func main() {
	client, err := DialHelloService("tcp", "localhost:3234")
	if err != nil {
		log.Fatal("dialing:", err)
	}

	var reply string
	err = client.start("哈哈哈", &reply)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("收到的回覆:", reply)
}

分別啓動服務端和客戶端,控制檯分別如下:

服務端回覆: hello:哈哈哈

收到的回覆: hello:哈哈哈

http、json-rpc等方式的調用可移步:https://blog.csdn.net/ffzhihua/article/details/83270771

google.golang.org/grpc

實踐目標:服務端有兩個服務,客戶端我要使用(調用)這兩個服務。

背景設計:遠端某個地方有兩個服務:分別是加密服務和解密服務,client調用時只需傳明文或密文作爲參數即可。

項目結構如下:

通過.proto文件約定好服務接口、參數等,通過工具protoc-gen-go生成客戶端和服務端共用的對照表。

secret.proto

syntax = "proto3";

package proto;

service SecretService {
  rpc Encrypt(SecretRequest) returns (SecretResponse) {}
  rpc Decrypt(SecretRequest) returns (SecretResponse) {}
}

message SecretRequest {
  string request = 1;
}

message SecretResponse {
  string response = 1;
}

生成的secret.pb.go挺長,這裏就不貼了。生成方式及環境搭建可移步https://blog.csdn.net/HYZX_9987/article/details/106462026

服務端

package main

import (
	"context"
	"encoding/base64"
	"fmt"
	pt "gRpc-demo/proto"
	"google.golang.org/grpc"
	"log"
	"net"
)

const PORT = "9001"

type SecretServiceStruct struct{}

// 加密服務
func (s *SecretServiceStruct) Encrypt(ctx context.Context, r *pt.SecretRequest) (*pt.SecretResponse, error) {
	log.Print("調用了加密服務:", r.Request)
	secret := base64.StdEncoding.EncodeToString([]byte(r.Request))
	return &pt.SecretResponse{Response: r.GetRequest() + "加密成功:" + secret}, nil
}

// 解密服務
func (s *SecretServiceStruct) Decrypt(ctx context.Context, r *pt.SecretRequest) (*pt.SecretResponse, error) {
	log.Print("調用瞭解密服務:", r.Request)
	str, err := base64.StdEncoding.DecodeString(r.Request)
	if err != nil {
		log.Fatalf("服務端解密失敗: %v", err.Error())
		return &pt.SecretResponse{Response: ""}, err
	}
	return &pt.SecretResponse{Response: r.GetRequest() + "解密成功:" + string(str)}, nil
}

func main() {
	server := grpc.NewServer()
	pt.RegisterSecretServiceServer(server, &SecretServiceStruct{})
	lis, err := net.Listen("tcp", ":"+PORT)
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}

	addr := fmt.Sprintf("%s", lis.Addr())
	log.Printf("Listening on %v", addr)
	log.Fatal(server.Serve(lis))
}

客戶端

package main

import (
	"context"
	pt "gRpc-demo/proto"
	"google.golang.org/grpc"
	"log"
	"strings"
)

const PORT = "9001"

var str = `this is gRpc`

func main() {
	// 上面gRPC的服務沒有提供證書支持,因此客戶端在連接服務器中通過grpc.WithInsecure()選項跳過對服務器證書的驗證
	conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("grpc.Dial err: %v", err)
	}
	defer conn.Close()

	client := pt.NewSecretServiceClient(conn)
	resp0, err := client.Encrypt(context.Background(), &pt.SecretRequest{
		Request: str,
	})
	if err != nil {
		log.Fatalf("client.Encrypt err: %v", err)
	}
	secretStr := strings.Split(resp0.GetResponse(), ":")[1]
	log.Printf("請求加密服務: %s", secretStr)

	resp1, err := client.Decrypt(context.Background(), &pt.SecretRequest{
		Request: secretStr,
	})
	if err != nil {
		log.Fatalf("client.Decrypt err: %v", err)
	}

	resStr := strings.Split(resp1.GetResponse(), ":")[1]
	log.Printf("請求解密服務: %s", resStr)
}

分別啓動服務端、客戶端,控制檯分別如下:

服務端:
2020/06/01 09:41:20 Listening on [::]:9001
2020/06/01 09:41:37 調用了加密服務:this is gRpc
2020/06/01 09:41:37 調用瞭解密服務:dGhpcyBpcyBnUnBj

客戶端:
2020/06/01 09:41:37 請求加密服務: dGhpcyBpcyBnUnBj
2020/06/01 09:41:37 請求解密服務: this is gRpc

 

 

 

 

 

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