提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
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)
}