本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Golang 使用Kratos微服务框架搭建基于gRPC的双向数据流Demo,简单的双向通信.
创建项目kratos new grpcStream
cd grpcStream
go generate ./...
go版本 1.17
kratosv2版本 v2.3.0
Proto
编辑 .\api\helloworld\v1\greeter.proto
// 流式请求
message StreamRequest{
string req = 1;
}
// 流式响应
message StreamResponse{
string res = 1;
}
// 服务方法
service Stream{
// 双向流式rpc,同时在请求参数前和响应参数前加上stream
rpc Conversations(stream StreamRequest) returns(stream StreamResponse){};
}
生成proto对应的go文件 kratos proto client .\api\helloworld\v1\greeter.proto
Service
生成server代码 kratos proto server .\api\helloworld\v1\greeter.proto -t .\internal\service
(这里已经生成了双向数据流的代码了)
service里的service把Stream加上
var ProviderSet = wire.NewSet(NewGreeterService,NewStreamService)
先简单的测试
go generate ./...
运行 运行种类:软件包(package)
软件包路径(Path):grpcStream/cmd/grpcStream
工作目录为项目目录
参数使用相对路径读取配置 -conf ./configs/config.yaml
测试代码
package main
import (
"context"
"github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/middleware/recovery"
transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
v1 "grpcStream/api/helloworld/v1"
"log"
)
// 测试客户端连接
func main() {
callGRPC()
}
func callGRPC() {
conn, err := transgrpc.DialInsecure(
context.Background(),
transgrpc.WithEndpoint("127.0.0.1:9000"),
transgrpc.WithMiddleware(
recovery.Recovery(),
),
)
if err != nil {
panic(err)
}
defer conn.Close()
client := v1.NewGreeterClient(conn)
log.Printf("[grpc] SayHello %+v\n", client)
reply, err := client.SayHello(context.Background(), &v1.HelloRequest{Name: "Nick"})
if err != nil {
log.Fatal(err)
}
if errors.IsBadRequest(err) {
log.Printf("[grpc] SayHello error is invalid argument: %v\n", err)
}
log.Printf("[grpc] SayHello %+v\n", reply)
}
双向数据流
编写 internal/service/stream.go
package service
import (
"fmt"
"io"
pb "grpcStream/api/helloworld/v1"
)
type StreamService struct {
pb.UnimplementedStreamServer
}
func NewStreamService() *StreamService {
return &StreamService{}
}
func (s *StreamService) Conversations(conn pb.Stream_ConversationsServer) error {
for {
req, err := conn.Recv()
if err == io.EOF {
// 流结束
fmt.Println("客户端发送的数据流结束")
return nil
}
if err != nil {
// 流出现错误
fmt.Println("接收数据出错:", err)
return err
}
// TODO 处理消息
fmt.Println(req.GetReq())
// 返回消息
err = conn.Send(&pb.StreamResponse{
Res: "Receive " + req.GetReq(),
})
if err != nil {
// 返回出现出现错误
return err
}
}
}
编写 internal/server/grpc.go
// 参数增加 stream *service.StreamService,
func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, stream *service.StreamService, logger log.Logger) *grpc.Server {
var opts = []grpc.ServerOption{
grpc.Middleware(
recovery.Recovery(),
),
}
if c.Grpc.Network != "" {
opts = append(opts, grpc.Network(c.Grpc.Network))
}
if c.Grpc.Addr != "" {
opts = append(opts, grpc.Address(c.Grpc.Addr))
}
if c.Grpc.Timeout != nil {
opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
}
srv := grpc.NewServer(opts...)
v1.RegisterGreeterServer(srv, greeter)
v1.RegisterStreamServer(srv, stream) //增加
return srv
}
go generate ./...
wire_gen要如下图所示,自己写的Stream才会生效
测试
test/main.go
package main
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/middleware/recovery"
transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
v1 "grpcStream/api/helloworld/v1"
"io"
"log"
)
// 测试客户端连接
func main() {
callStream()
}
func callStream() {
conn, err := transgrpc.DialInsecure(
context.Background(),
transgrpc.WithEndpoint("127.0.0.1:9000"),
transgrpc.WithMiddleware(
recovery.Recovery(),
),
)
if err != nil {
panic(err)
}
defer conn.Close()
client := v1.NewStreamClient(conn)
conversations, err := client.Conversations(context.TODO())
if err != nil {
log.Fatalf("Failed : %v", err)
return
}
waitc := make(chan struct{})
// 接收
go func() {
for {
in, err := conversations.Recv()
if err == io.EOF {
// read done.
close(waitc)
return
}
if err != nil {
log.Fatalf("Failed to receive a note : %v", err)
}
log.Printf("Got message (%s)", in.GetRes())
}
}()
// 客户端模拟发送
for n := 0; n < 5; n++ {
if err := conversations.Send(&v1.StreamRequest{Req: fmt.Sprintf("this is %d", n)}); err != nil {
log.Fatalf("Failed to send a note: %v", err)
}
}
//最后关闭流
err = conversations.CloseSend()
if err != nil {
log.Fatalf("Conversations close stream err: %v", err)
}
<-waitc
}
测试结果
左边是服务端接收到消息,然后返回,右边是客户端接收到返回消息输出。
通过chan异步发送消息
模拟聊天...... (下一篇)