gRPC--流式传输
普通RPC(一问一答式)
编写proto
syntax = "proto3"; // 指定proto版本
package hello_grpc; // 指定默认包名
// 指定golang包名
option go_package = "/hello_grpc";
//定义rpc服务
service HelloService {
// 定义函数
rpc SayH (Req ) returns (Res) {}
}
// HelloRequest 请求内容
message Req {
string message = 1;
}
// HelloResponse 响应内容
message Res{
string message = 1;
}
服务端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"net"
hello_grpc "rpc_demo/pb"
)
type server struct {
hello_grpc.UnimplementedHelloGrpcServer
}
func (s *server) SayHi(ctx context.Context, req *hello_grpc.Req) (*hello_grpc.Res, error) {
fmt.Println(req.GetMessage())
return &hello_grpc.Res{Message: "Hello " + req.GetMessage()}, nil
}
func main() {
l, err := net.Listen("tcp", ":8888")
if err != nil {
panic(err)
}
s := grpc.NewServer()
hello_grpc.RegisterHelloGrpcServer(s, &server{})
s.Serve(l)
}
客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
hello_grpc "rpc_demo/pb"
)
type server struct {
}
func main() {
conn, err := grpc.Dial("localhost:8888", grpc.WithInsecure())
defer conn.Close()
if err != nil {
panic(err)
}
client := hello_grpc.NewHelloGrpcClient(conn)
req, _ := client.SayHi(context.Background(), &hello_grpc.Req{Message: "我从客户端来"})
fmt.Println(req.GetMessage())
}
流式传输
流式定义
service ServicesStream{
rpc Search(Req) returns (Res); //传统的 即刻响应的
rpc SayHiInStream(stream Req) returns (Res); //入参为流
rpc SayHiOutStream(Req) returns (stream Res); //入参为流
rpc SayHiIOStream(stream Req) returns (stream Res); //双向流
}
流式传入(客户端流)
rpc SayHiInStream(stream Req)returns (Res){};
服务端编写
func(s *service) SayHiInStream(serve hello_grpc.HelloService_SayHiInStreamServer)(err error) {
var res hello_grpc.Res
res.NameRes = "success"
for{
if tem,err: = serve.Recv();
err != nil {
fmt.Println("传输完了", err) serve.SendAndClose(&res) break
}
else {
fmt.Println(tem)
}
}
return
}
func(grpc 服务结构体)方法名(grpc 给定好的一个流式回参server)(错误){ serve.Recv() 进行接收 判断是否 eof
然后 可以在最后关闭之前 用 sendAndClose传输res回去 }
客户端编写
inClient,_ := client.SayHiInStream(context.Background())
i:=1
for{
inClient.Send(&hello_grpc.Req{Sex: "99",Name: "我是流"})
time.Sleep(1 * time.Second)
i++
if i>10 {
res, err := inClient.CloseAndRecv()
fmt.Println(res.NameRes,err)
break
}
}
通过client上面的grpc提前建好的一个流方法
创建一个 client
然后对流进行写入操作 在准备关闭的时候 调用 closeAndRecv()关闭并且拿到服务端返回的信息
流式返回(服务端流)
rpc SayHiOutStream(Req)returns (stream Res){};
服务端
type server struct {
pb.UnimplementedHelloServiceServer
}
func (s *server) SayHiOutStream(req *pb.Req, stream pb.HelloService_SayHiOutStreamServer) error {
fmt.Printf("Received request from %s\n", req.Name)
for i := 1; i <= 5; i++ {
res := &pb.Res{
Message: fmt.Sprintf("Hi, %s! This is message #%d", req.Name, i),
}
if err := stream.Send(res); err != nil {
return err
}
time.Sleep(1 * time.Second) // 模拟延迟发送多个响应
}
return nil
}
func main() {
lis, err := net.Listen("tcp", ":1019")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloServiceServer(s, &server{})
log.Println("Server is running on port 1019...")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
定义的服务
- 定义了一个
server结构体,嵌套了pb.UnimplementedHelloServiceServer,这是 gRPC 自动生成的代码中的占位实现。 - 实现了
SayHiOutStream方法:- 接收客户端的请求(
*pb.Req)。 - 循环发送 5 条响应消息给客户端,每条消息之间间隔 1 秒。
- 接收客户端的请求(
主函数
- 创建一个 TCP 监听器,监听端口
1019。 - 初始化一个 gRPC 服务器实例,并将
server注册到该实例中。 - 启动 gRPC 服务器,开始处理客户端请求。
客户端
func main() {
conn, err := grpc.Dial("localhost:1019", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(client)
req := &pb.Req{Name: "Alice"}
stream, err := client.SayHiOutStream(context.Background(), req)
if err != nil {
log.Fatalf("could not send request: %v", err)
}
for {
res, err := stream.Recv()
if err != nil {
break
}
fmt.Println("Received:", res.Message)
}
}
流式出入(双向流)
rpc SayHiIOStream(stream Req)returns (stream Res){};
服务端
type server struct {
pb.UnimplementedHelloServiceServer
}
func (s *server) SayHiIOStream(stream pb.HelloService_SayHiIOStreamServer) error {
for {
// 接收客户端的消息
req, err := stream.Recv()
if err != nil {
return err
}
log.Printf("Received from client: %s", req.Name)
// 回复一条消息给客户端
res := &pb.Res{
Message: fmt.Sprintf("Hi, %s! I'm the server.", req.Name),
}
if err := stream.Send(res); err != nil {
return err
}
time.Sleep(500 * time.Millisecond) // 模拟处理延迟
}
}
func main() {
lis, err := net.Listen("tcp", ":1019")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHelloServiceServer(s, &server{})
log.Println("Server is running on port 1019...")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
实现**SayHiIOStream**方法
func (s *server) SayHiIOStream(stream pb.HelloService_SayHiIOStreamServer) error {
for {
// 接收客户端的消息
req, err := stream.Recv()
if err != nil {
return err
}
log.Printf("Received from client: %s", req.Name)
// 回复一条消息给客户端
res := &pb.Res{
Message: fmt.Sprintf("Hi, %s! I'm the server.", req.Name),
}
if err := stream.Send(res); err != nil {
return err
}
time.Sleep(500 * time.Millisecond) // 模拟处理延迟
}
}
- 这是一个无限循环,持续接收来自客户端的消息。
- 每当接收到一条消息时,打印出客户端的名字,并构造一条回复消息发送回去。
- 使用
time.Sleep来模拟处理时间,让每条消息之间有短暂的延迟。
客户端
func main() {
conn, err := grpc.Dial("localhost:1019", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewHelloServiceClient(conn)
stream, err := client.SayHiIOStream(context.Background())
if err != nil {
log.Fatalf("could not open stream: %v", err)
}
// 启动 goroutine 监听服务端的响应
go func() {
for {
res, err := stream.Recv()
if err != nil {
log.Printf("Error receiving: %v", err)
break
}
fmt.Println("Server says:", res.Message)
}
}()
// 模拟发送多个请求
names := []string{"Alice", "Bob", "Charlie", "David"}
for _, name := range names {
req := &pb.Req{Name: name}
if err := stream.Send(req); err != nil {
log.Fatalf("could not send: %v", err)
}
fmt.Println("Sent:", name)
time.Sleep(1 * time.Second)
}
// 关闭发送通道
err = stream.CloseSend()
if err != nil {
log.Fatalf("could not close send: %v", err)
}
}
启动 goroutine 监听服务端的响应
// 启动 goroutine 监听服务端的响应
go func() {
for {
res, err := stream.Recv()
if err != nil {
log.Printf("Error receiving: %v", err)
break
}
fmt.Println("Server says:", res.Message)
}
}()
- 在后台启动一个 goroutine 持续监听从服务端发来的消息。
- 每次接收到消息时打印出来。
发送请求
names := []string{"Alice", "Bob", "Charlie", "David"}
for _, name := range names {
req := &pb.Req{Name: name}
if err := stream.Send(req); err != nil {
log.Fatalf("could not send: %v", err)
}
fmt.Println("Sent:", name)
time.Sleep(1 * time.Second)
}
- 遍历一个名字列表,将每个名字作为请求发送给服务端。
- 发送请求后打印一条确认信息,并等待一秒以模拟用户输入间隔。
关闭发送通道
// 关闭发送通道
err = stream.CloseSend()
if err != nil {
log.Fatalf("could not close send: %v", err)
}
- 当所有消息都发送完毕后,调用
CloseSend()关闭客户端的发送部分,表示不会再发送更多数据。 - 这使得服务端能够检测到客户端已完成发送,从而可以选择是否继续处理或关闭连接。