篳路藍縷,以啓山林。撫有蠻夷,以屬華夏。不鳴則已,一鳴驚人。
——《左傳`宣公十二年》
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
實踐目標: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