除了普通的 HTTP 请求,你还可以通过 WebSockets 来连接服务器。WebSockets 可以以非轮询的方式与服务器进行双向通信。

在这里,你可以连接一个由 websocket.org 提供的测试服务器。该服务器只会返回你发送的信息。

使用步骤

  • 连接 WebSocket 服务器

  • 监听来自服务器的消息

  • 向服务器发送数据

  • 关闭 WebSocket 连接

1. 连接 WebSocket 服务器

web_socket_channel 包提供了连接 WebSocket 服务器所需的一些工具。

该包提供的 WebSocketChannel 不仅可以让你监听到来自服务器的消息还可以让你向服务器推送消息。

在 Flutter 中,只用一行代码就可以创建一个连接到服务器的 WebSocketChannel

  1. final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');

2. 监听来自服务器的消息

建立了连接之后,你就可以监听来自服务器的消息了。

当你向测试服务器发送一条消息之后,它会将同样的消息发送回来。

那么如何监听到这些消息并展示他们呢?在这个例子中,我们用 StreamBuilder 组件来监听新消息,用 Text 组件来展示它们。

  1. StreamBuilder(
  2. stream: widget.channel.stream,
  3. builder: (context, snapshot) {
  4. return Text(snapshot.hasData ? '${snapshot.data}' : '');
  5. },
  6. );

这样为什么可行?

WebSocketChannel 提供了一个来自服务器的消息 Stream

这个 Stream 类是 dart:async 包的基本组成部分。它提供了一个从数据源监听异步事件的方法。和 Future 不一样的是,Future 只能返回一个单独的异步响应,而 Stream 类可以随着时间的推移传递很多事件。

StreamBuilder 组件会和 Stream 建立起连接,并且每当它接收到一个使用给定 builder 函数的事件时,就会通知 Flutter 去 rebuild。

3. 向服务器发送数据

要向服务器发送数据,可以使用 WebSocketChannel 提供的 sink 下的 add 方法来发送信息。

  1. channel.sink.add('Hello!');

这又是如何工作的呢

WebSocketChannel 提供了一个 StreamSink 来向服务器推送消息。

这个 StreamSink 类提供了一个可以向数据源添加同步或者异步事件的通用方法。

4. 关闭 WebSocket 连接

当你使用完 WebSocket 之后,记得关闭这个连接。要关闭这个 WebSocket 连接,只需要关闭 sink

  1. channel.sink.close();

完整代码

  1. import 'package:flutter/foundation.dart';
  2. import 'package:web_socket_channel/io.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:web_socket_channel/web_socket_channel.dart';
  5. void main() => runApp(MyApp());
  6. class MyApp extends StatelessWidget {
  7. @override
  8. Widget build(BuildContext context) {
  9. final title = 'WebSocket Demo';
  10. return MaterialApp(
  11. title: title,
  12. home: MyHomePage(
  13. title: title,
  14. channel: IOWebSocketChannel.connect('ws://echo.websocket.org'),
  15. ),
  16. );
  17. }
  18. }
  19. class MyHomePage extends StatefulWidget {
  20. final String title;
  21. final WebSocketChannel channel;
  22. MyHomePage({Key key, @required this.title, @required this.channel})
  23. : super(key: key);
  24. @override
  25. _MyHomePageState createState() => _MyHomePageState();
  26. }
  27. class _MyHomePageState extends State<MyHomePage> {
  28. TextEditingController _controller = TextEditingController();
  29. @override
  30. Widget build(BuildContext context) {
  31. return Scaffold(
  32. appBar: AppBar(
  33. title: Text(widget.title),
  34. ),
  35. body: Padding(
  36. padding: const EdgeInsets.all(20.0),
  37. child: Column(
  38. crossAxisAlignment: CrossAxisAlignment.start,
  39. children: <Widget>[
  40. Form(
  41. child: TextFormField(
  42. controller: _controller,
  43. decoration: InputDecoration(labelText: 'Send a message'),
  44. ),
  45. ),
  46. StreamBuilder(
  47. stream: widget.channel.stream,
  48. builder: (context, snapshot) {
  49. return Padding(
  50. padding: const EdgeInsets.symmetric(vertical: 24.0),
  51. child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
  52. );
  53. },
  54. )
  55. ],
  56. ),
  57. ),
  58. floatingActionButton: FloatingActionButton(
  59. onPressed: _sendMessage,
  60. tooltip: 'Send message',
  61. child: Icon(Icons.send),
  62. ), // This trailing comma makes auto-formatting nicer for build methods.
  63. );
  64. }
  65. void _sendMessage() {
  66. if (_controller.text.isNotEmpty) {
  67. widget.channel.sink.add(_controller.text);
  68. }
  69. }
  70. @override
  71. void dispose() {
  72. widget.channel.sink.close();
  73. super.dispose();
  74. }
  75. }

Web Sockets Demo