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.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
	}
}

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

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值