54.grpc实现文件上传和下载

news2024/11/19 5:50:19

文章目录

  • 一:简介
    • 1. 什么是grpc
    • 2. 为什么我们要用grpc
  • 二:grpc的hello world
    • 1、 定义hello.proto文件
    • 2、生成xxx_grpc.pb.go文件
    • 3、生成xxx.pb.go结构体文件
    • 4、编写服务代码service.go
    • 5、编写客户端代码client.go
  • 三、服务端流式传输:文件下载
    • 文件下载
  • 四、客户端流式传输:文件上传
    • 文件上传
  • 五、双向流:聊天

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/32-grpc

一:简介

1. 什么是grpc

gRpc 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持C, C++, Node.js, Python, Ruby, Objective-C, PHP C# 支持.
gRPC 基于 HTTP2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。
参考: grpc官方文档中文版

2. 为什么我们要用grpc

  • 生态好:背靠Google。还有比如nginx也对grpc提供了支持
  • 跨语言:跨语言,且自动生成sdk
  • 性能高:比如protobuf性能高过json, 比如http2.0性能高过http1.1
  • 强类型:编译器就给你解决了很大一部分问题
  • 流式处理(基于http2.0):支持客户端流式,服务端流式,双向流式

二:grpc的hello world

在这里插入图片描述

1、 定义hello.proto文件

syntax = "proto3"; // 指定proto版本,不指定时默认是proto2
package hello_grpc; // 指定默认包名

// 指定golang包名,当编译为pb.go时,这个包名会替换上面package指定的名字
option go_package = "/hello_grpc";

message HelloRequest {
  string  name = 1;
  string message = 2;
}

message HelloResponse {
  string  name = 1;
  string message = 2;
}

// 定义rpc服务
service HelloService {
    // 定义函数
    rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

2、生成xxx_grpc.pb.go文件

 protoc --proto_path=32-grpc/grpc_proto --go-grpc_out=32-grpc/grpc_proto 32-grpc/grpc_proto/hello.proto

解释:

  1. protoc为我们安装的protocol buffer 的命令
  2. --proto_path选项为指定proto文件所在的路径,可以换为-I
  3. --go-grpc_out指明要生成的是grpc文件,且指明了生成后文件要放在哪个目录
  4. 最后的参数为proto文件,可以写多个,也可以写.,表示当前目录下所有proto文件

执行后如下:报红的地方为找不到对应的结构体,所以我们还需要生成结构体的pb.go文件
在这里插入图片描述

3、生成xxx.pb.go结构体文件

 protoc --proto_path=32-grpc/grpc_proto --go_out=32-grpc/grpc_proto 32-grpc/grpc_proto/hello.proto

命令和生成gprc文件几乎一致,只是--go-grpc_out选项换成了--go_out选项而已

在这里插入图片描述

注意:

  1. 23两个生成pb.go的命令也可以写成一个命令,如下
 protoc --proto_path=32-grpc/grpc_proto --go_out=32-grpc/grpc_proto --go-grpc_out=32-grpc/grpc_proto 32-grpc/grpc_proto/hello.proto
  1. pb描述和rpc⽅法之前旧版⽣成是在⼀个⽂件中,⽬前新版本pb⽅法已经分离⽣成的⽂件格式特征如下:
    在这里插入图片描述

4、编写服务代码service.go

在这里插入图片描述

package main

import (
	"context"
	"fmt"
	"golang-trick/32-grpc/grpc_proto/hello_grpc"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
)

type HelloService struct {
	hello_grpc.UnimplementedHelloServiceServer
}

func (h HelloService) SayHello(ctx context.Context, request *hello_grpc.HelloRequest) (*hello_grpc.HelloResponse, error) {
	fmt.Println(request)
	return &hello_grpc.HelloResponse{
		Name:    "lym",
		Message: "ok",
	}, nil
}

func main() {
	// 监听端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		grpclog.Fatalf("Failed to listen err:%v", err)
	}

	// 创建一个grpc服务器实例
	s := grpc.NewServer()
	server := HelloService{}

	// 将server结构体注册为grpc服务
	hello_grpc.RegisterHelloServiceServer(s, &server)
	fmt.Println("grpc server running:9090")

	// 开始处理客户端请求
	err = s.Serve(listen)
}

解释:
HelloService 是我们自定义的结构体,需要实现hello_grpc中的HelloServiceServer 接口,但是该接口包含一个私有的mustEmbedUnimplementedHelloServiceServer方法,导致无法实现该接口,目前解决办法就是让我们的结构体包含hello_grpc.UnimplementedHelloServiceServer,从而实现hello_grpc中的HelloServiceServer 接口

// HelloServiceServer is the server API for HelloService service.
// All implementations must embed UnimplementedHelloServiceServer
// for forward compatibility
type HelloServiceServer interface {
	// 定义函数
	SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
	mustEmbedUnimplementedHelloServiceServer()
}

运行服务端

在这里插入图片描述

5、编写客户端代码client.go

package main

import (
	"context"
	"fmt"
	"golang-trick/32-grpc/grpc_proto/hello_grpc"
	"log"

	"google.golang.org/grpc"
)

func main() {
	addr := ":8080"
	//使用grpc.Dial 创建一个到指定的地址的 grpc 连接
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect adddr[%s] failed,err:%v", addr, err))
	}

	defer conn.Close()

	// 初始化客户端
	client := hello_grpc.NewHelloServiceClient(conn)

	// 调用远程服务端方法
	resp, err := client.SayHello(context.Background(), &hello_grpc.HelloRequest{
		Name:    "lym",
		Message: "我是客户端",
	})
	fmt.Println(resp, err)

}

运行客户端

成功收到服务端的响应
在这里插入图片描述
且服务端也打印出了客户端发来的请求
在这里插入图片描述

三、服务端流式传输:文件下载

grpc共有四种传输方式:

  • 普通交互:如上面hello world示例,客户端发送一次请求,服务端响应一次。
  • 服务端流式:客户端发送一次请求,服务端响应多次
  • 客户端流式:客户端发送多次请求,服务端响应一次
  • 双向流式:客户端和服务端有问有答一样

而上面四次方式proto文件写法上区别就在于stream关键字的有无以及所在位置

如普通式:

// 定义rpc服务
service Service {
    // 定义函数
    rpc SayHello(Request) returns (Response) {}
}

服务端流式:

// 定义rpc服务
service ServiceStream {
    // 定义函数
    rpc SayHello(Request) returns (stream Response) {}
}

文件下载

因为要下载的文件可能很大,服务端不能一次就把整个文件响应回去,因此需要用到服务端流式,多次发送,目录结构以及文件大致如下:
在这里插入图片描述

1、首先编写stream.proto文件,注意响应多了stream关键字

syntax = "proto3"; // 指定proto版本,不指定时默认是proto2
package stream; // 指定默认包名

// 指定golang包名,当编译为pb.go时,这个包名会替换上面package指定的名字
option go_package = "/stream";


message Request {
    string name = 1;
}

message FileResponse {
    string file_name = 1;
    bytes content = 2;
}

service ServiceStream {
    rpc DownLoadFile(Request) returns (stream FileResponse){};
}

2、生成pb.go文件,这个就不赘述了

 protoc --proto_path=32-grpc/grpc_proto --go_out=32-grpc/grpc_proto --go-grpc_out=32-grpc/grpc_proto 32-grpc/grpc_proto/stream.proto

3、服务端代码stream_service.go。看代码注释即可, 不难

package main

import (
	"fmt"
	"golang-trick/32-grpc/grpc_proto/stream"
	"io"
	"net"
	"os"

	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
)

type ServiceStream struct {
	stream.UnimplementedServiceStreamServer
}

func (s ServiceStream) DownLoadFile(request *stream.Request, resp stream.ServiceStream_DownLoadFileServer) error {
	fmt.Println(request)
	// 获取要下载的文件
	file, err := os.Open("32-grpc/static/prometheus+granfana企业级监控实战v5.pdf")
	if err != nil {
		return err
	}
	defer file.Close()

	for {
		buf := make([]byte, 1024)
		_, err := file.Read(buf)
		if err == io.EOF {
			break
		}

		if err != nil {
			break
		}

		// 可以通过接口上的resp对象的Send方法不断给客户端响应
		resp.Send(&stream.FileResponse{
			Content: buf,
		})
	}
	// return 后表明本次响应结束,不会再Send
	return nil

}

func main() {
	// 监听端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		grpclog.Fatalf("Failed to listen err:%v", err)
	}

	// 创建一个grpc服务器实例
	s := grpc.NewServer()
	server := ServiceStream{}

	// 将server结构体注册为grpc服务
	stream.RegisterServiceStreamServer(s, &server)
	fmt.Println("grpc server running:8080")

	// 开始处理客户端请求
	err = s.Serve(listen)
}

4、客户端代码client.go,请求下载文件

package main

import (
	"bufio"
	"context"
	"fmt"
	"golang-trick/32-grpc/grpc_proto/stream"
	"io"
	"log"
	"os"

	"google.golang.org/grpc"
)

func main() {
	addr := ":8080"
	//使用grpc.Dial 创建一个到指定的地址的 grpc 连接
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect adddr[%s] failed,err:%v", addr, err))
	}

	defer conn.Close()

	// 初始化客户端
	client := stream.NewServiceStreamClient(conn)

	resp, err := client.DownLoadFile(context.Background(), &stream.Request{Name: "下载文件"})
	if err != nil {
		log.Fatalln(err)
	}

	file, err := os.OpenFile("32-grpc/static/下载的pdf文件.pdf", os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	writer := bufio.NewWriter(file)

	for {
		// 客户端从服务端得到的响应是一个stream,所以可以通过Recv不断接收,直到遇到io.EOF表明服务端响应完毕了
		recv, err := resp.Recv()
		if err == io.EOF {
			break
		}
		fmt.Println(fmt.Sprintf("写入数据 %d 字节", len(recv.Content)))
		// 将每轮接收到的内容写入到文件中
		writer.Write(recv.Content)
	}

	writer.Flush()
}

测试:运行服务端和客户端后,如下,接收到了文件
在这里插入图片描述

四、客户端流式传输:文件上传

文件上传

实际上,客户端流式和服务端流式思路基本是完全一致的,就是在request前加stream关键字即可,然后文件上传和文件下载也是类似的,只是发送方变为了客户端,然后服务端不断的接收,知道收到io.EOF时,响应给客户端接收成功的消息。文件结构大致如下:

1、proto文件,注意包名修改,以及request前加了stream关键字

syntax = "proto3"; // 指定proto版本,不指定时默认是proto2
package client_stream; // 指定默认包名

// 指定golang包名,当编译为pb.go时,这个包名会替换上面package指定的名字
option go_package = "/client_stream";


message FileRequest {
    string file_name = 1;
    bytes content = 2; // 对应go的[]byte类型
}

message Response {
    string text = 1;
}

service ClientStream {
    rpc UploadFile(stream FileRequest) returns (Response){};
}

2、生成pb.go文件

 protoc --proto_path=32-grpc/grpc_proto --go_out=32-grpc/grpc_proto --go-grpc_out=32-grpc/grpc_proto 32-grpc/grpc_proto/client_stream.proto

3、服务端代码file_upload_service.go

注意方法上响应只有errorresponse没有写在方法上,而是通过req.SendAndClose返回的响应结果

package main

import (
	"bufio"
	"fmt"
	"golang-trick/32-grpc/grpc_proto/client_stream"
	"io"
	"log"
	"net"
	"os"

	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
)

type FileUploadService struct {
	client_stream.UnimplementedClientStreamServer
}

func (f FileUploadService) UploadFile(req client_stream.ClientStream_UploadFileServer) error {
	// 这里文件名我们写死了,实际应该用客户端传过来的
	file, err := os.OpenFile("32-grpc/static/上传的png文件.png", os.O_CREATE|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	writer := bufio.NewWriter(file)

	for {
		recv, err := req.Recv()
		if err == io.EOF {
			break
		}
		fmt.Println(fmt.Sprintf("写入数据 %d 字节", len(recv.Content)))
		writer.Write(recv.Content)
	}

	writer.Flush()

	// 注意方法上响应只有error,response是从这里返回的,而没有写在方法上
	req.SendAndClose(&client_stream.Response{Text: "服务端接收完成啦!"})
	return nil
}

func main() {
	// 监听端口
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		grpclog.Fatalf("Failed to listen err:%v", err)
	}

	// 创建一个grpc服务器实例
	s := grpc.NewServer()
	server := FileUploadService{}

	// 将server结构体注册为grpc服务
	client_stream.RegisterClientStreamServer(s, &server)
	fmt.Println("grpc server running:8080")

	// 开始处理客户端请求
	err = s.Serve(listen)
}

4、客户端代码file_upload_client.go

注意:全部上传完成后,才告知服务端发送结束了,并通过resp, err := stream.CloseAndRecv()接收服务端的响应

package main

import (
	"context"
	"fmt"
	"golang-trick/32-grpc/grpc_proto/client_stream"
	"io"
	"log"
	"os"

	"google.golang.org/grpc"
)

func main() {
	addr := ":8080"
	//使用grpc.Dial 创建一个到指定的地址的 grpc 连接
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf(fmt.Sprintf("grpc connect adddr[%s] failed,err:%v", addr, err))
	}

	defer conn.Close()

	// 初始化客户端
	client := client_stream.NewClientStreamClient(conn)

	stream, err := client.UploadFile(context.Background())
	if err != nil {
		log.Fatalln(err)
	}

	file, err := os.Open("32-grpc/static/21.png")
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	for {
		buf := make([]byte, 1024)
		_, err := file.Read(buf)
		if err == io.EOF {
			break
		}

		if err != nil {
			break
		}

		stream.Send(&client_stream.FileRequest{
			Content: buf,
		})
	}

	// 全部上传完成后,在这里告知服务端发送结束了,并接收服务端的响应
	resp, err := stream.CloseAndRecv()
	fmt.Println(resp, err)

}

启动服务端和客户端,可以看到上传成功
在这里插入图片描述

五、双向流:聊天

双向流能想到的最简单的场景就是聊天,一来一回的,就是在proto文件的接口上requestresponse前都加上stream,具体如何使用就要用的时候再查下吧,哈哈哈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1298381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

短视频无人实景直播源码技术开发=抖去推saas直播源码

开发无人直播源码技术需要具备一定的编程和网络知识。以下是一些基本的步骤和资源,帮助你进行无人直播源码的开发搭建: 1. 选择编程语言和开发环境:根据你的个人喜好和技术熟练程度,可以选择一些流行的编程语言,如Pyth…

根据应聘者的姓名和所学专业判断是否需要这样的程序设计人员

一、程序分析 导入Scanner函数,分别输入应聘者的姓名和应聘者所学的程序设计语言。 二、具体代码 import java.util.Scanner; public class Recruitment {public static void main(String[] args){try (Scanner scan new Scanner(System.in)) {System.out.prin…

【Vue】设置路由默认跳转指定页面

目录 设置路由默认跳转 上一篇: 登录注册界面制作 https://blog.csdn.net/m0_67930426/article/details/134895214?spm1001.2014.3001.5502 以这篇文章为例 首先我们要了解一下vue项目的router包的作用 上一篇文章里,创建了登录注册页面 如果我们…

SpringSecurity6 | 自定义登录页面

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏&#xf…

Qt开发 之 Qt5各版本情况分析

文章目录 1、简介2、Qt5 版本归纳3、下载地址3.1、典型版本3.1.1、Qt5.0.03.1.2、Qt5.9.93.1.3、Qt5.12.12 3.2、当前Qt5最新版本 1、简介 Qt6 出生刚刚好一年的时间,已经出到6.6版本,带来了许多的新特性和改进。今天刚刚好抽空总结下陪伴 我工作这么长…

【K8S in Action】副本机制与控制器:部署托管的Pod

通过控制器来运行托管的 pod,Pod失败的时候自动重新启动它们。 1. 保持pod健康 从外部检查应用程序的运行状况: HTTPGET探针对容器的 IP 地址TCP套接字探针尝试与容器指定端口建立TCP连接Exec探针在容器内执行任意命令,并检查命令的退出状态码。如果状…

智能优化算法应用:基于人工兔算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于人工兔算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于人工兔算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工兔算法4.实验参数设定5.算法结果6.参考文献7.…

初出茅庐的小李博客之TobudOS移植到EVB_AIoT开发板

本博客参考教程: https://atomgit.com/OpenAtomFoundation/TobudOS/blob/master/doc/TobudOS_EVB_AIoT_STM32_Guide.md 介绍一下EVB_AIoT开发板 这个开发板是由TobudOS开源社区联合意法半导体、南京厚德物联网设计的一款高性能IoT开发平台,主控芯片是S…

学校安全检查系统

校园面积大、安全盲区多对学校安全管理带来诸多挑战;传统依靠人工纸质巡检记录存在漏检、管理难、联动差等诸多问题和缺点,巡检过程中很容易遗漏安全隐患的存续,从而导致安全事故的发生。 通过凡尔码平台模块化搭建学校安全管理系统&#xf…

【Bootloader学习理解----跳转优化异常】

笔者接着来介绍一下Bootloader的跳转代码以及优化 1、跳转代码理解 跳转代码可能要涉及到芯片架构的知识,要跳转到对应的位置,还要设置相关的SP 堆栈指针,具体可以参考笔者这篇文章BootLoader的理解与实现。 STM32的跳转代码如下所示: u32 …

多张二维码能一次解码处理吗?3个步骤就能完成

二维码是现在生活中很常见的内容承载方式,但是有时候我们需要将二维码内容转换成文本或者链接来使用,那么如何处理能够将二维码分解处理呢?想要将多张二维码图片分解处理,那么为了提高效率可以用二维码解码器的批量解码功能来处理…

【词云图】从excel和从txt文件,绘制以句子、词为单位的词云图

从excel和从txt文件,绘制以句子、词为单位的词云图 写在最前面数据说明&结论 从txt文件,绘制以句子、词为单位的词云图自我介绍 从excel,绘制以句子、词为单位的词云图读取excel绘制以句子、词为单位的词云图文章标题 写在最前面 经常绘…

TSINGSEE青犀AI视频识别技术渣土车识别智能监管方案

随着城市化进程的不断推进,渣土车在建筑垃圾的运输中发挥着越来越重要的作用。未密闭化运输、车容不洁挂土、违规抛洒滴漏是目前渣土运输过程中最常见的违规行为。传统的渣土车运输管理方式存在着很多问题,导致渣土车在运输过程出现的不规范行为得到有效…

vue3实现2d楼宇模型

需求背景解决效果视频效果2dFloor.vue 需求背景 需要实线一个2d楼宇模型&#xff0c;并按照租户温度渲染颜色 解决效果 视频效果 2dFloor.vue <!--/*** author: liuk* date: 2023/12/06* describe: 2d楼宇模型* CSDN:https://blog.csdn.net/hr_beginner?typeblog*/--&g…

TA-Lib学习研究笔记(九)——Pattern Recognition (5)

TA-Lib学习研究笔记&#xff08;九&#xff09;——Pattern Recognition &#xff08;5&#xff09; 最全面的形态识别的函数的应用&#xff0c;通过使用A股实际的数据&#xff0c;验证形态识别函数&#xff0c;用K线显示出现标志的形态走势&#xff0c;由于入口参数基本上是o…

Element-UI定制化Tree 树形控件

1.复制 说明&#xff1a;复制Tree树形控件。 <script> export default {data() {return {data: [{label: 一级 1,children: [{label: 二级 1-1,children: [{label: 三级 1-1-1}]}]}, {label: 一级 2,children: [{label: 二级 2-1,children: [{label: 三级 2-1-1}]}, {l…

1-3、Java反编译

语雀原文链接 文章目录 1、JD-GUI反编译下载1-1、打开class文件无反应 1、JD-GUI反编译下载 http://java-decompiler.github.io jd-gui-windows-1.6.6.zip 1-1、打开class文件无反应 目前是可以正常打jar包文件&#xff0c;但是在直接打开.class文件时软件会卡住。首先将要…

谷歌发布大模型Gemini,赶超GPT4

迄今为止规模最大&#xff0c;能力最强的谷歌大模型来了。当地时间 12 月 6 日&#xff0c;谷歌 CEO 桑达尔・皮查伊官宣 Gemini 1.0 版正式上线。 这次发布的 Gemini 大模型是原生多模态大模型&#xff0c;是谷歌大模型新时代的第一步&#xff0c;它包括三种量级&#xff1a;…

打破常规思维:Scrapy处理豆瓣视频下载的方式

概述 Scrapy是一个强大的Python爬虫框架&#xff0c;它可以帮助我们快速地开发和部署各种类型的爬虫项目。Scrapy提供了许多方便的功能&#xff0c;例如请求调度、数据提取、数据存储、中间件、管道、信号等&#xff0c;让我们可以专注于业务逻辑&#xff0c;而不用担心底层的…

[报错]记录IDEA远程开发报错:java: Cannot run program.....

报错内容 IDEA在进行远程开发的时候报错&#xff0c;内容如下&#xff1a; java: Cannot run program "/usr/lib/jvm/java-1.8.0-openjdk-amd64/bin/java" (in directory "/home/jim/.cache/JetBrains/RemoteDev-IU/_home_jim_DevCodes_Github_zfile/compile-…