一、Sending and receiving real-time data with WebSocket(用WebSocket发送和接收实时数据)
WebSocket类在dart:io
中, 可以让你的 web 应用和服务器持续的交互数据,不用 一直的轮询。创建 WebSocket 的服务器会监听 ws://
开头的 URL, 例如 ws://127.0.0.1:1337/ws。 通过 WebSocket 发送的数据可以是字符串或者 blob。 通常都是使用 JSON 格式的字符串。
要在 web 应用中使用 WebSocket,需要先创建 WebSocket 对象,把 WebSocket URL 作为该对象的参数。
import 'dart:async';
import 'dart:io';
void webSocketDemo() async {
// @Deprecated('This constructor will be removed in Dart 2.0. Use `implements`'
// ' instead of `extends` if implementing this abstract class.')
// var ws = new WebSocket('ws://127.0.0.1');
var socket = await Socket.connect('ws://127.0.0.1', 4001);
var ws = new WebSocket.fromUpgradedSocket(socket);
// 不用的时候一定记得关闭
// ws.close();
}
1.1.Sending data(发送数据)
使用 add()
函数向 WebSocket 发送数据:
/*
static const int connecting = 0;
static const int open = 1;
static const int closing = 2;
static const int closed = 3;
*/
// int get readyState; 返回当前连接状态
if(ws.readyState == WebSocket.open){ // 连接状态
/**
* Sends data on the WebSocket connection. The data in [data] must
* be either a `String`, or a `List` holding bytes.
*/
// 客户端发送消息
ws.add('Hello from Dart!');
}
1.2.Receiving data(接收数据)
要从 WebSocket 接收数据,需要注册一个事件 监听器:
// 客户端接收消息
ws.listen((onData){
print('客户端收到消息!');
print(onData);
});
1.3.Handling WebSocket events(处理WebSocket事件)
你的应用可以处理如下的 WebSocket 事件:onError
, onDone
, cancelOnError
, 和 (前面演示的) onData
。下面是演示各种事件的示例:
// 错误处理
ws.listen((onError){
print(onError);
});
// 完成后处理
ws.listen((onDone){
print(onDone);
});
// 如果[cancelOnError]为:true,当提交第一个错误事件时,订阅将自动取消
// cancelOnError默认值为:false.
ws.listen((cancelOnError){
print(cancelOnError);
});
也可另一种方式监听及处理事件:
ws.listen(dataHandler,
onError: errorHandler,
onDone: doneHandler,
cancelOnError: false);
void dataHandler(data){
print('received');
// var a = String.fromCharCodes(value);
var b = utf8.decode(data);
print(b);
}
void errorHandler(error, StackTrace trace){
print(error);
}
void doneHandler(){
...
}
二、Socket
Socket和WebSocket类似,这里简单介绍一下好了。。。
import 'dart:async';
import 'dart:io';
import 'dart:convert';
Socket socket;
void socketDemo() async {
var sock = await Socket.connect('ws://127.0.0.1', 4001).then((Socket s) {
socket = s;
socket.listen(dataHandler,
onError: errorHandler,
onDone: doneHandler,
cancelOnError: false);
}).catchError((AsyncError e) {
print("Unable to connect: $e");
});
//Connect standard in to the socket
// stdin.listen((data) => socket.write(new String.fromCharCodes(data).trim() + '\n'));
// 发送消息
socket.write('Hello Socket !\n');
}
void dataHandler(data){
print('received');
// var a = String.fromCharCodes(value);
var b = utf8.decode(data);
print(b);
}
void errorHandler(error, StackTrace trace){
print(error);
}
void doneHandler(){
/**
* Destroy the socket in both directions. Calling [destroy] will make the
* send a close event on the stream and will no longer react on data being
* piped to it.
*
* Call [close](inherited from [IOSink]) to only close the [Socket]
* for sending data.
*/
socket.destroy();// 不用的时候一定记得调用
}
除了上面提到的几个功能外,dart:io 库还包含 processes, sockets, 和 web sockets 等相关的 API。
三、I/O for command-line apps(命令行应用I/O)
dart:io 库 提供了一些和 文件、目录、进程、sockets、 WebSockets、和 HTTP 客户端以及服务器的 API。 只有命令行应用可以使用 dart:io
库,web app 无法使用。
一般而言,dart:io
库实现和提供的是异步 API。 同步函数很容易阻塞应用,后期扩展起来非常麻烦。 因此,大部分的操作返回值都是 Future
或者 Stream
对象, 如果你熟悉 Node.js 则对这种模式会有所了解。
dart:io
里面也有一小部分同步方法,这些方法都使用 sync
前缀命名方法名字。 这里就不再介绍这些同步方法了。
注意: 只有命令行应用才能导入
dart:io
。
3.1.Files and directories(文件和目录)
I/O 库可以让命令行应用读写文件和查看目录。 读取文件有两种方式:一次读完或者通过流的方式来读取。 一次读完需要把文件内容读到内存中,如果文件 非常大或者你希望一边读文件一边处理,则应该 使用 Stream, 在 流式读取文件中介绍。
3.1.1.Reading a file as text(以文本形式读取文件)
对于编码为 UTF-8 的文本,可以使用函数 readAsString()
一次性 的读取整个文本。如果单行文字比较重要,则可以 使用 readAsLines()
来读取。 这两个函数返回一个 Future 对象,当文件 读取完的时候,可以从 Future 对象获取一个或者多个字符串。
import 'dart:io';
main() async {
var config = new File('config.txt');
var contents;
// Put the whole file in a single string.
contents = await config.readAsString();
print('The entire file is ${contents.length} characters long.');
// Put each line of the file into its own string.
contents = await config.readAsLines();
print('The entire file is ${contents.length} lines long.');
}
3.1.2.Reading a file as binary(读取二进制文件)
下面的示例把文件数据读取为字节流。 同样 readAsBytes()
函数返回值为 Future, 当读完文件后,可以从 Future 中获取数据。
import 'dart:io';
main() async {
var config = new File('config.txt');
var contents = await config.readAsBytes();
print('The entire file is ${contents.length} bytes long');
}
3.1.3.Handling errors(错误处理)
在 Future 上注册一个 catchError
来处理异常, 还可以在 async
方法中使用 try
-catch
来 处理异常:
import 'dart:io';
main() async {
var config = new File('config.txt');
try {
var contents = await config.readAsString();
print(contents);
} catch (e) {
print(e);
}
}
3.1.4.Streaming file contents(流媒体文件内容)
使用 Stream 读取文件的时候, 使用 Stream API 或者 await for
可以一点点的读取, 详情参考 异步支持。
import 'dart:io';
import 'dart:convert';
import 'dart:async';
main() async {
var config = new File('config.txt');
Stream> inputStream = config.openRead();
var lines = inputStream
.transform(UTF8.decoder)
.transform(new LineSplitter());
try {
await for (var line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
}
3.1.5.Writing file contents(文件写内容)
- 使用 IOSink 可以往文件 写入内容。使用 File 的
openWrite()
函数获取到一个 IOSink。 默认的写模式为FileMode.WRITE
,新写入的数据会完全覆盖 文件之前的内容。
var logFile = new File('log.txt');
var sink = logFile.openWrite();
sink.write('FILE ACCESSED ${new DateTime.now()}\n');
sink.close();
- 如果想在文件末尾追加内容,则可以使用
mode
可选参数,参数取值 为FileMode.APPEND
:
var sink = logFile.openWrite(mode: FileMode.APPEND);
- 使用
add(List
函数可以写二进制数据到文件。data)
3.1.6.Listing files in a directory(列出目录中的文件)
查找目录中的所有文件和子目录是一个异步操作。 list()
函数返回一个 Stream,当遇到文件或者子目录的时候, Stream 就发射一个对象。
import 'dart:io';
main() async {
var dir = new Directory('/tmp');
try {
var dirList = dir.list();
await for (FileSystemEntity f in dirList) {
if (f is File) {
print('Found file ${f.path}');
} else if (f is Directory) {
print('Found dir ${f.path}');
}
}
} catch (e) {
print(e.toString());
}
}
3.1.7.Other common functionality(其他常见功能)
File 和 Directory 类包含其他的一些文件操作, 下面只是一些常见的函数:
创建文件或者目录:
create()
in File and Directory删除文件或者目录:
delete()
in File and Directory获取文件的长度:
length()
in File随机位置访问文件:
open()
in File
参考 File 和 Directory 的 API 文档来 查看所有的函数。
四、Decoding and encoding UTF-8 characters(解码和编码UTF-8字符)
- 使用
utf8.decode()
来解码 UTF8-encoded 字节流为 Dart 字符串:
import 'dart:convert' show utf8;
main() {
var string = utf8.decode([
0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9,
0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3,
0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4,
0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5,
0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1
]);
print(string); // 'Îñţérñåţîöñåļîžåţîờñ'
}
- 如果是 stream 字节流则可以在 Stream 的
transform()
函数上指定utf8.decoder
:
var lines = inputStream
.transform(utf8.decoder)
.transform(new LineSplitter());
try {
await for (var line in lines) {
print('Got ${line.length} characters from stream');
}
- 使用
utf8.encode()
把字符串编码为 utf8 字节流:
import 'dart:convert' show utf8;
main() {
List expected = [
0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9,
0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3,
0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4,
0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5,
0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1
];
List encoded = utf8.encode('Îñţérñåţîöñåļîžåţîờñ');
assert(() {
if (encoded.length != expected.length) return false;
for (int i = 0; i < encoded.length; i++) {
if (encoded[i] != expected[i]) return false;
}
return true;
});
}
五、reflection(反射)
dart:mirrors
库提供了基本的反射支持。 使用 mirror 来查询程序的结构,也可以 在运行时动态的调用方法或者函数。
dart:mirrors
库在命令行和 web 应用中均可使用。 导入 dart:mirrors
即可开始使用。
警告: 使用
dart:mirrors
可能会导致 dart2js 生成的 JavaScript 代码 文件非常大!
目前的解决方式是在导入dart:mirrors
之前添加一个@MirrorsUsed
注解。 详情请参考 MirrorsUsedAPI 文档。由于dart:mirrors
库依然还在 开发中,所以这个解决方案以后很有可能发生变化。
5.1.Symbols(符号)
mirror 系统使用 Symbol 类对象 来表达定义的 Dart 标识符名字。 Symbols 在混淆后的代码也可以 使用。
如果在写代码的时候,已经知道 symbol 的名字了,则可以使用 #符号名字 的方式直接使用。 直接使用的 symbol 对象是编译时常量,多次定义引用的是同一个对象。 如果名字不知道,则可以通过 Symbol 构造函数来 创建:
import 'dart:mirrors';
// If the symbol name is known at compile time.
const className = #MyClass;
// If the symbol name is dynamically determined.
var userInput = askUserForNameOfFunction();
var functionName = new Symbol(userInput);
在混淆代码的时候,编译器可能使用更加简短的名字来替代原来的符号(symbol)名字。 要获取原来的 symbol 名字,使用 MirrorSystem.getName()
函数。该函数 在代码混淆的情况下,也能返回正确的 symbol 名字。
import 'dart:mirrors';
const className = #MyClass;
assert('MyClass' == MirrorSystem.getName(className));
5.2.Introspection(检查)
使用 mirror 功能来检查程序的结构。可以检查 类、库以及对象等。
下面的示例使用 Person 类:
class Person {
String firstName;
String lastName;
int age;
Person(this.firstName, this.lastName, this.age);
String get fullName => '$firstName $lastName';
void greet(String other) {
print('Hello there, $other!');
}
}
在开始使用之前,需要在一个类或者对象上调用 reflect
函数来获取 到 mirror。
5.2.1.Class mirrors(类反射)
在任何 Type 上面调用 reflect
函数来获取 ClassMirror :
ClassMirror mirror = reflectClass(Person);
assert('Person' ==
MirrorSystem.getName(mirror.simpleName));
也可以在实例上调用 runtimeType 获取该对象的 Type。
var person = new Person('Bob', 'Smith', 33);
ClassMirror mirror = reflectClass(person.runtimeType);
assert('Person' ==
MirrorSystem.getName(mirror.simpleName));
获取到 ClassMirror 后,就可以查询类的构造函数、成员变量、等信息。 下面是列出类的所有构造函数的示例:
showConstructors(ClassMirror mirror) {
var constructors = mirror.declarations.values
.where((m) => m is MethodMirror && m.isConstructor);
constructors.forEach((m) {
print('The constructor ${m.simpleName} has '
'${m.parameters.length} parameters.');
});
}
下面是列出类的成员变量的示例:
showFields(ClassMirror mirror) {
var fields = mirror.declarations.values
.where((m) => m is VariableMirror);
fields.forEach((VariableMirror m) {
var finalStatus = m.isFinal ? 'final' : 'not final';
var privateStatus = m.isPrivate ?
'private' : 'not private';
var typeAnnotation = m.type.simpleName;
print('The field ${m.simpleName} is $privateStatus ' +
'and $finalStatus and is annotated as ' +
'$typeAnnotation.');
});
}
详情请参考 ClassMirror 的 API 文档。
5.2.2.Instance mirrors (实例反射)
在对象上调用 reflect 函数可以获取到一个 InstanceMirror 对象。
var p = new Person('Bob', 'Smith', 42);
InstanceMirror mirror = reflect(p);
如果你已经有个 InstanceMirror 对象了,但是想知道该对象反射的目标对象,则需要 调用 reflectee
。
var person = mirror.reflectee;
assert(identical(p, person));
5.3.Invocation(调度)
获取到 InstanceMirror 后,就可以调用里面的函数、getter和setter了。 详细信息请参考 API docs for InstanceMirror 的 API 文档。
5.3.1.Invoke methods(方法调度)
使用 InstanceMirror 的 invoke()
函数来调用对象的函数。 第一个参数为要调用的函数名字,第二个参数为该函数的 一个位置参数列表。第三个参数为可选参数,用来指定命名 参数。
var p = new Person('Bob', 'Smith', 42);
InstanceMirror mirror = reflect(p);
mirror.invoke(#greet, ['Shailen']);
5.3.2.Invoke getters and setters(getter、setter调度)
使用 InstanceMirror 的 getField()
和 setField()
函数来 查询和设置对象的属性。
var p = new Person('Bob', 'Smith', 42);
InstanceMirror mirror = reflect(p);
// Get the value of a property.
var fullName = mirror.getField(#fullName).reflectee;
assert(fullName == 'Bob Smith');
// Set the value of a property.
mirror.setField(#firstName, 'Mary');
assert(p.firstName == 'Mary');
文章 Reflection in Dart with Mirrors 更详细 的介绍了反射功能。另外也可以参考 dart:mirror, 特别是 MirrorsUsed, ClassMirror, 和 InstanceMirror 这些类的 API 文档。