愿你出走半生,归来仍是少年
不知不觉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内容的讲解上,这部分的内容希望可以重点掌握。