今天本来想用dart做一些native的小工具,但是准备开始写的时候才发现dart的生态的确还欠缺很多,dart连获取自身运行时内存信息的方法都没有,别提执行shell或其他监控了。那么,如果dart自身不行,能不能通过已有的工具组合起来为dart提供服务呢?毕竟dart的 isolate 自动释放内存和安全的内存隔离是一个亮点,而且还能通过主线程控制其他的isolate空间,等于一个 FPM,如果因为生态而无法使用,就有点遗憾了。为了能给dart增加能力,想到了官方提供的dart grpc包,就有了今天的 golang + dart grpc入门了。
我打算用 golang 创建 grpc server ,用 dart 作为 client 进行通信,因为golang的生态和各方面都比较优秀,只有在内存上,对堆内存的释放和我的预期有差距。
PS: 假设我们在golang中需要利用不同的协程进行数据筛选和处理,然后通过chan汇总到main,那么内存的消耗可能会超出我们的预期,这个时候,我们就需要像dart这样,当isolate执行完没有引用后,立即释放其内存,节约宝贵的系统资源。当然,这并不是说golang不适用,golang在内存复用上,可以减少很多内存开辟和释放的开销,只是应用的场景不同而已。
很久没有搞golang了,这里记录一下开发过程和参考资料。
Golang 端:
- 下载 protoc 编译工具,https://github.com/google/protobuf/releases
- 开启protobuf-golang 代码生成器插件
go get -u github.com/golang/protobuf/protoc-gen-go
- 配置 command PATH
export PATH=$PATH:$GOPATH/bin
- 创建 Golang 项目,这里我直接用 Goland IDE 创建,创建在 GOPATH 之外,使用了 go mod 作为单独的包管理工具,并且创建 vendor 目录。
mkdir grpc_server //创建项目目录
cd grpc_server
mkdir src //创建源码目录
cd src //进入源码目录
go mod init grpc_server
ls -al . //你会看到有一个Mod文件表示 mod 命令执行成功
touch main.go //创建项目主执行文件
- 约定目录结构
/
/proto/ //proto 定义文件目录
/main.go
- 编写 protobuf 文件,定义 service 和 message. 语法参考:https://colobu.com/2015/01/07/Protobuf-language-guide/#%E5%8C%85%EF%BC%88Package%EF%BC%89\
文件:/proto/site_info.proto
syntax = "proto3"; //protobuf 语法版本,2 和 3的版本有差异,要注意和客户端用一致的
package siteinfo; //定义生成代码的包名
//定义一个 grpc 服务,每个服务可以看成是一个API 微服务,微服务下有很多相应的接口
service SiteInfo {
//定义RPC方法,此方法接收一个 SearchById 结构的消息,返回一个 User 结构的消息
rpc GetUserInfoById(SearchById) returns (User) {}
}
//定义消息结构细节
message SearchById{
//定义消息体中的参数 userId 为 int32 类型,在消息体中顺序为第一位
int32 userId = 1;
}
message User{
string id = 1;
string name = 2;
string phone = 3;
string status = 4;
}
- 生成 golang protobuf 代码
path: grpc_server/src/
//注意 --go_out 参数的 plugins=grpc:. 如不加这个也可以执行成功,
//但是生成的文件会缺少 grpc 方法,例如 定义的 GetUserInfoById 方法会缺失
protoc -I $PWD/ proto/site_info.proto --go_out=plugins=grpc:.
- 实现 protoc 工具生成代码中的 SiteInfoServer 接口,也就是我们再 proto 文件中定义的服务和方法,覆盖生成方法实现具体的业务逻辑。
path: main.go
type Server struct {}
func (s *Server) GetUserInfoById(ctx context.Context, in *pb.SearchById) (*pb.User, error){
//Mysql 查询
return &pb.User{
Id: "1",
Name: "User",
Phone: "1376776",
},nil
}
- 创建 grpc 服务,绑定 tcp 端口监听,将生成的 服务注册到 grpc server中
path: main.go
func main(){
listen, err := net.Listen("tcp",Port)
if err != nil {
panic(err)
}
s := grpc.NewServer()
pb.RegisterSiteInfoServer(s,&Server{})
s.Serve(listen)
}
- 这里注意一下因为用了 go mod ,还需要同步import包, 创建go项目独立的 vendor 目录
go mod vendor
- 启动服务, server 端完成
import (
"context"
"net"
grpc "google.golang.org/grpc"
pb "grpc_server/proto"
)
func main(){
listen, err := net.Listen("tcp",Port)
if err != nil {
panic(err)
}
s := grpc.NewServer()
pb.RegisterSiteInfoServer(s,&Server{})
s.Serve(listen)
}
const (
Port = ":3389"
)
type Server struct {}
func (s *Server) GetUserInfoById(ctx context.Context, in *pb.SearchById) (*pb.User, error){
//Mysql 查询
return &pb.User{
Id: "1",
Name: "User",
Phone: "1376776",
},nil
}
Dart 端:
- 开启protobuf-dart 代码生成器插件
pub global activate protoc_plugin
- 配置 PATH
export PATH=$PATH:$HOME/.pub-cache/bin
- 创建 Dart console command 项目,项目名称: grpc_client ,这里我用的是 idea ,直接选择就会创建默认目录结构
//约定proto 文件目录在创建的项目根目录下的 proto 目录中
mkdir /grpc_client/proto/
- 将 golang 项目中的 site_info.proto 文件复制到 dart 项目的 proto目录
- 生成dart 端 grpc 代码
cd grpc_client
-I后紧接着是 proto 文件所在的目录,路径是相对当前执行命令目录的,目录后面有空格
--dart_out=grpc:[dir_path] 这里的dir_path 也是相对当前目录的路径
protoc --dart_out=grpc:proto -Iproto/ site_info.proto
- 注册项目依赖包
/grpc_client/pubspec.yml:
name: grpc_client
description: A sample command-line application.
# version: 1.0.0
# homepage: https://www.example.com
environment:
sdk: '>=2.7.0 <3.0.0'
dependencies:
grpc: ^2.1.3
protobuf: ^1.0.1
protoc_plugin: 19.0.1
dev_dependencies:
pedantic: ^1.8.0
test: ^1.6.0
- 在 mian.dart 中调用RPC
import '../proto/site_info.pb.dart';
import '../proto/site_info.pbgrpc.dart';
import 'package:grpc/grpc.dart';
Future<Null> main(List<String> arguments) async{
final channel = new ClientChannel('localhost',
port:3389,
options: const ChannelOptions(credentials: ChannelCredentials.insecure())
);
final stub = SiteInfoClient(channel);
final arg = 3;
try{
final res = await stub.getUserInfoById(SearchById()..userId = arg);
print(res.toString());
print(res.runtimeType);
}catch(e){
print(e);
}
// await channel.shutdown();
}
到这里就ok了,dart 和 golang 的grpc 交互成功,后续可以根据每个语言的特点来进行服务划分,通过grpc来跨语言调用。