gRPC 是一个支持多种语言的高性能 RPC 框架,拥有丰富的 API 来简化服务端和客户端的开发过程。gRPC 支持四种 RPC 类型:Unary RPC、Server Streaming RPC、Client Streaming RPC 和 Bidirectional Streaming RPC。下面是双向流 API 的使用方法。
双向流 API 示例
在双向流中,客户端和服务器之间可以同时发送和接收消息。以下是一个双向流的完整示例,展示了客户端和服务端之间的聊天功能。
1. 定义 Protobuf 文件
首先,定义一个 Protobuf 文件 chat.proto
,描述服务和消息结构:
bash
syntax = "proto3";
package chat;
service ChatService {
// 双向流 RPC
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user = 1;
string text = 2;
}
2. 编译 Protobuf 文件
使用 protoc
编译器生成 Go 代码:
bash
protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. chat.proto
这会生成 chat.pb.go
文件,其中包含了定义的服务和消息。
3. 实现服务器
创建 server.go
文件,具体实现 ChatService
服务:
Go
package main
import (
"fmt"
"io"
"log"
"net"
pb "path/to/your/proto/package"
"google.golang.org/grpc"
)
type server struct {
pb.UnimplementedChatServiceServer
}
func (s *server) Chat(stream pb.ChatService_ChatServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
log.Printf("Received message from %s: %s", in.User, in.Text)
// Echo message back to client
err = stream.Send(&pb.ChatMessage{User: "Server", Text: "Echo: " + in.Text})
if err != nil {
return err
}
}
}
func main() {
listen, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterChatServiceServer(s, &server{})
log.Println("Server is running on port :50051")
if err := s.Serve(listen); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
4. 实现客户端
创建 client.go
文件,用于连接服务器并发送和接收消息:
Go
package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"time"
pb "path/to/your/proto/package"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewChatServiceClient(conn)
stream, err := client.Chat(context.Background())
if err != nil {
log.Fatalf("failed to create stream: %v", err)
}
waitc := make(chan struct{})
go func() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter message: ")
text, _ := reader.ReadString('\n')
if err := stream.Send(&pb.ChatMessage{User: "Client", Text: text}); err != nil {
log.Fatalf("failed to send message: %v", err)
break
}
}
stream.CloseSend()
}()
go func() {
for {
in, err := stream.Recv()
if err == io.EOF {
close(waitc)
return
}
if err != nil {
log.Fatalf("failed to receive message: %v", err)
break
}
log.Printf("Received message from %s: %s", in.User, in.Text)
}
}()
<-waitc
}
5. 运行示例
编译并运行服务器和客户端:
-
编译服务器和客户端:
Gogo build -o server server.go go build -o client client.go
-
启动服务器:
Go./server
-
启动客户端:
Go./client
总结
在这个例子中,服务器和客户端可以通过双向流(Bidirectional Streaming RPC)方式进行实时通信。客户端通过终端输入消息,并通过 gRPC 发送到服务器,服务器接收到消息后回显给客户端。这个示例展示了 gRPC 强大且灵活的流处理能力,在实际项目中可以根据需求进行扩展和修改。
Go
import cycle not allowedgo list
could not import example.com/user/go-project/grpc/service2 (missing metadata for import of "example.com/user/go-project/grpc/service2")
这个错误提示说您在导入路径中循环引用或缺少某些必要的元数据。通常,这些问题可能是因为错误的导入路径配置、循环依赖或者项目结构问题。
步骤 1:确认 go.mod
和目录结构正确
确保 go.mod
文件在项目根目录中,并且包含正确的模块路径和依赖项。
假设项目结构为:
Go
/home/user/go-project/grpc/
├── go.mod
├── go.sum
├── service1/
│ ├── chat.proto
│ ├── chat.pb.go
│ ├── chat_grpc.pb.go
│ ├── server.go
│ └── client.go
└── service2/
├── chat.proto
├── chat.pb.go
├── chat_grpc.pb.go
├── server.go
└── client.go
确认 go.mod
文件
在 ./grpc
目录下,初始化并正确配置 go.mod
文件:
Go
cd /home/user/go-project/grpc
go mod init example.com/user/go-project/grpc
更新 go.mod
文件以包含必要的依赖,确保内容类似如下:
Go
module example.com/user/go-project/grpc
go 1.17
require (
google.golang.org/grpc v1.39.0
google.golang.org/protobuf v1.26.0
)
运行以下命令来下载和更新所有依赖:
Go
go mod tidy