Golang开发gRPC服务入门介绍

Golang开发gRPC服务入门介绍

什么是gRPC?

gRPC是一种高性能、开源和通用的RPC框架,由Google推出,基于ProtoBuf序列化协议来实现,具有简单易用、跨语言、高效快速等特点。

gRPC工作原理是什么?

gRPC基于HTTP/2协议,利用protobuf进行序列化,传输效率极高,具体实现原理请参考官方文档

gRPC的优点

  • 性能高:采用protobuf序列化协议和HTTP/2协议,传输效率极高。
  • 跨语言:支持多种语言,如Go、Java、C++等。
  • 易用性好:gRPC支持自动生成客户端和服务端的代码,用户开发时无需关心网络编程。

如何使用gRPC?

gRPC的使用分为三个步骤:

  1. 定义一个.proto文件来描述你要实现的服务。该文件定义了服务的接口、消息类型等信息。
  2. 根据.proto文件生成客户端和服务端的代码。gRPC支持多种代码生成语言,如Go、Java、C++等。
  3. 在服务端实现.proto文件中定义的接口,客户端调用生成的代码,即可实现远程调用。

示例一:使用gRPC实现简易计算器

我们将使用gRPC实现一个简易计算器服务,支持加、减、乘、除四种运算。

定义.proto文件

syntax = "proto3";

package calculator;

service Calculator {
  rpc Add(Params) returns (Result) {}
  rpc Subtract(Params) returns (Result) {}
  rpc Multiply(Params) returns (Result) {}
  rpc Divide(Params) returns (Result) {}
}

message Params {
  int32 num1 = 1;
  int32 num2 = 2;
}

message Result {
  int32 res = 1;
}

根据.proto文件生成代码

使用protoc工具生成对应的Go代码。

protoc -I calculator/ calculator/calculator.proto --go_out=plugins=grpc:calculator

实现服务端

服务端代码如下:

package main

import (
    "context"
    "log"
    "net"

    pb "calculator"

    "google.golang.org/grpc"
)

type server struct{}

func (s *server) Add(ctx context.Context, in *pb.Params) (*pb.Result, error) {
    log.Printf("Add Received: %v\n", in)
    return &pb.Result{Res: in.Num1 + in.Num2}, nil
}

func (s *server) Subtract(ctx context.Context, in *pb.Params) (*pb.Result, error) {
    log.Printf("Subtract Received: %v\n", in)
    return &pb.Result{Res: in.Num1 - in.Num2}, nil
}

func (s *server) Multiply(ctx context.Context, in *pb.Params) (*pb.Result, error) {
    log.Printf("Multiply Received: %v\n", in)
    return &pb.Result{Res: in.Num1 * in.Num2}, nil
}

func (s *server) Divide(ctx context.Context, in *pb.Params) (*pb.Result, error) {
    log.Printf("Divide Received: %v\n", in)
    return &pb.Result{Res: in.Num1 / in.Num2}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    srv := grpc.NewServer()
    pb.RegisterCalculatorServer(srv, &server{})
    log.Printf("Listen to :8080...")
    if err := srv.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

实现客户端

客户端代码如下:

package main

import (
    "context"
    "log"
    "os"
    "strconv"

    pb "calculator"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial(":9000", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("failed to connect: %v", err)
    }
    defer conn.Close()

    client := pb.NewCalculatorClient(conn)

    cmd := os.Args[1]
    num1, err := strconv.Atoi(os.Args[2])
    num2, err := strconv.Atoi(os.Args[3])
    if err != nil {
        log.Fatalf("failed to parse args: %v", err)
    }

    var res *pb.Result
    switch cmd {
    case "add":
        res, err = client.Add(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
    case "subtract":
        res, err = client.Subtract(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
    case "multiply":
        res, err = client.Multiply(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
    case "divide":
        res, err = client.Divide(context.Background(), &pb.Params{Num1: int32(num1), Num2: int32(num2)})
    default:
        log.Fatalf("invalid command: %v", cmd)
    }

    if err != nil {
        log.Fatalf("failed to call: %v", err)
    }

    log.Printf("Result: %v", res.Res)
}

运行

先启动服务端:

go run server.go

然后在另一个终端启动客户端:

go run client.go add 1 2

注意事项

  • 代码中的grpc.WithInsecure()表示使用非安全的连接方式,实际应用中应使用安全的方式。
  • 服务端代码启动时监听的端口号应与客户端代码中连接的端口号一致。
  • 在调用远程函数时需要传入context.Context参数。

示例二:使用gRPC实现文件传输

这里介绍如何使用gRPC实现文件传输。

定义.proto文件

syntax = "proto3";

package filetransfer;

service FileTransfer {
  rpc Upload(stream Chunk) returns (Response) {}
  rpc Download(Request) returns (stream Chunk) {}
}

message Request {
  string filename = 1;
}

message Chunk {
  bytes content = 1;
}

message Response {
  bool ok = 1;
}

根据.proto文件生成代码

使用protoc工具生成对应的Go代码。

protoc -I filetransfer/ filetransfer/filetransfer.proto --go_out=plugins=grpc:filetransfer

实现服务端

服务端代码如下:

package main

import (
    "context"
    "io"
    "log"
    "net"

    pb "filetransfer"

    "google.golang.org/grpc"
)

type server struct{}

func (s *server) Upload(stream pb.FileTransfer_UploadServer) error {
    log.Printf("Upload Request\n")
    dst, err := CreateFile("/tmp/"+stream.Context().Value("filename").(string), false)
    if err != nil {
        return err
    }
    for {
        chunk, err := stream.Recv()
        if err == io.EOF {
            log.Printf("Upload Done\n")
            return stream.SendAndClose(&pb.Response{Ok: true})
        }
        data := chunk.Content
        dst.Write(data)
    }

    return nil
}

func (s *server) Download(req *pb.Request, stream pb.FileTransfer_DownloadServer) error {

    log.Printf("Download Request, filename: %s\n", req.Filename)
    file, err := OpenFile("/tmp/"+req.Filename, true)
    if err != nil {
        return err
    }

    defer file.Close()

    for {
        chunk := make([]byte, 1024)
        n, err := file.Read(chunk)
        if err == io.EOF {
            log.Printf("Download Done\n")
            break
        } else if err != nil {
            return err
        }
        stream.Send(&pb.Chunk{Content: chunk[:n]})
    }

    return nil
}

func OpenFile(filepath string, readonly bool) (*os.File, error) {
    if readonly {
        return os.Open(filepath)
    } else {
        return os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
    }
}

func CreateFile(filepath string, readonly bool) (*os.File, error) {
    if readonly {
        return os.Create(filepath)
    } else {
        return os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    }
}

func main() {
    lis, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    srv := grpc.NewServer()

    pb.RegisterFileTransferServer(srv, &server{})

    log.Printf("Listen to :9000...")
    if err := srv.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

实现客户端

客户端代码如下:

package main

import (
    "context"
    "io"
    "log"
    "os"
    "time"

    pb "filetransfer"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial(":9000", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("failed to connect: %v", err)
    }
    defer conn.Close()

    client := pb.NewFileTransferClient(conn)

    filename := os.Args[2]
    if os.Args[1] == "upload" {
        stream, err := client.Upload(
            context.Background(),
            grpc.PerRPCCredentials(credentials.NewStaticCredentials("","***")),//添加Token
            grpc.Header(&metadata.MD{"filename": []string{filename}}),
            )
        if err != nil {
            log.Fatalf("failed to upload: %v", err)
        }

        file, err := os.Open(filename)
        if err != nil {
            log.Fatalf("failed to open file: %v", err)
        }
        defer file.Close()

        for {
            chunk := make([]byte, 1024)
            n, err := file.Read(chunk)
            if err == io.EOF {
                break
            } else if err != nil {
                log.Fatalf("failed to read file: %v", err)
            }
            if err := stream.Send(&pb.Chunk{Content: chunk[:n]}); err != nil {
                log.Fatalf("failed to send chunk: %v", err)
            }
        }

        resp, err := stream.CloseAndRecv()
        if err != nil {
            log.Fatalf("failed to receive response: %v", err)
        }

        log.Printf("Upload Response: %v", resp.Ok)
    } else if os.Args[1] == "download" {
        stream, err := client.Download(context.Background(), &pb.Request{Filename: filename})
        if err != nil {
            log.Fatalf("failed to download: %v", err)
        }

        file, err := os.Create(filename)
        if err != nil {
            log.Fatalf("failed to create file: %v", err)
        }
        defer file.Close()

        for {
            chunk, err := stream.Recv()
            if err == io.EOF {
                break
            } else if err != nil {
                log.Fatalf("failed to receive chunk: %v", err)
            }
            _, err = file.Write(chunk.Content)
            if err != nil {
                log.Fatalf("failed to write file: %v", err)
            }
        }

        log.Printf("Download Done")
    } else {
        log.Fatalf("invalid command: %v", os.Args[1])
    }
}

运行

先启动服务端:

go run server.go

然后在另一个终端启动客户端上传文件:

go run client.go upload /path/to/file

或者下载文件:

go run client.go download filename

注意事项

  • 异步流式传输可以大幅提高文件传输效率。
  • 为了支持异步传输,在服务端和客户端的方法参数中,需要使用流式传输。
阅读剩余 89%

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Golang开发gRPC服务入门介绍 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • 思科CCNA认证学习笔记(五)IPV4地址、IP地址分类及特殊IP地址

    思科CCNA认证学习笔记(五)IPV4地址、IP地址分类及特殊IP地址 1. IPV4地址 IPV4地址是互联网协议版本4(Internet Protocol Version 4)使用的地址格式。它由32位二进制数表示,通常以点分十进制的形式呈现。IPV4地址的格式如下: XXX.XXX.XXX.XXX 其中,每个XXX代表一个8位二进制数,可以取值范围为0…

    other 2023年7月30日
    00
  • java订单号生成的几种方式

    Java订单号生成的几种方式 在Java应用程序中,生成订单号是一个非常常见的需求。而如何生成一个合理、唯一的、规范的订单号,也是一个需要我们深入研究的问题。本文将介绍几种常见的Java订单号生成方式,包括UUID、时间戳、自增序列、分布式ID等。 UUID方式 UUID即通用唯一识别码,它是一种由网络软件生成的标准化的128位唯一标识符,通常用于标识软件构…

    其他 2023年3月28日
    00
  • Win2003 server 最大支持多少内存

    Win2003 Server 最大支持多少内存攻略 Windows Server 2003是一款老版本的服务器操作系统,其对内存的支持有一定限制。下面是详细的攻略,包括了两个示例说明。 1. 确定操作系统版本 首先,需要确定你所使用的Windows Server 2003的具体版本。Windows Server 2003有多个版本,包括Standard、En…

    other 2023年8月2日
    00
  • 深入解析docker文件分层原理

    深入解析Docker文件分层原理 Docker是一种虚拟化容器技术,通过容器技术,可以将应用程序及其依赖项打包成一个轻量级、可移植的容器,并通过Docker Engine安装到任何支持Docker Engine的操作系统上。Docker文件分层原理是Docker的核心原理之一,本篇将从以下方面深入解析Docker文件分层原理。 Docker文件分层原理是什么…

    other 2023年6月27日
    00
  • Python基础之变量基本用法与进阶详解

    Python基础之变量基本用法与进阶详解 变量基本用法 在Python中,变量是用来存储数据的容器。使用变量可以方便地引用和操作数据。下面是变量的基本用法: 变量的定义和赋值 在Python中,可以使用等号(=)来定义和赋值变量。变量名可以是任意合法的标识符,但不能以数字开头。 # 定义一个整数变量 num = 10 # 定义一个字符串变量 name = \…

    other 2023年8月9日
    00
  • JAVA编程实现随机生成指定长度的密码功能【大小写和数字组合】

    当然!下面是关于\”JAVA编程实现随机生成指定长度的密码功能【大小写和数字组合】\”的完整攻略: JAVA编程实现随机生成指定长度的密码功能【大小写和数字组合】 在JAVA中,可以使用随机数生成器和字符集来实现随机生成指定长度的密码。以下是两个示例: 示例1:生成指定长度的密码 import java.util.Random; public class P…

    other 2023年8月19日
    00
  • css样式底部平均分布

    CSS样式底部平均分布 在网站开发过程中,我们经常需要将一排元素展示在页面底部,比如页脚链接、社交媒体图标等等。而如果我们希望这些元素在底部平均分布,应该怎么做呢? 下面,我们来介绍一种简单易用的CSS样式,可以轻松地实现底部元素平均分布的效果。 使用Flex布局 CSS3引入的弹性盒子布局(Flexbox)为我们提供了更加便捷的布局方式。下面的代码片段展示…

    其他 2023年3月28日
    00
  • win系统中XP必联电子阿里智能路由器动态IP上网的详细设置教程

    Win系统中XP必联电子阿里智能路由器动态IP上网的详细设置教程 本教程将详细介绍如何在Windows XP操作系统中使用XP必联电子阿里智能路由器进行动态IP上网设置。以下是完整的攻略: 步骤一:连接路由器 将XP必联电子阿里智能路由器连接到电源,并确保其正常启动。 使用网线将路由器的LAN口与计算机的网卡连接。 步骤二:访问路由器设置页面 打开任意浏览器…

    other 2023年7月31日
    00
合作推广
合作推广
分享本页
返回顶部