河北手机网站制作企业,什么网站可以帮人做ppt赚钱,太原做网站的网络公司,瑞安商城网站建设文章目录 一#xff1a;简介1. 什么是grpc2. 为什么我们要用grpc 二#xff1a;grpc的hello world1、 定义hello.proto文件2、生成xxx_grpc.pb.go文件3、生成xxx.pb.go结构体文件4、编写服务代码service.go5、编写客户端代码client.go 三、服务端流式传输#xff1a;文件下载… 文章目录 一简介1. 什么是grpc2. 为什么我们要用grpc 二grpc的hello world1、 定义hello.proto文件2、生成xxx_grpc.pb.go文件3、生成xxx.pb.go结构体文件4、编写服务代码service.go5、编写客户端代码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_path32-grpc/grpc_proto --go-grpc_out32-grpc/grpc_proto 32-grpc/grpc_proto/hello.proto解释
protoc为我们安装的protocol buffer 的命令--proto_path选项为指定proto文件所在的路径可以换为-I--go-grpc_out指明要生成的是grpc文件且指明了生成后文件要放在哪个目录最后的参数为proto文件可以写多个也可以写.表示当前目录下所有proto文件
执行后如下报红的地方为找不到对应的结构体所以我们还需要生成结构体的pb.go文件
3、生成xxx.pb.go结构体文件 protoc --proto_path32-grpc/grpc_proto --go_out32-grpc/grpc_proto 32-grpc/grpc_proto/hello.proto命令和生成gprc文件几乎一致只是--go-grpc_out选项换成了--go_out选项而已 注意
2和3两个生成pb.go的命令也可以写成一个命令如下 protoc --proto_path32-grpc/grpc_proto --go_out32-grpc/grpc_proto --go-grpc_out32-grpc/grpc_proto 32-grpc/grpc_proto/hello.protopb描述和rpc⽅法之前旧版⽣成是在⼀个⽂件中⽬前新版本pb和⽅法已经分离⽣成的⽂件格式特征如下
4、编写服务代码service.go package mainimport (contextfmtgolang-trick/32-grpc/grpc_proto/hello_grpcnetgoogle.golang.org/grpcgoogle.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 mainimport (contextfmtgolang-trick/32-grpc/grpc_proto/hello_grpcloggoogle.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_path32-grpc/grpc_proto --go_out32-grpc/grpc_proto --go-grpc_out32-grpc/grpc_proto 32-grpc/grpc_proto/stream.proto3、服务端代码stream_service.go。看代码注释即可 不难
package mainimport (fmtgolang-trick/32-grpc/grpc_proto/streamionetosgoogle.golang.org/grpcgoogle.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/prometheusgranfana企业级监控实战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 后表明本次响应结束不会再Sendreturn 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 mainimport (bufiocontextfmtgolang-trick/32-grpc/grpc_proto/streamiologosgoogle.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_path32-grpc/grpc_proto --go_out32-grpc/grpc_proto --go-grpc_out32-grpc/grpc_proto 32-grpc/grpc_proto/client_stream.proto3、服务端代码file_upload_service.go
注意方法上响应只有errorresponse没有写在方法上而是通过req.SendAndClose返回的响应结果
package mainimport (bufiofmtgolang-trick/32-grpc/grpc_proto/client_streamiolognetosgoogle.golang.org/grpcgoogle.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()// 注意方法上响应只有errorresponse是从这里返回的而没有写在方法上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 mainimport (contextfmtgolang-trick/32-grpc/grpc_proto/client_streamiologosgoogle.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文件的接口上request和response前都加上stream具体如何使用就要用的时候再查下吧哈哈哈