• go使用grpc


    示例源代码地址

    https://github.com/lishuangquan1987/grpctest

    protoc下载

    protoc是protobuf的编译工具,能根据.proto文件生成为各种语言的源文件。
    protoc集成了如下语言的转换:
    在这里插入图片描述

    protoc 的下载地址:https://github.com/protocolbuffers/protobuf/releases
    在这里插入图片描述
    我是在window上开发的,所以选择protoc-21.9-win64.zip
    下载之后解压:
    在这里插入图片描述
    在bin的目录下存在protoc.exe:
    在这里插入图片描述
    protoc.exe的路径加入到系统环境变量:
    在这里插入图片描述

    验证protoc是否安装完成:
    打开控制台输入:

    protoc --version
    
    • 1

    出现如下输出,表示安装完成:
    在这里插入图片描述

    protoc-gen-go安装

    前面已经下载了protoc.exe,要想把.proto文件转换为.go源代码,必须使用protoc和插件protoc-gen-go
    protoc-gen-go的安装使用如下命令:

    go install github.com/golang/protobuf/protoc-gen-go@latest
    
    • 1

    安装之后,会在%USERPROFILE%\go\bin如下目录生成一个protoc-gen-go.exe文件:
    在这里插入图片描述

    .proto文件转换为.go文件

    proto文件的目录下,输入如下指令来生成go文件:

    protoc --go_out=plugins=grpc:. *.proto
    
    • 1

    在这里插入图片描述

    编写go语言的服务端和客户端

    本示例想实现一个客户端和服务端相互通讯聊天的例子实现如下:

    • 客户端可以任意时刻给服务端发消息
    • 服务端可以任意时刻给客户端发消息
    • 客户端发送消息只有服务端能收到
    • 服务端发送消息,所有的客户端都能收到

    定义.proto文件

    syntax="proto3";
    
    //注意这里不要写错
    option go_package="./;testpb";
    
    message CallRequest{
        string data=1;
    }
    message CallResponse{
        string data=1;
    }
    
    service TestService{
        rpc CallEachOther (stream CallRequest) returns (stream CallResponse);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    项目的结构

    在这里插入图片描述

    其中protos是client和server共用的proto文件以及生成的.proto文件。

    根据.proto文件生成.go文件

    打开集成终端:

    在这里插入图片描述

    进入到protos目录:
    cd protos
    
    • 1

    输入如下指令:

    protoc --go_out=plugins=grpc:. *.proto
    
    • 1

    可以看到生成了test.pb.go文件:
    在这里插入图片描述

    更改test.pb.go的包名为protos

    在这里插入图片描述

    初始化module

    go mod init grpctest
    
    • 1

    服务端与客户端共用一个Module,分别在两个不同的文件有两个main.go文件作为各自的入口
    因为前面已经生成了test.pb.go源码文件,只需要如下命令即可自动下载grpc通讯所需要的包:

    go mod tidy
    
    • 1

    go 服务端开发

    定义服务端的testservice

    testservice主要是实现grpc接口中的方法和启动grpc服务。
    grpctest/test_server/test_service/test_service.go的代码如下:

    package testservice
    
    import (
    	"fmt"
    	uuid "github.com/satori/go.uuid"
    	"google.golang.org/grpc"
    	proto "grpctest/protos"
    	"net"
    	"sync"
    )
    
    var Service *TestService
    
    type ClientInfo struct {
    	UUID       string
    	chWrite    chan string
    	chRead     chan string
    	chWriteErr chan struct{}
    	chReadErr  chan struct{}
    	chFinish   chan struct{}
    }
    
    type TestService struct {
    	mu      sync.Mutex
    	Clients map[string]*ClientInfo
    }
    
    func (s *TestService) CallEachOther(t proto.TestService_CallEachOtherServer) error {
    	//加入Client
    	client := &ClientInfo{
    		UUID:       uuid.NewV4().String(),
    		chWrite:    make(chan string),
    		chRead:     make(chan string),
    		chWriteErr: make(chan struct{}),
    		chReadErr:  make(chan struct{}),
    		chFinish:   make(chan struct{}, 2),
    	}
    	s.Clients[client.UUID] = client
    	fmt.Printf("客户端:%s已连接\n", client.UUID)
    	//写
    	go func() {
    		for {
    			select {
    			case str, ok := <-client.chWrite:
    				if !ok {
    					client.chFinish <- struct{}{}
    					client.chWriteErr <- struct{}{}
    					return //通道关闭,调用完成
    				}
    				err := t.Send(&proto.CallResponse{Data: str})
    				if err != nil {
    					client.chWriteErr <- struct{}{}
    					client.chFinish <- struct{}{}
    					return
    				}
    			case <-client.chReadErr: //读错误时,写要停止
    				return
    			}
    		}
    	}()
    	go func() {
    		for {
    			r, err := t.Recv()
    			if err != nil {
    				client.chReadErr <- struct{}{}
    				client.chFinish <- struct{}{}
    				return
    			}
    			fmt.Printf("[%s-接收]:%s\n", client.UUID, r.Data)
    		}
    	}()
    	select {
    	case <-client.chFinish:
    		delete(s.Clients, client.UUID)
    		fmt.Printf("客户端:%s已断开\n", client.UUID)
    		return nil
    	}
    }
    
    func Send(msg string) {
    	for _, v := range Service.Clients {
    		v.chWrite <- msg
    	}
    	fmt.Printf("已向%d个客户端发送了消息:%s", len(Service.Clients), msg)
    }
    
    func StartService() {
    	lis, err := net.Listen("tcp", "0.0.0.0:9091")
    	if err != nil {
    		fmt.Printf("listen error:%v\n", err)
    		return
    	}
    	s := grpc.NewServer()
    	Service = &TestService{
    		Clients: map[string]*ClientInfo{},
    	}
    	proto.RegisterTestServiceServer(s, Service)
    	if err := s.Serve(lis); err != nil {
    		fmt.Printf("serve error:%v\n", err)
    		return
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    调用:
    grpctest/test_server/main.go:

    package main
    
    import (
    	"fmt"
    	testservice "grpctest/test_server/test_service"
    )
    
    func main() {
    	fmt.Println("启动...")
    	go testservice.StartService()
    
    	for {
    		fmt.Println("请输入要发送的字符串:")
    		var str string
    		fmt.Scanln(&str)
    		testservice.Send(str)
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    go客户端开发

    定义客户端的test_service

    grpctest/test_client/test_service/test_service.go:

    package testservice
    
    import (
    	"fmt"
    	"golang.org/x/net/context"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    	proto "grpctest/protos"
    )
    
    func NewClientService() *ClientService {
    	return &ClientService{
    		chFinish:   make(chan struct{}, 2),
    		chWriteErr: make(chan struct{}, 1),
    		chReadErr:  make(chan struct{}, 1),
    	}
    }
    
    type ClientService struct {
    	chFinish   chan struct{}
    	chReadErr  chan struct{}
    	chWriteErr chan struct{}
    }
    
    func (s *ClientService) StartService() {
    	conn, err := grpc.Dial("0.0.0.0:9091", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
    	if err != nil {
    		fmt.Printf("dial error:%v\n", err)
    		return
    	}
    	defer conn.Close()
    	c := proto.NewTestServiceClient(conn)
    	callClient, err := c.CallEachOther(context.Background())
    	if err != nil {
    		fmt.Printf("call CallEachOther fail:%v\n", err)
    		return
    	}
    	//读
    	go func() {
    		for {
    			select {
    			case <-s.chWriteErr:
    				return
    			default:
    				break
    			}
    			data, err := callClient.Recv()
    			if err != nil {
    				fmt.Printf("recv error :%v\n", err)
    				s.chReadErr <- struct{}{}
    				s.chFinish <- struct{}{}
    				return
    			}
    			fmt.Printf("[接受]:%s\n", data.Data)
    		}
    	}()
    	//写
    	go func() {
    		for {
    			select {
    			case <-s.chReadErr:
    				return
    			default:
    				break
    			}
    			fmt.Println("请输入要发送的字符串:")
    			var str string
    			fmt.Scanln(&str)
    			err := callClient.Send(&proto.CallRequest{
    				Data: str,
    			})
    			if err != nil {
    				s.chWriteErr <- struct{}{}
    				s.chFinish <- struct{}{}
    				return
    			}
    		}
    	}()
    	select {
    	case <-s.chFinish:
    		break
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    调用:
    grpctest/test_client/main.go:

    package main
    
    import (
    	"fmt"
    	testservice "grpctest/test_client/test_service"
    )
    
    func main() {
    	for {
    		fmt.Println("开始连接服务器...")
    		s := testservice.NewClientService()
    		s.StartService()
    		fmt.Println("与服务器连接断开...")
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    最后go server 和 go client通讯的界面如下:
    在这里插入图片描述

    c# client

    安装所需要的grpc包
    • Google.Protobuf
    • Grpc.Core
    • Grpc.Tools
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    根据.proto文件生成.cs文件

    安装Grpc.Tools之后,会在packages目录下生成Grpc.Tools.2.50.0目录,打开tools/windows_x64目录,可以看到有grpc_csharp_plugin.exeprotoc.exe文件:
    在这里插入图片描述

    • 拷贝test.protopackages\Grpc.Tools.2.50.0\tools\windows_x64(grpc_charp_plugins.exe)所在的目录,在protos目录打开终端,输入如下命令生成.cs文件
      protoc -I . --csharp_out . --grpc_out . --plugin=protoc-gen-grpc=grpc_csharp_plugin.exe *.proto
      在这里插入图片描述

    在这里插入图片描述

    • 将生成的文件Test.csTestGrpc.cs拷贝到项目中:
      在这里插入图片描述
    编写客户端代码
    using Grpc.Core;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace grpctest_csharp
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Console.Title="C# gRPC客户端";
                var channel = new Channel("127.0.0.1:9091",ChannelCredentials.Insecure);
                var client=new TestService.TestServiceClient(channel);
                var callEachotherContext = client.CallEachOther();
                Task.Factory.StartNew(async () =>
                {
                    while (await callEachotherContext.ResponseStream.MoveNext())
                    {
                        Console.WriteLine("接收:{0}", callEachotherContext.ResponseStream.Current.Data);
                    }
                });
                while (true)
                {
                    Console.WriteLine("请输入要发送的内容");
                    var str = Console.ReadLine();
                    callEachotherContext.RequestStream.WriteAsync(new CallRequest() { Data = str }).Wait();
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    最后运行的结果如下:
    在这里插入图片描述

  • 相关阅读:
    架构师系列---RPC通信原理
    3_使用传统CNN网络训练图像分类模型
    实验四:面向对象编程实验(2)—封装、继承和包
    QImageReader
    springboot毕设项目宠物医院信息管理36518(java+VUE+Mybatis+Maven+Mysql)
    第一:Python基于钉钉监控发送消息提醒
    Windows VS C++工程:包含目录、库目录、附加依赖项、附加包含目录、附加库目录配置与静态库、动态库的调用——以OCCI的配置为例
    西米支付”:在游戏SDK中,提供了哪些支付渠道?SDK的用处?
    FDTD script command(结构)
    国内率先自主数字源表及IGBT测试系统亮相半导体分立器件年会
  • 原文地址:https://blog.csdn.net/lishuangquan1987/article/details/127964715