(十四)Dart WebSocket、Socket、IO、字符编码、reflection

一、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 文档。

你可能感兴趣的:((十四)Dart WebSocket、Socket、IO、字符编码、reflection)