Golang如何使用gRPC实现双向流通信

来源:网络编程作者:深圳网站建设头衔:草根站长
导读:本期聚焦于小伙伴创作的《Golang如何使用gRPC实现双向流通信》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Golang如何使用gRPC实现双向流通信》有用,将其分享出去将是对创作者最好的鼓励。

gRPC的双向流通信模式允许客户端和服务端建立连接后,双方都可以独立地向对方发送消息流,不需要等待对方的响应,非常适合实时数据同步、长连接推送等场景。在Golang中实现gRPC双向流,需要先定义protobuf协议,再分别实现服务端和客户端的逻辑。

Golang如何使用gRPC实现双向流通信

环境准备

首先需要安装Golang环境和相关依赖工具:

  • 安装Golang,版本建议1.16及以上
  • 安装protobuf编译器protoc,用于生成Golang代码
  • 安装Golang的protobuf和gRPC相关插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go get google.golang.org/grpc
go get google.golang.org/protobuf

定义protobuf协议文件

双向流通信需要在protobuf的rpc定义中使用stream关键字标记请求和响应参数,下面定义一个简单的聊天场景的协议文件chat.proto

syntax = "proto3";

package chat;

option go_package = "./chat";

// 定义聊天消息结构
message ChatMessage {
  string sender = 1;  // 发送者
  string content = 2; // 消息内容
  int64 timestamp = 3; // 时间戳
}

// 定义聊天服务
service ChatService {
  // 双向流通信的rpc方法,请求和响应都带stream
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

执行以下命令生成Golang代码:

protoc --go_out=. --go_opt=paths=source_relative 
--go-grpc_out=. --go-grpc_opt=paths=source_relative 
chat.proto

实现gRPC服务端

服务端需要实现ChatServiceServer接口,处理客户端的双向流请求,这里实现简单的消息转发逻辑,将客户端发送的消息加上服务端标识后返回:

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"time"

	pb "your_module_path/chat" // 替换为实际的模块路径
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// 定义服务端结构体,实现ChatServiceServer接口
type chatServer struct {
	pb.UnimplementedChatServiceServer
}

// 实现Chat方法,处理双向流
func (s *chatServer) Chat(stream pb.ChatService_ChatServer) error {
	// 启动一个goroutine接收客户端发送的消息
	go func() {
		for {
			// 接收客户端消息
			in, err := stream.Recv()
			if err == io.EOF {
				// 客户端关闭发送流
				return
			}
			if err != nil {
				log.Printf("接收客户端消息失败: %v", err)
				return
			}
			log.Printf("收到客户端消息: 发送者=%s, 内容=%s", in.Sender, in.Content)

			// 构造响应消息,加上服务端标识
			resp := &pb.ChatMessage{
				Sender:    "server",
				Content:   fmt.Sprintf("已收到你的消息: %s", in.Content),
				Timestamp: time.Now().Unix(),
			}
			// 发送响应给客户端
			if err := stream.Send(resp); err != nil {
				log.Printf("发送消息给客户端失败: %v", err)
				return
			}
		}
	}()

	// 服务端也可以主动发送消息给客户端,这里每隔3秒发送一条心跳消息
	ticker := time.NewTicker(3 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			heartbeat := &pb.ChatMessage{
				Sender:    "server",
				Content:   "服务端心跳消息",
				Timestamp: time.Now().Unix(),
			}
			if err := stream.Send(heartbeat); err != nil {
				log.Printf("发送心跳消息失败: %v", err)
				return status.Error(codes.Aborted, "连接中断")
			}
		case <-stream.Context().Done():
			// 连接上下文结束,退出循环
			return nil
		}
	}
}

func main() {
	// 监听端口
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("监听端口失败: %v", err)
	}
	// 创建gRPC服务端
	s := grpc.NewServer()
	// 注册服务
	pb.RegisterChatServiceServer(s, &chatServer{})
	log.Printf("gRPC服务端启动,监听端口: 50051")
	// 启动服务
	if err := s.Serve(lis); err != nil {
		log.Fatalf("启动服务失败: %v", err)
	}
}

实现gRPC客户端

客户端需要调用Chat方法获取双向流对象,然后分别启动goroutine处理发送和接收消息的逻辑:

package main

import (
	"context"
	"io"
	"log"
	"time"

	pb "your_module_path/chat" // 替换为实际的模块路径
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// 连接服务端
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("连接服务端失败: %v", err)
	}
	defer conn.Close()

	// 创建客户端
	client := pb.NewChatServiceClient(conn)
	// 调用Chat方法获取双向流
	stream, err := client.Chat(context.Background())
	if err != nil {
		log.Fatalf("调用Chat方法失败: %v", err)
	}

	// 启动goroutine接收服务端消息
	go func() {
		for {
			resp, err := stream.Recv()
			if err == io.EOF {
				log.Println("服务端关闭发送流")
				return
			}
			if err != nil {
				log.Printf("接收服务端消息失败: %v", err)
				return
			}
			log.Printf("收到服务端消息: 发送者=%s, 内容=%s", resp.Sender, resp.Content)
		}
	}()

	// 客户端发送消息
	messages := []string{"你好,服务端", "这是第二条消息", "测试双向流通信"}
	for _, msg := range messages {
		sendMsg := &pb.ChatMessage{
			Sender:    "client",
			Content:   msg,
			Timestamp: time.Now().Unix(),
		}
		if err := stream.Send(sendMsg); err != nil {
			log.Fatalf("发送消息失败: %v", err)
		}
		log.Printf("客户端发送消息: %s", msg)
		time.Sleep(2 * time.Second)
	}

	// 关闭客户端的发送流
	if err := stream.CloseSend(); err != nil {
		log.Printf("关闭发送流失败: %v", err)
	}

	// 等待接收完所有消息
	time.Sleep(5 * time.Second)
}

运行测试

先启动服务端程序,再启动客户端程序,可以看到双方都能独立发送和接收消息,服务端会定时发送心跳,客户端发送的消息也会被服务端处理后返回。需要注意的是,双向流的关闭需要双方都处理对应的EOF信号,避免连接泄漏。

注意事项

  • 双向流的生命周期和gRPC连接绑定,连接断开后流也会自动关闭
  • 发送和接收操作需要做好并发控制,避免同时操作流导致的错误
  • 实际项目中需要添加更完善的错误处理和重连机制
  • 流中的消息顺序是有保障的,同一方向发送的消息会按发送顺序被接收

GolanggRPC双向流通信protobuf修改时间:2026-07-16 04:42:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。