1、四种数据流
- 简单模式(Simple RPC):即客户端发起一次请求,服务端响应一个数据。
- 服务端数据流模式(Server-side streaming RPC):客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
- 客户端数据流模式(Client-side streaming RPC):客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。
- 双向数据流模式(Bidirectional streaming RPC):客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时相互发送,可以实现实时交互。典型的例子就是聊天机器人。
2、创建proto文件
echo.proto
syntax = "proto3";
option go_package="../echo";
//EchoRequest is the request for echo.
message EchoRequest {
string message = 1;
}
//EchoResponse is the response for echo.
message EchoResponse {
string message = 1;
}
// Echo is the echo service
service Echo {
// UnaryEcho is unary echo.
rpc UnaryEcho(EchoRequest) returns (EchoResponse);
// ServerStreamingEcho is server side streaming.
rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse);
// ClientStreamingEcho is client side streaming.
rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse);
// BidirectionalStreamingEcho is bidi streaming.
rpc BidirectionalStreamingEcho(stream EchoRequest) returns(stream EchoResponse);
}
使用以下命令,生成go 源码文件:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
echo.proto
3、服务端流模式
server端:
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"net"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
pb "happy-go/microservice/grpc/features/proto/echo"
)
var port = flag.Int("port", 50051, "the port to serve on")
const (
timestampFormat = time.StampNano
streamingCount = 10
)
type server struct {
pb.UnimplementedEchoServer
}
func (s *server) ServerStreamingEcho(in *pb.EchoRequest, stream pb.Echo_ServerStreamingEchoServer) error {
fmt.Printf("--- ServerStreamingEcho ---\n")
//create trailer in defer to record function return time
defer func() {
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
stream.SetTrailer(trailer)
}()
// Read metadata from client.
md, ok := metadata.FromIncomingContext(stream.Context())
if !ok {
return status.Errorf(codes.DataLoss, "ServerStreamingEcho: failed to get metadata")
}
if t, ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
}
//create and send header
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
stream.SendHeader(header)
fmt.Printf("request received: %v\n", in)
//Read requests and send responses.
for i := 0; i < streamingCount; i++ {
fmt.Printf("echo message %v\n", in.Message)
err := stream.Send(&pb.EchoResponse{Message: in.Message})
if err != nil {
return err
}
}
return nil
}
func main() {
flag.Parse()
rand.Seed(time.Now().UnixNano())
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
fmt.Printf("server listening at %v\n", lis.Addr())
s := grpc.NewServer()
pb.RegisterEchoServer(s, &server{})
s.Serve(lis)
}
client端:
package main
import (
"context"
"flag"
"fmt"
pb "happy-go/microservice/grpc/features/proto/echo"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to ")
const (
timestampFormat = time.StampNano
streamingCount = 10
)
// ServerStreamingEcho(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (Echo_ServerStreamingEchoClient, error)
func serverStreamingWithMetadata(c pb.EchoClient, message string) {
fmt.Printf("--- server streaming client ---\n")
// Create metadata and context.
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
ctx := metadata.NewOutgoingContext(context.Background(), md)
//Make RPC using the context with metadata
stream, err := c.ServerStreamingEcho(ctx, &pb.EchoRequest{Message: message})
if err != nil {
log.Fatalf("failed to call ServerStreamingEcho: %v", err)
}
//Read the header when the header arrives
header, err := stream.Header()
if err != nil {
log.Fatalf("failed to get header from stream: %v", err)
}
// Read metadata from server's header
if t, ok := header["timestamp"]; ok {
fmt.Printf("timestamp from header:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("timestamp expected but doesn't exist in header")
}
if l, ok := header["location"]; ok {
fmt.Printf("location from header:\n")
for i, e := range l {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("location expected but doesn't exist in header")
}
// Read all the response
var rpcStatus error
fmt.Printf("response:\n")
for {
r, err := stream.Recv()
if err != nil {
rpcStatus = err
break
}
fmt.Printf(" - %s\n", r.Message)
}
if rpcStatus != io.EOF {
log.Fatalf("failed to finish server streaming: %v", rpcStatus)
}
//Read the trailer after the RPC is finished
trailer := stream.Trailer()
//Read metadata from server's trailer
if t, ok := trailer["timestamp"]; ok {
fmt.Printf("timestamp from trailer:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("timestamp expected but doesn't exist in trailer")
}
}
const message = "this is examples/metadata"
func main() {
flag.Parse()
//Set up a connection to the server
conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewEchoClient(conn)
serverStreamingWithMetadata(client, message)
time.Sleep(1 * time.Second)
}
4、客户端流模式
server端:
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"net"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
pb "happy-go/microservice/grpc/features/proto/echo"
)
var port = flag.Int("port", 50051, "the port to serve on")
const (
timestampFormat = time.StampNano
streamingCount = 10
)
type server struct {
pb.UnimplementedEchoServer
}
func (s *server) ClientStreamingEcho(stream pb.Echo_ClientStreamingEchoServer) error {
fmt.Printf("--- ClientStreamingEcho ---\n ")
// Create trailer in defer to record function return time
defer func() {
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
stream.SetTrailer(trailer)
}()
// Read metadata form client
md, ok := metadata.FromIncomingContext(stream.Context())
if !ok {
return status.Errorf(codes.DataLoss, "ClientStreamingEcho:failed to get metadata")
}
if t, ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
}
//Create and send header
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
stream.SendHeader(header)
//read requests and send response
var message string
for {
in, err := stream.Recv()
if err == io.EOF {
fmt.Printf("echo last received message\n")
return stream.SendAndClose(&pb.EchoResponse{Message: message})
}
message = in.Message
fmt.Printf("request received: %v,building echo\n", in)
if err != nil {
return err
}
}
}
func main() {
flag.Parse()
rand.Seed(time.Now().UnixNano())
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
fmt.Printf("server listening at %v\n", lis.Addr())
s := grpc.NewServer()
pb.RegisterEchoServer(s, &server{})
s.Serve(lis)
}
client端:
package main
import (
"context"
"flag"
"fmt"
pb "happy-go/microservice/grpc/features/proto/echo"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to ")
const (
timestampFormat = time.StampNano
streamingCount = 10
)
//ClientStreamingEcho(ctx context.Context, opts ...grpc.CallOption) (Echo_ClientStreamingEchoClient, error)
func clientStreamWithMetadata(c pb.EchoClient, message string) {
fmt.Sprintf("--- client streaming ---\n")
// Create medata and context.
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
ctx := metadata.NewOutgoingContext(context.Background(), md)
//Makd RPC using the context with metadata
stream, err := c.ClientStreamingEcho(ctx)
if err != nil {
log.Fatalf("failed to call ClientStreamingEcho: %v\n", err)
}
// Read the header when the header arrives.
header, err := stream.Header()
if err != nil {
log.Fatalf("failed to get header form steam: %v", err)
}
//Read metadata form server's header.
if t, ok := header["timestamp"]; ok {
fmt.Printf("timestamp from header:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("timestamp expected but doesn't exist in header")
}
if l, ok := header["location"]; ok {
fmt.Printf("location from header:\n")
for i, e := range l {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("location expected but doesn't exist in header")
}
// Send all requests to the server
for i := 0; i < streamingCount; i++ {
if err := stream.Send(&pb.EchoRequest{Message: message}); err != nil {
log.Fatal("failed to send streaming: %v\n", err)
}
}
// Read the response
r, err := stream.CloseAndRecv()
if err != nil {
log.Fatalf("failed to CloseAndRecv: %v\n", err)
}
fmt.Printf("response:\n")
fmt.Printf(" - %s\n\n", r.Message)
//Read the trailer after the RPC is finished
trailer := stream.Trailer()
// Read metadata from server's trailer
if t, ok := trailer["timestamp"]; ok {
fmt.Printf("timestamp from trailer:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("timestamp expected but doesn't exist in trailer")
}
}
const message = "this is examples/metadata"
func main() {
flag.Parse()
//Set up a connection to the server
conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewEchoClient(conn)
clientStreamWithMetadata(client, message)
time.Sleep(1 * time.Second)
}
5、双向数据流模式
server端:
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"math/rand"
"net"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
pb "happy-go/microservice/grpc/features/proto/echo"
)
var port = flag.Int("port", 50051, "the port to serve on")
const (
timestampFormat = time.StampNano
streamingCount = 10
)
type server struct {
pb.UnimplementedEchoServer
}
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
fmt.Printf("--- BidirectionalStreamingEcho ---\n")
// Create trailer in defer to record function return time
defer func() {
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
stream.SetTrailer(trailer)
}()
//Receive metadata from client
md, ok := metadata.FromIncomingContext(stream.Context())
if !ok {
return status.Errorf(codes.DataLoss, "BidirectionalStreamingEcho:failed to get metadata")
}
if t, ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
}
// Create and send header.
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
stream.SendHeader(header)
// Read requests and send responses
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
fmt.Printf("request received %v,sending echo\n", in)
if err := stream.Send(&pb.EchoResponse{Message: in.Message}); err != nil {
return err
}
}
}
func main() {
flag.Parse()
rand.Seed(time.Now().UnixNano())
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
fmt.Printf("server listening at %v\n", lis.Addr())
s := grpc.NewServer()
pb.RegisterEchoServer(s, &server{})
s.Serve(lis)
}
client端:
package main
import (
"context"
"flag"
"fmt"
pb "happy-go/microservice/grpc/features/proto/echo"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to ")
const (
timestampFormat = time.StampNano
streamingCount = 10
)
// c(ctx context.Context, opts ...grpc.CallOption) (Echo_BidirectionalStreamingEchoClient, error)
func bidirectionalWithMetadata(c pb.EchoClient, message string) {
fmt.Printf(" --- bidirectinal --- \n")
// Create metadata and context
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
ctx := metadata.NewOutgoingContext(context.Background(), md)
//Make RPC using the context with the metadata
stream, err := c.BidirectionalStreamingEcho(ctx)
if err != nil {
log.Fatalf("failed to call bidirectionalWithMetadata: %v\n", err)
}
go func() {
//Read the header when the header arrives.
header, err := stream.Header()
if err != nil {
log.Fatalf("failed to get header from stream:%v", err)
}
//Read metadata from server's header.
if t, ok := header["timestamp"]; ok {
fmt.Printf("timestamp from header:\n")
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatalf("timestamp expected but doesn't exist in header")
}
if l, ok := header["location"]; ok {
fmt.Printf("location from header:\n")
for i, e := range l {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatalf("location expected but doesn't exist in header")
}
//Send All requests to the server
for i := 0; i < streamingCount; i++ {
if err := stream.Send(&pb.EchoRequest{Message: message}); err != nil {
log.Fatalf("failed to send streaming: %v\n", err)
}
}
stream.CloseSend()
}()
//Read all response
var rpcStatus error
fmt.Printf("response:\n")
for {
r, err := stream.Recv()
if err != nil {
rpcStatus = err
break
}
fmt.Printf(" - %s\n", r.Message)
}
if rpcStatus != io.EOF {
log.Fatalf("failed to finish server streaming: %v", rpcStatus)
}
// Read the trailer after the RPC is finished
trailer := stream.Trailer()
// Read metadata from server's trailer
if t, ok := trailer["timestamp"]; ok {
for i, e := range t {
fmt.Printf(" %d. %s\n", i, e)
}
} else {
log.Fatal("timestamp expected but doesn't exist in trailer")
}
}
const message = "this is examples/metadata"
func main() {
flag.Parse()
//Set up a connection to the server
conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewEchoClient(conn)
bidirectionalWithMetadata(client, message)
}