目录如下:
proto文件书写
- 根目录执行 生成 hello.pb.go 文件
protoc -I . --go_out=. --go_opt=paths=source_relative ./helloworld/proto/hello.proto
- 根目录执行 生成 hello_grpc.pb.go 文件
protoc -I . --go-grpc_out=. --go-grpc_opt=paths=source_relative ./helloworld/proto/hello.proto
syntax = "proto3";
// 命名空间防止命名冲突
package helloworld;
//生成代码的引用路径
option go_package = "grpc/helloworld/proto";
import "google/protobuf/timestamp.proto";// 引入其他proto文件,时间类型
import "google/protobuf/any.proto";// go语言的interface类型,可以包含任意类型
service Greeter {
// 一元调用 (适合小数据量)
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 客户端流式调用 (适合文件上传)
rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply) {}
// 服务端流式调用 (适合文件下载)
rpc SayHelloStream (HelloRequest) returns (stream HelloReply) {}
// 双向流式调用 (适合实时通信)
rpc SayHelloBothStream (stream HelloRequest) returns (stream HelloReply) {}
}
enum Gender {
// 从0开始
MALE = 0;
FEMALE = 1;
THIRD = 2;
reserved 3; // 也可使用保留字段
}
message Address {
string provice = 1;
string city = 2;
}
message HelloRequest {
// 小技巧: 使用的字段多的排前面
// 1开始 1-15: 占一个字节
string name = 1;
Gender gender = 2;
uint32 age = 3;
google.protobuf.Timestamp birthday = 4; // 时间类型
Address addr = 5; //自定义类型
repeated string hobys = 6; // 定义数组/切片
map<string, google.protobuf.Any> data = 7; // 定义map
reserved 8, 9 ,18 to 50; // 保留数字字段
reserved "phone", "email"; // 保留字段名
}
message HelloReply {
string message = 1;
}
// 根目录执行 生成 hello.pb.go 文件
// protoc -I . --go_out=. --go_opt=paths=source_relative ./helloworld/proto/hello.proto
// 根目录执行 生成 hello_grpc.pb.go 文件
// protoc -I . --go-grpc_out=. --go-grpc_opt=paths=source_relative ./helloworld/proto/hello.proto
服务端代码
- go run .\helloworld\server\server.go 执行命令
package main
import (
"context"
"flag"
"fmt"
"grpc/helloworld/proto"
"io"
"log"
"net"
"time"
"google.golang.org/grpc"
)
// 设置端口号为50051
var (
port = flag.Int("port", 50051, "端口号")
)
type server struct {
proto.UnimplementedGreeterServer // 没实现的接口也能使用
}
// 实现 SayHello 方法
func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &proto.HelloReply{Message: "Hello " + in.GetName()}, nil
}
// 客户端流式 RPC
func (s *server) SayHelloClientStream(stream proto.Greeter_SayHelloClientStreamServer) error {
i := 0
for {
req, err := stream.Recv() // 接收客户端发送的数据
if err == io.EOF { // 接收完毕
return stream.SendAndClose(&proto.HelloReply{Message: fmt.Sprintf("total: %d", i)})
}
if err != nil {
log.Printf("stream.Recv() error: %v", err)
return err
}
log.Printf("Received: %v", req.GetName())
i++
}
}
// 服务端流式 RPC
func (s *server) SayHelloStream(in *proto.HelloRequest, stream proto.Greeter_SayHelloStreamServer) error {
log.Printf("Received request: %v", in.GetName())
// 模拟发送多条消息
messages := []string{
fmt.Sprintf("Hello %s!", in.GetName()),
fmt.Sprintf("Welcome to gRPC streaming, %s!", in.GetName()),
fmt.Sprintf("This is message 3 for %s!", in.GetName()),
fmt.Sprintf("Last message for %s!", in.GetName()),
}
// 逐条发送消息
for _, msg := range messages {
// 模拟处理延迟
time.Sleep(time.Second)
// 发送消息
if err := stream.Send(&proto.HelloReply{Message: msg}); err != nil {
log.Printf("Failed to send message: %v", err)
return err
}
log.Printf("Sent message: %s", msg)
}
return nil
}
// 双向流式 RPC
func (s *server) SayHelloBothStream(stream proto.Greeter_SayHelloBothStreamServer) error {
i := 0
for {
req, err := stream.Recv() // 接收客户端发送的数据
if err == io.EOF { // 接收完毕
break
}
if err != nil {
log.Printf("stream.Recv() error: %v", err)
return err
}
log.Printf("Received: %v\n", req)
// 发送数据给客户端
err = stream.Send(&proto.HelloReply{Message: fmt.Sprintf("%d", i)})
if err != nil {
log.Printf("stream.Send() error: %v", err)
return err
}
i++
}
return nil
}
func main() {
flag.Parse() // 解析命令行参数
//server端
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
return
}
s := grpc.NewServer() // 创建gRPC服务
proto.RegisterGreeterServer(s, &server{}) // 注册服务
err = s.Serve(lis)
if err != nil {
log.Fatalf("failed to serve: %v", err)
return
}
fmt.Println("server start")
}
// go run .\helloworld\server\server.go 执行命令
客户端代码
- go run .\helloworld\client\client.go 执行命令
package main
import (
"context"
"flag"
"fmt"
"grpc/helloworld/proto"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
addr = flag.String("addr", "localhost:50051", "server address") // 获取服务端的端口
)
func main() {
flag.Parse()
// 设置连接超时
// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// defer cancel()
// 客户端实现
// conn, err := grpc.DialContext(ctx, *addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close() // 关闭连接
// 创建客户端
c := proto.NewGreeterClient(conn)
sayHello(c) // 调用服务端的方法
sayHelloStream(c) // 调用客户端流的方法
SayHelloBothStream(c) // 调用双向流的方法
SayHelloStream(c) // 调用服务端流的方法
}
func getHelloRequest() *proto.HelloRequest {
birthday := timestamppb.New(time.Now()) // 创建生日
any1, _ := anypb.New(birthday) // 必须使用proto里面定义的类型,否则会报错
in := &proto.HelloRequest{
Name: "jxb",
Gender: proto.Gender_FEMALE,
Age: 18,
Birthday: birthday,
Hobys: []string{"吃饭", "睡觉", "打豆豆"},
Addr: &proto.Address{
Provice: "湖南",
City: "永州",
},
Data: map[string]*anypb.Any{
"a": any1,
},
} // 创建请求
return in
}
// 正常调用
func sayHello(c proto.GreeterClient) {
ctx := context.Background() // 创建上下文
in := getHelloRequest() // 获取请求
res, err := c.SayHello(ctx, in) // 调用服务端的方法
if err != nil {
log.Fatal(err)
return
}
log.Println(res.Message) // 打印服务端返回的消息
}
// 客户端流式调用
func sayHelloStream(c proto.GreeterClient) {
ctx := context.Background() // 创建上下文
list := []*proto.HelloRequest{
getHelloRequest(),
getHelloRequest(),
getHelloRequest(),
} // 创建请求列表
stream, err := c.SayHelloClientStream(ctx) // 调用服务端的方法
if err != nil {
log.Fatal(err)
return
}
// 客户端持续发送流
for _, in := range list {
if err = stream.Send(in); err != nil {
log.Fatal(err)
return
}
}
reply, err := stream.CloseAndRecv() // 关闭流并接收服务端返回的消息
if err != nil {
log.Fatal(err)
return
}
log.Println("客户端流式调用返回消息:", reply.Message) // 打印服务端返回的消息
}
// 双向流式调用
func SayHelloBothStream(c proto.GreeterClient) {
ctx := context.Background() // 创建上下文
list := []*proto.HelloRequest{
getHelloRequest(),
getHelloRequest(),
getHelloRequest(),
} // 创建请求列表
stream, err := c.SayHelloBothStream(ctx) // 调用服务端的方法
if err != nil {
log.Fatal(err)
return
}
var done = make(chan struct{}, 0) // 创建一个通道用于接收服务端返回的消息
// 客户端持续接收(用协程)
go func() {
for {
repley, err := stream.Recv() // 接收请求
if err == io.EOF {
close(done) // 关闭通道
return
}
if err != nil {
log.Fatal(err)
close(done) // 关闭通道
return
}
fmt.Printf("client recv: %v\n", repley.Message)
}
}()
// 客户端持续发送流
for _, in := range list {
if err = stream.Send(in); err != nil {
log.Fatal(err)
return
}
}
err = stream.CloseSend() // 关闭客户端发送流
<-done // 等待服务端返回消息
if err != nil {
log.Fatal(err)
return
}
}
// 服务端流式调用
func SayHelloStream(c proto.GreeterClient) {
ctx := context.Background() // 创建上下文
stream, err := c.SayHelloStream(ctx, getHelloRequest()) // 调用服务端的方法
if err != nil {
log.Fatal(err)
return
}
// 创建一个通道来同步
done := make(chan struct{})
// 持续接收服务端流式响应
go func() {
defer close(done) // 确保退出时关闭通道
for {
reply, err := stream.Recv() // 接收响应
if err == io.EOF { // 流结束
break
}
if err != nil {
log.Fatal(err)
return
}
fmt.Printf("client recv: %v\n", reply.Message)
}
}()
<-done // 等待接收完成
fmt.Println("Client stream processing completed")
}
// go run .\helloworld\client\client.go 执行命令
-
服务端
-
客户端