| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| import 'dart:isolate'; |
| |
| import 'package:stream_channel/isolate_channel.dart'; |
| import 'package:stream_channel/stream_channel.dart'; |
| |
| Future<void> main() async { |
| // A StreamChannel<T>, is in simplest terms, a wrapper around a Stream<T> and |
| // a StreamSink<T>. For example, you can create a channel that wraps standard |
| // IO: |
| var stdioChannel = StreamChannel(stdin, stdout); |
| stdioChannel.sink.add('Hello!\n'.codeUnits); |
| |
| // Like a Stream<T> can be transformed with a StreamTransformer<T>, a |
| // StreamChannel<T> can be transformed with a StreamChannelTransformer<T>. |
| // For example, we can handle standard input as strings: |
| var stringChannel = stdioChannel |
| .transform(StreamChannelTransformer.fromCodec(utf8)) |
| .transformStream(const LineSplitter()); |
| stringChannel.sink.add('world!\n'); |
| |
| // You can implement StreamChannel<T> by extending StreamChannelMixin<T>, but |
| // it's much easier to use a StreamChannelController<T>. A controller has two |
| // StreamChannel<T> members: `local` and `foreign`. The creator of a |
| // controller should work with the `local` channel, while the recipient should |
| // work with the `foreign` channel, and usually will not have direct access to |
| // the underlying controller. |
| var ctrl = StreamChannelController<String>(); |
| ctrl.local.stream.listen((event) { |
| // Do something useful here... |
| }); |
| |
| // You can also pipe events from one channel to another. |
| ctrl |
| ..foreign.pipe(stringChannel) |
| ..local.sink.add('Piped!\n'); |
| await ctrl.local.sink.close(); |
| |
| // The StreamChannel<T> interface provides several guarantees, which can be |
| // found here: |
| // https://pub.dev/documentation/stream_channel/latest/stream_channel/StreamChannel-class.html |
| // |
| // By calling `StreamChannel<T>.withGuarantees()`, you can create a |
| // StreamChannel<T> that provides all guarantees. |
| var dummyCtrl0 = StreamChannelController<String>(); |
| var guaranteedChannel = StreamChannel.withGuarantees( |
| dummyCtrl0.foreign.stream, dummyCtrl0.foreign.sink); |
| |
| // To close a StreamChannel, use `sink.close()`. |
| await guaranteedChannel.sink.close(); |
| |
| // A MultiChannel<T> multiplexes multiple virtual channels across a single |
| // underlying transport layer. For example, an application listening over |
| // standard I/O can still support multiple clients if it has a mechanism to |
| // separate events from different clients. |
| // |
| // A MultiChannel<T> splits events into numbered channels, which are |
| // instances of VirtualChannel<T>. |
| var dummyCtrl1 = StreamChannelController<String>(); |
| var multiChannel = MultiChannel<String>(dummyCtrl1.foreign); |
| var channel1 = multiChannel.virtualChannel(); |
| await multiChannel.sink.close(); |
| |
| // The client/peer should also create its own MultiChannel<T>, connected to |
| // the underlying transport, use the corresponding ID's to handle events in |
| // their respective channels. It is up to you how to communicate channel ID's |
| // across different endpoints. |
| var dummyCtrl2 = StreamChannelController<String>(); |
| var multiChannel2 = MultiChannel<String>(dummyCtrl2.foreign); |
| var channel2 = multiChannel2.virtualChannel(channel1.id); |
| await channel2.sink.close(); |
| await multiChannel2.sink.close(); |
| |
| // Multiple instances of a Dart application can communicate easily across |
| // `SendPort`/`ReceivePort` pairs by means of the `IsolateChannel<T>` class. |
| // Typically, one endpoint will create a `ReceivePort`, and call the |
| // `IsolateChannel.connectReceive` constructor. The other endpoint will be |
| // given the corresponding `SendPort`, and then call |
| // `IsolateChannel.connectSend`. |
| var recv = ReceivePort(); |
| var recvChannel = IsolateChannel<void>.connectReceive(recv); |
| var sendChannel = IsolateChannel<void>.connectSend(recv.sendPort); |
| |
| // You must manually close `IsolateChannel<T>` sinks, however. |
| await recvChannel.sink.close(); |
| await sendChannel.sink.close(); |
| |
| // You can use the `Disconnector` transformer to cause a channel to act as |
| // though the remote end of its transport had disconnected. |
| var disconnector = Disconnector<String>(); |
| var disconnectable = stringChannel.transform(disconnector); |
| disconnectable.sink.add('Still connected!'); |
| await disconnector.disconnect(); |
| |
| // Additionally: |
| // * The `DelegatingStreamController<T>` class can be extended to build a |
| // basis for wrapping other `StreamChannel<T>` objects. |
| // * The `jsonDocument` transformer converts events to/from JSON, using |
| // the `json` codec from `dart:convert`. |
| // * `package:json_rpc_2` directly builds on top of |
| // `package:stream_channel`, so any compatible transport can be used to |
| // create interactive client/server or peer-to-peer applications (i.e. |
| // language servers, microservices, etc. |
| } |