介绍
grpc streaming是基于http/2的。
简单版的rpc存在以下问题
- 数据包过大时造成的瞬时压力
- 接收数据包时,需要所有数据包都接受成功且正确后,才能够回调响应,进行业务处理(无法客户端边发送,服务端边处理)
streaming rpc优点
- 大规模数据包
- 实时场景
目录结构
stream_server、stream_client存放服务端和客户端文件。
IDL
stream.proto文件内容
syntax = "proto3";
package proto;
service StreamService{
//服务器端流式rpc
rpc List(StreamRequest) returns (stream StreamResponse){};
//客户端流式rpc
rpc Record(stream StreamRequest)returns (StreamResponse){};
//双向流式rpc
rpc Route(stream StreamRequest)returns(stream StreamResponse){};
}
message StreamPoint{
string name = 1;
int32 value = 2;
}
message StreamRequest{
StreamPoint pt = 1;
}
message StreamResponse{
StreamPoint pt = 1;
}
基础模板
server
package main
import (
"google.golang.org/grpc"
"log"
"net"
pb "wsn-grpc/proto"
)
type StreamService struct {
}
func main() {
port := ":9092"
server := grpc.NewServer()
pb.RegisterStreamServiceServer(server, &StreamService{})
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatal("net.listen err:%v", err)
}
server.Serve(lis)
}
func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error {
return nil
}
func (s *StreamService) Record(stream pb.StreamService_RecordServer) error {
return nil
}
func (s *StreamService) Route(stream pb.StreamService_RouteServer) error {
return nil
}
client
package main
import (
"google.golang.org/grpc"
"log"
pb "wsn-grpc/proto"
)
func main() {
port := ":9092"
conn, err := grpc.Dial("localhost"+port, grpc.WithInsecure())
if err != nil {
log.Fatalf("grpc.Dial err:%v", err)
}
defer conn.Close()
client := pb.NewStreamServiceClient(conn)
err = printLists(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "grpc list", Value: 2022}})
if err != nil {
log.Fatalf("printlists.err:%v", err)
}
err = printRecord(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "grpc record", Value: 2022}})
if err != nil {
log.Fatalf("printrecord.err:%v", err)
}
err = printRoute(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "grpc route", Value: 2022}})
if err != nil {
log.Fatalf("printroute.err:%v", err)
}
}
func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
return nil
}
func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
return nil
}
func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
return nil
}
代码实现
服务端流式RPC
服务端流式RPC,是单向流,指的是 server端为stream client端是普通的rpc请求。
server
func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error {
for n := 0; n <= 6; n++ {
time.Sleep(2*time.Second)
err := stream.Send(&pb.StreamResponse{
Pt: &pb.StreamPoint{
Name: r.Pt.Name,
Value: r.Pt.Value + int32(n),
},
})
if err != nil {
return err
}
}
return nil
}
client
func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
stream, err := client.List(context.Background(), r)
if err != nil {
return err
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
log.Printf("resp:name:%s,value:%s", resp.Pt.Name, resp.Pt.Value)
}
return nil
}
RecvMsg会从流中读取完整的grpc消息体。
- RecvMsg是阻塞等待的
- RecvMsg当流成功/结束(调用close)时,会返回io.EOF
- RecvMsg当流出现任何错误时,流会被中止,错误信息会包含rpc错误码。
默认的MaxReceiveMessageSize值为102410244,建议不要超出。
客户端流式RPC
客户端通过流式发起多次RPC请求给服务端,服务端发起一次响应给客户端。
server
func (s *StreamService) Record(stream pb.StreamService_RecordServer) error {
for {
r, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.StreamResponse{Pt: &pb.StreamPoint{Name: "grpc server record", Value: 1}})
}
if err != nil {
return err
}
log.Printf("stream recv name:%s value:%d", r.Pt.Name, r.Pt.Value)
time.Sleep(1 * time.Second)
}
return nil
}
stream.SendAndClose作用:
我们对客户端发送的消息都进行了处理,当发现io.EOF(流关闭)后,需要将最终的响应结果发送给客户端,同时关闭正在另外一侧等待的Recv
client
func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
stream, err := client.Record(context.Background())
for n := 0; n < 6; n++ {
err := stream.Send(r)
if err != nil {
return err
}
}
resp, err := stream.CloseAndRecv()
if err != nil {
return err
}
log.Printf("resp: name:%s value:%d", resp.Pt.Name, resp.Pt.Value)
return nil
}
stream.CloseAndRecv和stream.SendAndClose是配套使用的流方法。
双向流式RPC
客户端与服务端都以同样的流式操作。
首个请求一定是client发起,但具体交互方式(谁先谁后、一次发多少、响应多少、什么时候关闭)根据程序编写的方式来确定。
server
func (s *StreamService) Route(stream pb.StreamService_RouteServer) error {
n := 0
for {
err := stream.Send(&pb.StreamResponse{
Pt: &pb.StreamPoint{
Name: "grpc stream client:route",
Value: int32(n),
},
})
if err != nil {
return err
}
r, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
n++
log.Printf("stream recv name:%s value:%d", r.Pt.Name, r.Pt.Value)
}
return nil
}
client
func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
stream, err := client.Route(context.Background())
if err != nil {
return err
}
for n := 0; n <= 6; n++ {
err := stream.Send(r)
if err != nil {
return err
}
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
log.Printf("resp name:%s value:%d", resp.Pt.Name, resp.Pt.Value)
}
stream.CloseSend()
return nil
}