Dart Protobuf 用法简介

简单记录下如何在 Dart 中使用 Protobuf。

参考

注意点:

安装和编译

第一步,安装 Protobuf 编译器。可以从 Protobuf release 页面下载和安装,也可以 brew install protobuf 安装。

第二步,安装 Protobuf Dart 插件。

  • 下载代码git clone https://github.com/dart-lang/protobuf.git
  • 编译插件。调用 pub install 编译插件,编译后源码 bin 目录下可以找到 proto-gen-dart 文件。如果出错,可以使用 pub --trace install 查看详细错误日志
  • 使用插件。将插件配置到 PATH 路径中,或者调用 protoc 时使用 --plugin 参数指定插件路径

第三步,运行 protoc 编译生成 .proto.dart 文件。

1
2
3
4
5
6
➜  aproj_pub_proj git:(cm) ✗ protoc --proto_path=proto --dart_out=build/gen --plugin=/Users/xxx/Documents/GitHub/protobuf/protoc_plugin/bin proto/aproj/comm_conn.proto
protoc-gen-dart: program not found or is not executable
--dart_out: protoc-gen-dart: Plugin failed with status code 1.
➜ aproj_pub_proj git:(cm) ✗ protoc --proto_path=proto --dart_out=build/gen --plugin=/Users/xxx/Documents/GitHub/protobuf/protoc_plugin/bin/protoc-gen-dart proto/aproj/comm_conn.proto
/Users/kingcmchen/Documents/GitHub/protobuf/protoc_plugin/bin/protoc-gen-dart: line 3: dart: command not found
--dart_out: protoc-gen-dart: Plugin failed with status code 127.
  • 第一次出错是因为 --plugin 指定的 Dart 插件路径不正确,应当指定具体文件而不是文件所在的目录
  • 第二次出错是因为 Dart 插件依赖 dart 命令,要确保 PATH 中有配置 dart

PATH 中配置 dart 命令方法如下:

1
export DART_PATH="$HOME/flutter/bin/cache/dart-sdk/bin"

编译成功!

-w770

发送字符串

先使用 Dart 实现简单的服务器端 SimpleServer 和客户端 SimpleClient,代码分别如下。

SimpleServer 收到客户端发送的数据,转换成大写的 UTF-8 后发回客户端,并关闭 Socket。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'dart:convert';
import 'dart:io';

main() async {
ServerSocket serverSocket =
await ServerSocket.bind(InternetAddress.anyIPv4, 6760);
print('Started');

serverSocket.listen((Socket socket) {
socket.listen((List<int> event) async {
var msg = utf8.decode(event);
print('Received $msg');
socket.write(msg.toUpperCase());

await socket.close();
});
});
}

SimpleClient 向服务器端发送 ‘hello’,并且接收和输出服务器端的响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'dart:async';
import 'dart:convert';
import 'dart:io';
main() async {
Socket socket = await Socket.connect('127.0.0.1', 6760);
print('Connected');

// listen to the received data event stream
socket.listen((List<int> event) {
print(utf8.decode(event));
});

// send hello
socket.add(utf8.encode('hello'));

// wait 5 seconds
await Future.delayed(Duration(seconds: 5));

// .. and close the socket
socket.close();
}

发送 Protobuf 数据

如何在使用 Protobuf 数据在 SimpleServerSimpleClient 之间通信?

注意,生成的 .pb.dart 文件中有如下 import

1
2
import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart' as $pb;

所以相应地需要在 pubspec.yaml 中添加对应的依赖。

第一步,为工程添加 fixnumprotobuf 依赖。添加后记得运行 flutter pb get 同步一下。

1
2
3
4
5
6
dependencies:
...

http: 0.12.0+2
fixnum: 0.10.9
protobuf: 0.13.15

第二步,将生成的 .pb.dart 文件拷贝到工程,供 SimpleServerSimpleClient 引用。

第三步,修改服务器端 SimpleServer 和客户端 SimpleClient 代码,使用 Protobuf 通信。修改后的代码分别如下:

SimpleServer 收到客户端发送的 Protobuf 数据并以 JSON 格式打印出来,然后向客户端发送 Protobuf 数据 CommRsp,最后关闭 Socket。

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
import 'dart:io';

import 'comm_conn.pb.dart';

main() async {
ServerSocket serverSocket =
await ServerSocket.bind(InternetAddress.anyIPv4, 6760);
print('Started');

serverSocket.listen((Socket socket) {
socket.listen((List<int> event) async {
var msg = CommReq.fromBuffer(event).writeToJson();

print('Received $msg');

var rsp = CommRsp.create();
rsp.cmd = 0;
rsp.result = 0;
rsp.uid = '41006';
// socket.write(writeToBuffer);
socket.add(rsp.writeToBuffer());
socket.close();

});
});
}

SimpleClient 向服务器端发送 Protobuf 数据 CommReq,并且接收和打印服务器端的响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'dart:io';

import 'comm_conn.pb.dart';

main() async {
Socket socket = await Socket.connect('127.0.0.1', 6760);
print('Connected');

// listen to the received data event stream
socket.listen((List<int> event) {
print(CommRsp.fromBuffer(event));
});

// send hello
var req = CommReq.create();
req.cmd = 0;
req.uid = 'cm';
req.ext1 = 'hello';
socket.add(req.writeToBuffer());

// .. and close the socket
await socket.close();
}

注意以下两点:

  • 注意 Protobuf 数据字段分为 required 字段和 optional 字段
  • 使用 socket.add() 发送数据而不是 socket.write()。注意 socket.write() 先对要发送的数据编码后再调用 socket.add(),实际发送的是编码后的数据
1
2
3
4
5
void write(Object obj) {
String string = '$obj';
if (string.isEmpty) return;
add(_encoding.encode(string));
}

如何在 Dart 中创建和解析 Protobuf 数据,请参考官方文档