go使用grpc实现go与go,go与C#相互调用

Posted lishuangquan1987

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go使用grpc实现go与go,go与C#相互调用相关的知识,希望对你有一定的参考价值。

示例源代码地址

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

出现如下输出,表示安装完成:

protoc-gen-go安装

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

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

安装之后,会在%USERPROFILE%\\go\\bin如下目录生成一个protoc-gen-go.exe文件:

.proto文件转换为.go文件

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

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

编写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);

项目的结构

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

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

打开集成终端:

进入到protos目录:
cd protos

输入如下指令:

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

可以看到生成了test.pb.go文件:

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

初始化module

go mod init grpctest

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

go mod tidy

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.CallResponseData: 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
	


调用:
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)
	


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
	


调用:
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("与服务器连接断开...")
	


最后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();
            
        
    


最后运行的结果如下:

以上是关于go使用grpc实现go与go,go与C#相互调用的主要内容,如果未能解决你的问题,请参考以下文章

go语言使用go-sciter创建桌面应用 事件处理,函数与方法定义,go与tiscript之间相互调用

gRPC双向数据流的交互控制(go语言实现)

Go语言入门篇-gRPC基于golang & java简单实现

[go]grpc远程接口调用实现

每天进步一点点:go基于grpc构建微服务框架-服务注册与发现

go-chassis 为 grpc-go 带来高级云原生特性