Home >> gRPC 简易实现

gRPC 简易实现

2024-03-05 16:39 AtmosphereMao

gRPC 简易实现流程

Prerequisites

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

Operation

Proto

./src/service/service.proto

syntax = "proto3";
package hello;
option go_package = "grpc-server/src/service";

message Request{
  string value = 1;
}
message Response{
  string value = 1;
}

// The HelloService service definition
// service 关键字
// HelloService 服务名称 对应接口的名称
// service服务会对应.pb.go文件里interface,里面的rpc对应接口中的函数
service HelloService{
  rpc Hello (Request) returns (Response){}
  rpc Channel(stream Request) returns (stream Response) {}
}

生成go文件

$ protoc -I . --go_out=./src/service --go_opt=module="grpc-server/src/service" --go-grpc_out=./src/service --go-grpc_opt=module="grpc-server/src/service" ./src/service/service.proto

Server

./src/server/main.go

package main

import (
    "context"
    "google.golang.org/grpc"
    "log"
    "net"
    "zyy-grpc-server/src/service"
)

type HelloService struct {
    // UnimplementedHelloServiceServer这个结构体是必须要内嵌进来的
    // 也就是说我们定义的这个结构体对象必须继承UnimplementedHelloServiceServer。
    // 嵌入之后,我们就已经实现了GRPC这个服务的接口,但是实现之后我们什么都没做,没有写自己的业务逻辑,
    // 我们要重写实现的这个接口里的函数,这样才能提供一个真正的rpc的能力。
    service.UnimplementedHelloServiceServer
}

var _ service.HelloServiceServer = (*HelloService)(nil)

// Hello 重写实现的接口里的Hello函数
func (p *HelloService) Hello(ctx context.Context, req *service.Request) (*service.Response, error) {
    resp := &service.Response{}
    resp.Value = "hello:" + req.Value
    log.Printf("Received: %v", resp.Value)
    return resp, nil
}

func main() {
    // 首先通过grpc.NewServer() 构造一个grpc服务对象
    grpcServer := grpc.NewServer()
    // 然后通过grpc插件生成的RegisterHelloServiceServer函数注册我们实现的HelloService服务。
    service.RegisterHelloServiceServer(grpcServer, new(HelloService))

    listen, err := net.Listen("tcp", ":8888")
    if err != nil {
        log.Fatal("Listen TCP err:", err)
    }
    //最后通过grpcServer.Serve(listen) 在一个监听端口上提供gRPC服务
    grpcServer.Serve(listen)
}

Client

./src/client/main.go

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "log"
    "zyy-grpc-server/src/service"
)

func main() {
    // grpc.Dial负责和gRPC服务建立连接
    //conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
    // 这里会提示,WithInsecure已被弃用,
    // 如果你不想继续使用WithInsecure,可以使用
    // 函数insecure.NewCredentials()返回credentials.TransportCredentials的一个实现。
    // 您可以将其作为DialOption与grpc.WithTransportCredentials一起使用:
    // 但是,API标记为实验性的,因此即使他们已经添加了弃用警告,您也不必立即切换。
    conn, err := grpc.Dial("localhost:8888", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatal("Dial err: ", err)
    }
    defer conn.Close()

    // NewHelloServiceClient函数是xxx_grpc.pb.go中自动生成的函数,
    // 基于已经建立的连接构造HelloServiceClient对象,
    // 返回的client其实是一个HelloServiceClient接口对象
    //
    client := service.NewHelloServiceClient(conn)

    // 通过接口定义的方法就可以调用服务端对应gRPC服务提供的方法
    req := &service.Request{Value: "test"}
    reply, err := client.Hello(context.Background(), req)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply.GetValue())
}

Stream gRPC

server

./src/server/main.go

package main

import (
    "google.golang.org/grpc"
    "io"
    "log"
    "net"
    "zyy-grpc-server/src/service"
)

type HelloService struct {
    service.UnimplementedHelloServiceServer
}

var _ service.HelloServiceServer = (*HelloService)(nil)

func (p *HelloService) Channel(stream service.HelloService_ChannelServer) error {
    // 服务端在循环中接收客户端发来的数据
    for {
        args, err := stream.Recv()
        if err != nil {
            // 如果遇到io.EOF表示客户端流关闭
            if err == io.EOF {
                return nil
            }
            return err
        }

        // 响应一个请求
        // 生成返回的数据通过流发送给客户端
        resp := &service.Response{
            Value: "hello," + args.GetValue(),
        }
        log.Printf("Received: %v", resp.Value)
        err = stream.Send(resp)
        if err != nil {
            // 服务端发送异常,函数退出,服务端流关闭
            return err
        }
    }
}

func main() {
    // 1. 构造一个gRPC服务对象
    grpcServer := grpc.NewServer()
    // 2. 通过gRPC插件生成的RegisterHelloServiceServer 函数注册我们实现的HelloService服务。
    service.RegisterHelloServiceServer(grpcServer, new(HelloService))
    // 3. 监听:8888端口
    listen, err := net.Listen("tcp", ":8888")
    if err != nil {
        log.Fatal("Listen TCP err:", err)
    }
    // 4. 通过grpcServer.Serve(listen) 在一个监听端口上提供gRPC服务
    grpcServer.Serve(listen)
}

Client

./src/client/main.go

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "io"
    "log"
    "time"
    "zyy-grpc-server/src/service"
)

func main() {
    conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    client := service.NewHelloServiceClient(conn)

    // 客户端先调用Channel方法,获取返回的流对象

    stream, err01 := client.Channel(context.Background())
    if err01 != nil {
        log.Fatal(err01)
    }

    // 在客户端我们将发送和接收放到两个独立的Goroutine

    // 首先向服务端发送数据:
    go func() {
        for {
            req := &service.Request{
                Value: "test",
            }
            if err := stream.Send(req); err != nil {
                log.Fatal(err)
            }
            time.Sleep(time.Second)
        }
    }()

    // 然后再循环中接收服务端返回的数据
    for {
        reply, err := stream.Recv()
        if err != nil {
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }
        fmt.Println(reply.GetValue())
    }
}

评论


暂无评论


* 登录后即可评论

©2022 联系我们

粤ICP备2022023863号
500x500