writing/reading 字节数 to/from Dart 中的套接字数据

writing/reading number of bytes to/from socket data in Dart

在下面的 server/client 套接字代码中,服务器尝试发送从 0 到 1023 的整数。客户端接收到正确数量的整数,但它们在 255 处换行。这是为什么以及正确的方法是什么?

* 服务器 *

import 'dart:async';
import 'dart:io';

main() async {
  final server = await ServerSocket.bind('127.0.0.1', 4041);
  server.listen((Socket socket) {
    print('Got connected ${socket.remoteAddress}');
    for(int i=0; i<1024; i++) {
      socket.add([i]);
    }
    socket.close();
    print('Closed ${socket.remoteAddress}');
  });
}

* 客户端 *

import 'dart:async';
import 'dart:io';

main() async {
  final client = await Socket.connect('127.0.0.1', 4041);
  client.listen(
      (var data) => print('Got $data'),
      onDone: () { print('Done'); client.close(); },
      onError: (e) { print('Got error $e'); client.close(); });
  print('main done');
}

此外,感觉 Socket api 有点不同 - 在支持流式读取方面比其他语言更 Dartish。但是,如果您只想读取 N 字节怎么办?你如何做到这一点?我看到 take 方法 (Stream take(int count)) 但我不清楚 count 正在占用,特别是在读取了多少字节方面。因为 Socket 实现了 Stream< List < int > > 并且因为 int 并不是真正固定大小的实体(我想??),你怎么知道已经读取了多少字节?

这对我有用,我不确定这是最优雅的解决方案

import 'dart:io';
import 'dart:async';
import 'dart:typed_data';

main() async {
  final server = await ServerSocket.bind('127.0.0.1', 4041);
  server.listen((Socket socket) {
    print('Got connected ${socket.remoteAddress}');

    var toByte = new StreamTransformer<List<int>, Uint8List>.fromHandlers(
        handleData: (data, sink) {
      sink.add(new Uint64List.fromList(data).buffer.asUint8List());
    });

    var streamController = new StreamController<List<int>>();
    streamController.stream.transform(toByte).pipe(socket);

    for (int i = 0; i < 1024; i++) {
      streamController.add([i]);
    }
    streamController.close();
    print('Closed ${socket.remoteAddress}');
  });
}
import 'dart:io';
import 'dart:async';

main() async {
  final Socket client = await Socket.connect('127.0.0.1', 4041);

  var fromByte = new StreamTransformer<List<int>, List<int>>.fromHandlers(
      handleData: (data, sink) {
    sink.add(data.buffer.asInt64List());
  });

  client.transform(fromByte).listen((e) => e.forEach(print));
  print('main done');
}

原尝试:

import 'dart:io';
import 'dart:typed_data';

main() async {
  final server = await ServerSocket.bind('127.0.0.1', 4041);
  server.listen((Socket socket) {
    print('Got connected ${socket.remoteAddress}');
    for(int i=0; i<1024; i++) {
      socket.add(new Uint64List.fromList([i]).buffer.asUint8List());
    }
    socket.close();
    print('Closed ${socket.remoteAddress}');
  });
}
import 'dart:io';
import 'dart:typed_data';

main() async {
  final Socket client = await Socket.connect('127.0.0.1', 4041);
  client.listen(
      (var data) {
        var ints = new Uint8List.fromList(data).buffer.asInt64List();
        ints.forEach((i) => print('Got $i'));
      },
      onDone: () { print('Done'); client.close(); },
      onError: (e) { print('Got error $e'); client.close(); });
  print('main done');
}

如果您不需要 Int64 的范围,您也可以使用 Int32、Int16 或某些 UIntXX 类型或任何最适合您的值范围的类型,以节省一些冗余传输的 0 字节。

我怀疑使用此处解释的转换器 https://www.dartlang.org/articles/converters-and-codecs/ could be used to do it more elegantly. This seems similar https://gist.github.com/xxgreg/9104926 但我不得不承认我无法将这些示例应用于您的用例。

更新

我按照上面链接的文章中显示的那样工作

import 'dart:convert';
import 'dart:typed_data';

class IntConverter extends Converter<List<int>, List<int>> {
  const IntConverter();

  List<int> convert(List<int> data) {
    if (data is Uint8List) {
      return data.buffer.asInt64List();
    } else {
      return new Uint64List.fromList(data).buffer.asUint8List();
    }
  }

  IntSink startChunkedConversion(sink) {
    return new IntSink(sink);
  }
}

class IntSink extends ChunkedConversionSink<List<int>> {
  final _converter;
  // fales when this type is used
  // final ChunkedConversionSink<List<int>> _outSink;
  final _outSink;

  IntSink(this._outSink) : _converter = new IntConverter();

  void add(List<int> data) {
    _outSink.add(_converter.convert(data));
  }

  void close() {
    _outSink.close();
  }
}
import 'dart:io';
import 'dart:typed_data';

main() async {
  final server = await ServerSocket.bind('127.0.0.1', 4041);
  server.listen((Socket socket) {
    print('Got connected ${socket.remoteAddress}');

    for (int i = 0; i < 1024; i++) {
      socket.add(new Uint64List.fromList([i]).buffer.asUint8List());
    }
    socket.close();
    print('Closed ${socket.remoteAddress}');
  });
}
import 'dart:io';
import 'byte_converter.dart';

main() async {
  final Socket client = await Socket.connect('127.0.0.1', 4041);
  client.transform(new IntConverter()).listen((e) => e.forEach(print));
  print('main done');
}

需要明确的一件事是,尽管 dart:io 中的大多数方法都在 List<int> 上运行,但 Dart 中所有 IO 的 基本假设 是这些列表是字节值的列表。文档可能不太准确。

这意味着您可以在发送字节时使用您喜欢的任何 List 实现 - 包括类型化数据,例如

var socket = ...
socket.add([1, 2, 3]);
socket.add(new List<int>.generate(3, (int index) => index * index);
socket.add('ASCII'.codeUnits);
socket.add(UTF8.encode('Søren'));

var typedData = new Uint8List(8);
var data = new ByteData.view(typedData.buffer);
data.setInt32(0, 256 * 256 - 1);
data.setInt32(4, 256 * 256 - 1, Endianness.LITTLE_ENDIAN);
socket.add(typedData)

从套接字接收数据时,Socket 流上传送的数据是从 OS 传送的字节块。您无法预测当时收到了多少字节。如果您希望能够读取特定数量的字节,一种选择是实现一个字节 reader,它具有如下方法:

Future<List<int>> read(int numBytes);

这个字节 reader 然后可以用 List<int> 的流初始化并处理帧。你可以做的是有一个缓冲区(可能使用 dart:io 中的 BytesBuilder),它在收到数据时收集数据。当 read 被调用时, Future 当有足够的字节可用时完成。这样一个字节reader也可以在缓冲数据量达到一定限制时暂停流,并在读取数据时再次恢复。