go语言的魔幻旅程32-gRPC

241 阅读5分钟

愿你出走半生,归来仍是少年

不知不觉go系列的教程已经写到32篇,争取今年能写到80篇的目标吧,时间如流水,逝者如斯夫,没有目标的生活,确实每一分每一秒过得那么的心惊胆颤,或许等到这个月的月末,又有一批少年即将放出樊笼,进入社会这所大学进行历练,前路未知,唯有祝他们出走半生,归来仍是少年。

什么是RPC、gRPC

rpc(Remote Procedure Call) 字面表达意思是远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,翻译成大白话就是一个节点请求另一个节点的服务,因为不需要了解调用过程中的网络细节,因此对于服务的调用方而言同调用本地方法没有明显的区别;gRPC是一个由Google公司开发并开源的一款高性能、开源并且适用于多门编程语言的RPC框架,同时支持HTTP2、提供强大的流式调用能力


原生RPC实现

go语言原生就提供了实现rpc的包,实现起来也比较简单,服务端只需要提供对外的远程过程方法和结构体,然后将其注册到rpc服务中就可以了,然后客户端通过指定的服务名称和方法名称就可以进行rpc方法的调用,不过针对具体的实现方式服务端和客户端都有所规则要求。

服务端要求: 方法可导出 方法有两个参数,都是可导出类型或类型 方法第二个参数是指针 方法只有一个error接口类型的返回值 方法格式:func (t *T) MethodName (argType T1, replyType *T2) error  //T、T1、T2都能被encoding/gob序列化

客户端要求: 方法调用格式:func (client *Client) Call(serviceMethod string, args interface{}, reply interface{})

原生实现案例

//服务端代码

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

//创建int类型的服务端对象
type Panda int

func(this *Panda) GetInfo(argType int, replyType *int) error {
	fmt.Println("打印对方发过来的数据", argType)

	//执行代码
	*replyType = argType + 123456
	return nil
}

func pandaText(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "hello panda")
}

func main () {
	//客户端页面的请求
	http.HandleFunc("/panda", pandaText)

	//将类实例化为对象
	pd := new(Panda)

	//服务端注册一个对象,并将该对象作为一个服务暴露出去
	rpc.Register(pd)

	//连接到网络
	rpc.HandleHTTP()

	//监听端口
	In, err := net.Listen("tcp", ":10086")
	if err != nil {
		fmt.Println("network error")
	}
	http.Serve(In, nil)
}

//客户端代码
import (
	"fmt"
	"net/rpc"
)

func main() {

	//建立网络的链接
	cli, err := rpc.DialHTTP("tcp", "127.0.0.1:10086")
	if err != nil {
		fmt.Println("network error")
	}

	var pd int

	//客户端调用服务端GetInfo方法,并传递参数
	err = cli.Call("Panda.GetInfo", 10086, &pd)
	if err != nil {
		fmt.Println("call failed")
	}
	fmt.Println("服务端输出的值:", pd)
}

gRPC实现

1、gRPC在rpc原有功能的基础上进行了拓展, 通信的方式更加丰富了,目前有四种通信的方式:

简单rpc(Simple RPC):就是一般的rpc调用,一个请求对象对应一个返回对象

服务端流式rpc(Server-side streaming RPC):一个请求对象,服务端可以返回多个结果对象

客户端流式rpc(Client-sude streaming RPC):客户端传入多个请求对象,服务端返回一个响应结果

双向流式rpc(Bidirectional streaming RPC):结合客户端流式rpc和服务端流式rpc,可以传入|响应多个对象

2、protoc安装

实现gRPC相关的功能需要借助protoc这个工具,protoc工具可以帮助我们讲proto 文件编程成各种各样的源码文件,验证是否安转(protoc --version)

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1

======================================================

//或者这样也是可以的
go get github.com/micro/protobuf/{proto,protoc-gen-go}

3、proto文件

proto文件存放了我们需要实现的服务及请求和响应的数据格式,test.proto 文件内容:

syntax = "proto3"; 

option go_package = ".;proto";

package proto; // 包名

//定义服务
service HelloServer {
  rpc SayHello (HelloReq) returns (HelloRsp){}
  rpc SayName (NameReq) returns (NameRsp){}
}

//客户端发送给服务端
message HelloReq {
  string name = 1 ;
}

//服务端返回给客户端
message HelloRsp {
  string msg = 1 ;
}

//客户端发送给服务端
message NameReq {
  string name = 1 ;
}

//服务端返回给客户端
message NameRsp {
  string msg = 1 ;
}

proto文件编写完成后,我们需要借助protoc工具生成xxx.pb.go文件,使用的命令是切换到proto文件所在的目录下,运行:

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

其中 . 表示proto源文件的位置,test.proto为编写的proto文件的名称.

以下是服务端和客户端实现的源码

//服务端代码

package main

import (
	pd "code/grpc/proto"   //导入的是proto文件的包路径
	"context"
	"fmt"
	"google.golang.org/grpc"
	"net"
)

type server struct{}

func (this *server) SayHello(ctx context.Context, in *pd.HelloReq) (out *pd.HelloRsp,err error) {
	return &pd.HelloRsp{Msg:"hello"}, nil
}

func (this *server) SayName(ctx context.Context, in *pd.NameReq) (out *pd.NameRsp, err error) {
	return &pd.NameRsp{Msg: in.Name + "it is name"}, nil
}

func main() {
	In, err := net.Listen("tcp", ":10088")
	if err != nil {
		fmt.Println("net error:", err)
	}

	//创建grpc服务
	srv := grpc.NewServer()

	//注册服务
	pd.RegisterHelloServerServer(srv, &server{})
	err = srv.Serve(In)
	if err != nil {
		fmt.Println("Server error", err)
	}
}


//客户端代码
package main

import (
	pd "code/grpc/proto"     //proto文件的存放的包路径
	"context"
	"fmt"
	"google.golang.org/grpc"
)

func main() {

	//客户端连接服务端
	conn, err := grpc.Dial("127.0.0.1:10088", grpc.WithInsecure())
	if err != nil {
		fmt.Println("network error", err)
	}

	defer conn.Close()

	//获取grpc句柄
	c := pd.NewHelloServerClient(conn)

	//通过句柄调用服务端SayHello函数
	re1, err := c.SayHello(context.Background(), &pd.HelloReq{Name: "zhang"})
	if err != nil {
		fmt.Println("calling SayHello error", err)
	}
	fmt.Println(re1.Msg)

	//通过句柄调用服务端SayName函数
	re2, err := c.SayName(context.Background(), &pd.NameReq{Name: "kuiwu"})
	if err != nil {
		fmt.Println("calling SayName() error", err)
	}
	fmt.Println(re2.Msg)
}

小结

这篇文章中主要讲述了rpc相关的内容,重点放在gRPC内容的讲解上,这部分的内容希望可以重点掌握。