使用gRPC的stream向客户端实时传送信息的坑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用gRPC的stream向客户端实时传送信息的坑相关的知识,希望对你有一定的参考价值。

参考技术A 最近项目使用gRPC来实现PC端和手机的双向通讯, 我要把PC端的状态持续的发送给手机端 但是遇到一个问题
在client端不能及时收到server端发送的stream的信息, 往往要client主动write后, client端才能及时收到相关的信息。
后来查了一下, 找到了类似的问题
https://stackoverflow.com/questions/58299740/how-can-i-receive-data-on-client-side-before-calling-end-on-the-server-side-f
但是这个地方并没有给出解决方案,
去查文档
https://nodejs.org/api/stream.html#stream_class_stream_writable

终于找到了解决方案:

当然, 我后来发现, 如果我不调用cork, 只要在nextTick中调用就可以实现flush的功能

以上来纪念我4月15日的半天光影。

gRPC和gRPC-Gateway的使用以及遇到的坑

原创不易,未经允许,请勿转载。

系统:windows10

go版本:1.13.15

一、gRPC的使用

1.1 gPRC和Protobuf的安装

go get github.com/golang/protobuf/proto
go get google.golang.org/grpc
go get github.com/golang/protobuf/protoc-gen-go

装好上面三个之后,还需要安装一个protoc,可以到https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0下载编译好的可执行文件,然后把压缩包中bin目录下的protoc.exe放到GOPATH/bin目录下。

压缩包里面的include目录也放到GOPATH/bin目录下,之后用得到

现在,可以在GOPATH/bin目录下,看到protoc-gen-go.exeprotoc.exe这两个可执行文件

1.2 编写proto文件

在编写文件之间,先创建一个go项目,命名为grpc_and_grpc-gateway_demo,以grpc_and_grpc-gateway_demo为根目录,创建一个文件名为proto/helloworld.proto

protobuf语法可以参考https://www.jianshu.com/p/4443c28d4bf7

syntax = "proto3";
package helloworld; // 指定生成后go的package名
option go_package = "helloworld/"; // 生成的目录名,末尾的/不能省略,否则会出错。
message request 
  string name = 1;

message response 
  string res = 1;


service HelloWorld 
  rpc Call(request) returns(response)

编写好proto文件后,打开命令行窗口,进入proto目录下

cd proto

使用protoc编译helloworld.proto文件,生成对应的go文件

protoc --go_out=plugins=grpc:. ./helloworld.proto
  • --go_out表示生成go文件。
  • grpc:.冒号后面表示生成文件保存的位置,这个路径要已存在。.表示当前目录。
  • helloworld.proto表示要编译的文件名

执行好上面的命令后,如果没出错的话,在proto目录下会生成一个helloworld/helloworld.pb.go文件

其中helloworld为文件夹名,就算go_package指定的目录名。

helloworld.pb.go文件包含按照helloworld.proto文件描述的,服务端接口HelloWorldServer,客户端接口以及实现HelloWorldClient,以及RequestResponse结构体

1.3 编写服务端接口程序

grpc_and_grpc-gateway_demo为根目录,创建server/main.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	pb "grpc_and_grpc-gateway_demo/proto/helloworld"
	"net"
)

type helloWorldServer struct

func (this helloWorldServer) Call(ctx context.Context, request *pb.Request) (*pb.Response, error) 
	resp := new(pb.Response)
	resp.Res = "Hello " + request.Name
	return resp, nil


func main() 
	listen, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil 
		fmt.Println(err)
		return
	
	defer listen.Close()
	
	s := grpc.NewServer()
	pb.RegisterHelloWorldServer(s,helloWorldServer)
	
	fmt.Println("Listen on 127.0.0.1:8080")
	
	s.Serve(listen)


运行该程序

go run main.go

1.4 编写客户端程序

grpc_and_grpc-gateway_demo为根目录,创建client/main.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	pb "grpc_and_grpc-gateway_demo/proto/helloworld"
)

func main() 
	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
	if err != nil 
		fmt.Println(err)
	
	defer conn.Close()

	c := pb.NewHelloWorldClient(conn)
	req := &pb.RequestName: "jiang"
	res, err := c.Call(context.Background(), req)

	if err != nil 
		fmt.Println(err)
	
	fmt.Println(res.Res)


运行该程序

go run main.go

最后打印

Hello jiang

二、gRPC-Gateway的使用

通过protobuf的自定义option实现了一个网关,服务端同时开启gRPC和HTTP服务,HTTP服务接收客户端请求后转换为grpc请求数据,获取响应后转为json数据返回给客户端。

2.1 安装grpc-gateway

安装推荐去grpc-gateway的GitHub仓库,看最新的文档进行,安装,

 go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway

一些博客教程里面写的安装命令是下面这种

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

但是用这个命令安装的话,在后面执行命令的时候,会出现一些错误,可能会打印出一长串东西,结尾大概如下

--grpc-gateway_out: 11:1: expected 'IDENT', found 'import'

2.2 编写proto文件

grpc_and_grpc-gateway_demo为根目录,创建一个文件名为proto/helloworld_http.proto

syntax = "proto3";
package helloworld_http; // 指定生成后go的package名
option go_package = "helloworld_http/"; // 生成的目录名,末尾的/不能省略,否则会出错。

import "google/api/annotations.proto";  

message request 
  string name = 1;

message response 
  string res = 1;


service HelloWorldHTTP 
  rpc Call(request) returns(response)
    option (google.api.http) = 
      post : "/hello/world/call"
      body : "*"
    ;
  

在编译前,还需要把google/api/annotations.proto等文件复制到proto目录下。

如果你本地下载了github.com\\grpc-ecosystem\\grpc-gateway1.0+版本的话,进入该项目目录,找到third_party\\googleapis项目中的这个目录,然后把里面的google文件夹整个复制到proto目录下即可。

如果没有下载1.0+版本的话,可以到这个GitHub上去拷贝https://github.com/grpc-ecosystem/grpc-gateway/tree/v1/third_party/googleapis

protoc --go_out=plugins=grpc:. .\\helloworld_http.proto
protoc --grpc-gateway_out=logtostderr=true:. .\\helloworld_http.proto

两个命令执行完后,没出错误的话,会在proto目录下生成两个文件:helloworld_http/helloworld_http.pb.gohelloworld_http/helloworld_http.pb.gw.go

2.3 编写服务端代码

grpc_and_grpc-gateway_demo为根目录,创建server_http/server.go

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	pb "grpc_and_grpc-gateway_demo/proto/helloworld_http" // 编译生成的包
	"net"
)

type helloWorldServer struct

func (this helloWorldServer) Call(ctx context.Context, request *pb.Request) (*pb.Response, error) 
	resp := new(pb.Response)
	resp.Res = "Hello " + request.Name
	return resp, nil


func main() 
	listen, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil 
		fmt.Println(err)
		return
	
	defer listen.Close()

	s := grpc.NewServer()
	pb.RegisterHelloWorldHTTPServer(s,helloWorldServer)

	fmt.Println("Listen on 127.0.0.1:8080")

	s.Serve(listen)


运行代码

go run server.go

2.4 编写http转发代码

grpc_and_grpc-gateway_demo为根目录,创建server_http/main.go

package main

import (
	"context"
	"fmt"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"grpc_and_grpc-gateway_demo/proto/helloworld_http"
	"net/http"
)

func main() 
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// grpc服务地址
	endpoint := "127.0.0.1:8080"
	mux := runtime.NewServeMux()
	opts := []grpc.DialOptiongrpc.WithInsecure()

	// HTTP转grpc
	err := helloworld_http.RegisterHelloWorldHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts)

	if err != nil 
		fmt.Println("Register handler err:%v\\n", err)
	

	// http监听的端口
	fmt.Println("HTTP Listen on 8088")
	http.ListenAndServe(":8088", mux)


运行代码

go run main.go

2.5 测试

打开postmain或者其他接口测试工具,往以下地址发送请求,请求方法为POST

http://localhost:8088/hello/world/call

请求体为一个json数据


    "name":"jiang"

请求方法为上面proto文件中option定义的,请求地址/hello/world/call也一样是上面定义好的。

json数据name为request的内容。

三、可能遇到的坑

这个坑在上面提到过一次了,在这里再说一下,因为这个坑, 浪费博主一两个小时的时间。

一开始安装protoc-gen-grpc-gateway的时候,我也是用下面的命令安装的

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

结果在编译这个命令的时候,不能正常生成文件,反而打印了一堆奇奇怪怪的东西。

protoc --grpc-gateway_out=logtostderr=true:. .\\helloworld_http.proto

生成如下乱码。

E1207 15:44:17.874478   15920 generator.go:108] 11:1: expected 'IDENT', found 'import':
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: hello.proto

/*
Package  is a reverse proxy.

It translates gRPC into RESTful JSON APIs.

.....
.....

--grpc-gateway_out: 11:1: expected 'IDENT', found 'import'

后来看了github上的文档才解决的。

拒绝白嫖从一键三连开始!

原创不易,未经允许,请勿转载。

博客主页:https://xiaojujiang.blog.csdn.net/

以上是关于使用gRPC的stream向客户端实时传送信息的坑的主要内容,如果未能解决你的问题,请参考以下文章

gRPC和gRPC-Gateway的使用以及遇到的坑

gRPC和gRPC-Gateway的使用以及遇到的坑

gRPC和gRPC-Gateway的使用以及遇到的坑

gRPC - Firestore 如何实现服务器-> 客户端实时流式传输

grpc的简单用例 (golang实现)

Grpc Streaming 你造?