Bootstrap

RPC与GRPC

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

rpc是基于tcp协议的,但是gRPC是http2.0传输协议。实现rpc功能需要序列化协议传输协议。go内置的rpc比较灵活,默认是gob序列化协议,tcp传输协议,也可以去修改


提示:以下是本篇文章正文内容,下面案例可供参考

一、RPC

1.1、RPC的入门使用

Go内置的RPC里默认是使用gob序列化协议的,其他语言没有gob编码序列化,默认不不跨语言通信的,但rpc支持替换序列化。

服务端

package main

import (
	"net"
	"net/rpc"
)

type HelloService struct{}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}

func main() {
	//1. 实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2. 注册处理逻辑 handler
	_ = rpc.RegisterName("HelloService", &HelloService{})
	//3. 启动服务
	conn, _ := listener.Accept() //当一个新的连接进来的时候,
	rpc.ServeConn(conn)

	//一连串的代码大部分都是net的包好像和rpc没有关系
	//不行。rpc调用中有几个问题需要解决 1. call id 2. 序列化和反序列化 编码和解码
	//python下的开发而言 这个就显得不好用
	//可以跨语言调用呢 1. go语言的rpc的序列化协议是什么(Gob) 2. 能否替换成常见的序列化
}

客户端

package main

import (
	"fmt"
	"net/rpc"
)

func main() {
	//1. 建立连接
	client, err := rpc.Dial("tcp", "localhost:1234")
	if err != nil {
		panic("连接失败")
	}
	var reply string //string有默认值
	//调用连接。
	err = client.Call("HelloService.Hello", "bobby", &reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(reply)
}

1.2、替换rpc的序列化协议为json

替换为json格式,就可以支持跨语言通信了

服务端

package main

import (
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
)

type HelloService struct{}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}

func main() {
	//1. 实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2. 注册处理逻辑 handler
	_ = rpc.RegisterName("HelloService", &HelloService{})
	//3. 启动服务
	for {
		conn, _ := listener.Accept() //当一个新的连接进来的时候,
		go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
	}
}

Go客户端

package main

import (
	"fmt"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
)

func main() {
	//1. 建立连接
	conn, err := net.Dial("tcp", "localhost:1234")
	if err != nil {
		panic("连接失败")
	}
	var reply string //string有默认值
	client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
	err = client.Call("HelloService.Hello", "bobby", &reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(reply)
}

Python客户端
在这里插入图片描述

1.3、替换rpc的传输化协议为http

服务端

package main

import (
	"io"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
)

type HelloService struct{}

func (s *HelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}

func main() {
	//1. 实例化一个server
	_ = rpc.RegisterName("HelloService", &HelloService{})
	http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
		var conn io.ReadWriteCloser = struct {
			io.Writer
			io.ReadCloser
		}{
			ReadCloser: r.Body,
			Writer:     w,
		}
		rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
	})
	http.ListenAndServe(":1234", nil)
}

Python客户端
在这里插入图片描述

1.4、把RPC改造成gRPC的雏形。

  • 使用protobuf生成的文件,就是客户端和服务端的代理。我们只需要写业务的代码即可handle。
  • 服务端代理做了注册,细节是使用接口为形参,这样实现的解耦,只需实现了接口的类型函数即可作为参数。
  • 客户端的代理做了连接RPC的拨号,拿到句柄直接像函数调用一样去client.Hello("bobby", &reply)去调用远程函数。
//------------- server.go-------------
package main

import (
	"OldPackageTest/new_helloworld/hanlder"
	"net"
	"net/rpc"

	"OldPackageTest/new_helloworld/server_proxy"
)

func main() {
	//1. 实例化一个server
	listener, _ := net.Listen("tcp", ":1234")
	//2. 注册处理逻辑 handler
	_ = server_proxy.RegisterHelloService(&hanlder.NewHelloService{})
	//3. 启动服务
	for {
		conn, _ := listener.Accept() //当一个新的连接进来的时候,
		go rpc.ServeConn(conn)
	}

	//一连串的代码大部分都是net的包好像和rpc没有关系
	//不行。rpc调用中有几个问题需要解决 1. call id 2. 序列化和反序列化 编码和解码
	//python下的开发而言 这个就显得不好用
	//可以跨语言调用呢 1. go语言的rpc的序列化协议是什么(Gob) 2. 能否替换成常见的序列化
}


//-------------------server_proxy.go-----------------------
package server_proxy

import (
	"OldPackageTest/new_helloworld/hanlder"
	"net/rpc"
)

type HelloServicer interface {
	Hello(request string, reply *string) error
}

//如果做到解耦 - 我们关系的是函数 鸭子类型
func RegisterHelloService(srv HelloServicer) error {
	return rpc.RegisterName(hanlder.HelloServiceName, srv)
}


//-------------------handle.go-----------------------
package hanlder

//名称冲突的问题
const HelloServiceName = "handler/HelloService"

//我们关心的是NewHelloService这个名字呢 还是这个结构体中的方法
type NewHelloService struct{}

func (s *NewHelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply的值
	*reply = "hello, " + request
	return nil
}

//-------------------client.go-----------------------
package main

import (
	"OldPackageTest/new_helloworld/client_proxy"
	"fmt"
)

func main() {
	//1. 建立连接

	client := client_proxy.NewHelloServiceClient("tcp", "localhost:1234")
	//1. 只想写业务逻辑 不想关注每个函数的名称
	// 客户端部分
	var reply string //string有默认值
	err := client.Hello("bobby", &reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(reply)

	//1. 这些概念在grpc中都有对应
	//2. 发自灵魂的拷问: server_proxy 和 client_proxy能否自动生成啊 为多种语言生成
	//3. 都能满足 这个就是protobuf + grpc
}
//-------------------client_proxy.go-----------------------
package client_proxy

import (
	"OldPackageTest/new_helloworld/hanlder"
	"net/rpc"
)

type HelloServiceStub struct {
	*rpc.Client
}

//在go语言中没有类、对象 就意味着没有初始化方法
func NewHelloServiceClient(protcol, address string) HelloServiceStub {
	conn, err := rpc.Dial(protcol, address)
	if err != nil {
		panic("connect error!")
	}
	return HelloServiceStub{conn}
}

func (c *HelloServiceStub) Hello(request string, reply *string) error {
	err := c.Call(hanlder.HelloServiceName+".Hello", request, reply)
	if err != nil {
		return err
	}
	return nil
}

二、gRPC

2.1、基本使用

//-------------------server.go-----------------------
package main

import (
	"context"
	"net"

	"google.golang.org/grpc"

	"OldPackageTest/Mycode/helloworld/proto"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
	error) {
	return &proto.HelloReply{
		Message: "hello " + request.Name,
	}, nil
}

func main() {
	g := grpc.NewServer()
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:8088")
	if err != nil {
		panic("failed to listen:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("failed to start grpc:" + err.Error())
	}
}
//-------------------client.go-----------------------
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"

	"OldPackageTest/Mycode/helloworld/proto"
)

func main() {
	//stream
	conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)
	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

protobuf文件需要cmd到当前目录执行。
helloworld.proto文件名
protoc -I. helloworld.proto --go_out=plugins=grpc:.

syntax = "proto3";
option go_package = "./;proto";  //是./当前目录下, 报名叫proto
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}
//将 sessionid放入 放入cookie中 http协议
message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

//go语言中是生成一个文件, 也就只有python会生成两个文件

2.2、gRPC流模式

有三种流模式

  • 客户端流:如物联网终端实时的给服务器发送检测的实时数据,服务端给给终端一个响应
  • 服务端流:如客户端发给服务器股票代码,服务端回给客户端实时的股票趋势
  • 双向流:聊天机器人
//-------------server.go-------------------
package main

import (
	"OldPackageTest/stream_grpc_test/proto"
	"fmt"
	"google.golang.org/grpc"
	"net"
	"sync"
	"time"
)

const PORT = ":50052"

type server struct {
}

func (s *server) GetStream(req *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {
	i := 0
	for {
		i++
		_ = res.Send(&proto.StreamResData{
			Data: fmt.Sprintf("%v", time.Now().Unix()),
		})
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}

	return nil
}

func (s *server) PutStream(cliStr proto.Greeter_PutStreamServer) error {
	for {
		if a, err := cliStr.Recv(); err != nil {
			fmt.Println(err)
			break
		} else {
			fmt.Println(a.Data)
		}
	}

	return nil
}

func (s *server) AllStream(allStr proto.Greeter_AllStreamServer) error {
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for {
			data, _ := allStr.Recv()
			fmt.Println("收到客户端消息:" + data.Data)
		}
	}()

	go func() {
		defer wg.Done()
		for {
			_ = allStr.Send(&proto.StreamResData{Data: "我是服务器"})
			time.Sleep(time.Second)
		}
	}()

	wg.Wait()
	return nil
}

func main() {
	lis, err := net.Listen("tcp", PORT)
	if err != nil {
		panic(err)
	}
	s := grpc.NewServer()
	proto.RegisterGreeterServer(s, &server{})
	err = s.Serve(lis)
	if err != nil {
		panic(err)
	}
}



//-----------------client-----------------
package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"google.golang.org/grpc"

	"OldPackageTest/stream_grpc_test/proto"
)

func main() {
	conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	//服务端流模式
	c := proto.NewGreeterClient(conn)
	res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "慕课网"})
	for {
		a, err := res.Recv() //如果大家懂socket编程的话就明白 send recv
		if err != nil {
			fmt.Println(err)
			break
		}
		fmt.Println(a.Data)
	}

	//客户端流模式
	putS, _ := c.PutStream(context.Background())
	i := 0
	for {
		i++
		_ = putS.Send(&proto.StreamReqData{
			Data: fmt.Sprintf("慕课网%d", i),
		})
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}

	//双向流模式
	allStr, _ := c.AllStream(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		for {
			data, _ := allStr.Recv()
			fmt.Println("收到客户端消息:" + data.Data)
		}
	}()

	//1. 集中学习protobuf, grpc

	go func() {
		defer wg.Done()
		for {
			_ = allStr.Send(&proto.StreamReqData{Data: "慕课网"})
			time.Sleep(time.Second)
		}
	}()

	wg.Wait()
}

proto文件

syntax = "proto3";

option go_package="../../common/stream/proto/v1";//这得修改,此值是在当前文件的相对路径下../../common/stream/proto/v1生成proto文件,包名为v1
service Greeter {
    rpc GetStream(StreamReqData) returns (stream StreamResData); //服务端流模式
    rpc PutStream(stream StreamReqData) returns (StreamResData); //客户端流模式
    rpc AllStream(stream StreamReqData) returns (stream StreamResData); //双向流模式
}

message StreamReqData {
    string data = 1;
}

message StreamResData {
    string data = 1;
}

2.3、gRPC的metadata

在这里插入图片描述
应用场景在身份的验证,metadata携带一些身份信息过去。


//------------server.go------------------
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc/metadata"
	"net"

	"google.golang.org/grpc"

	"OldPackageTest/grpc_test/proto"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
	error) {

	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		fmt.Println("get metadata error")
	}
	if nameSlice, ok := md["name"]; ok {
		fmt.Println(nameSlice)
		for i, e := range nameSlice {
			fmt.Println(i, e)
		}
	}
	return &proto.HelloReply{
		Message: "hello " + request.Name,
	}, nil
}

func main() {
	g := grpc.NewServer()
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		panic("failed to listen:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("failed to start grpc:" + err.Error())
	}
}
//------------client.go------------------
package main

import (
	"OldPackageTest/grpc_test/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

func main() {
	//stream
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)

	//md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
	md := metadata.New(map[string]string{
		"name":    "bobby",
		"pasword": "imooc",
	})
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	r, err := c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

proto文件

syntax = "proto3";
option go_package = ".;proto";
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}
//将 sessionid放入 放入cookie中 http协议
message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

//go语言中是生成一个文件, 也就只有python会生成两个文件

client端的另外一种写法,很方便的写出matadata携带的数据

package main

import (
	"OldPackageTest/grpc_test/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
)

type customCredential struct{}

func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appid":  "101010",
		"appkey": "i am key",
	}, nil
}

// RequireTransportSecurity indicates whether the credentials requires
// transport security.
func (c customCredential) RequireTransportSecurity() bool {
	return false
}

func main() {
	//stream
	//interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error{
	//	start := time.Now()
	//	//md := metadata.New(map[string]string{
	//	//	"appid":"10101",
	//	//	"appkey":"i am key",
	//	//})
	//	ctx = metadata.NewOutgoingContext(context.Background(), md)
	//	err := invoker(ctx, method, req, reply, cc, opts...)
	//	fmt.Printf("耗时:%s\n", time.Since(start))
	//	return err
	//}
	grpc.WithPerRPCCredentials(customCredential{})
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithPerRPCCredentials(customCredential{}))
	conn, err := grpc.Dial("127.0.0.1:50051", opts...)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)
	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

2.4、gRPC的拦截器

//------------server.go------------------
package main

import (
	"context"
	"fmt"
	"net"
	"time"

	"google.golang.org/grpc"

	"OldPackageTest/grpc_test/proto"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
	error) {
	time.Sleep(2 * time.Second)
	return &proto.HelloReply{
		Message: "hello " + request.Name,
	}, nil
}

func main() {
	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		fmt.Println("接收到了一个新的请求")
		res, err := handler(ctx, req)
		fmt.Println("请求已经完成")
		return res, err
	}

	opt := grpc.UnaryInterceptor(interceptor)
	g := grpc.NewServer(opt)
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		panic("failed to listen:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("failed to start grpc:" + err.Error())
	}
}
//------------client.go------------------
package main

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/grpc"

	"OldPackageTest/grpc_test/proto"
)

func main() {
	//stream
	interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		start := time.Now()
		err := invoker(ctx, method, req, reply, cc, opts...)
		fmt.Printf("耗时:%s\n", time.Since(start))
		return err
	}
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithUnaryInterceptor(interceptor))


	conn, err := grpc.Dial("127.0.0.1:50051", opts...)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)
	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

;