美文网首页Flutter
Flutter之WebSocket

Flutter之WebSocket

作者: h2coder | 来源:发表于2023-07-18 13:54 被阅读0次

    前言

    • WebSocket很常用,在很多语言都有支持,例如Java、JavaScript、Rust、C++、Go等,那么Dart也是有支持的,在Flutter中使用web_socket_channel即可使用WebSocket
    • Flutter的跨平台功能很强大,本篇使用Flutter的WebSocket来实现安卓、iOS、Web的3个平台的应用编写

    效果展示

    首页.png 聊天页面.png

    依赖

    • Toast库可以换其他的,我选择fluttertoast是因为它是使用平台API来实现的,在Android平台上调用的的是Toast,可以跨页面、跨应用显示,如果是纯Flutter实现,是不可以跨应用的
    # WebSocket支持库
    web_socket_channel: ^2.4.0
    # Toast,支持Android、iOS、Web
    fluttertoast: ^8.0.9
    

    工具类

    • Toast工具类(toast_util.dart)
    import 'package:flutter/material.dart';
    import 'package:fluttertoast/fluttertoast.dart';
    
    /// Toast 工具类
    class ToastUtil {
      static toast(String msg) {
        Fluttertoast.showToast(
            msg: msg,
            toastLength: Toast.LENGTH_SHORT,
            gravity: ToastGravity.BOTTOM,
            timeInSecForIosWeb: 1,
            backgroundColor: Colors.black,
            textColor: Colors.white,
            fontSize: 16.0);
      }
    }
    
    • WebSocket工具类(web_socket_manager.dart)
    import 'dart:async';
    
    import 'package:flutter/foundation.dart';
    import 'package:web_socket_channel/html.dart';
    import 'package:web_socket_channel/io.dart';
    import 'package:web_socket_channel/web_socket_channel.dart';
    
    /// 连接状态枚举
    enum ConnectStatusEnum {
      //已连接
      connect,
      //连接中
      connecting,
      //已关闭
      close,
      //关闭中
      closing
    }
    
    /// 接收到消息后的回调
    typedef ListenMessageCallback = void Function(String msg);
    
    /// 错误回调
    typedef ErrorCallback = void Function(Exception error);
    
    /// WebSocket管理类
    class WebSocketManager {
      /// 连接状态,默认为关闭
      ConnectStatusEnum _connectStatus = ConnectStatusEnum.close;
    
      /// WebSocket通道
      WebSocketChannel? _webSocketChannel;
    
      /// WebSocket通道的流
      Stream<dynamic>? _webSocketChannelStream;
    
      /// WebSocket状态的流控制器
      final StreamController<ConnectStatusEnum> _socketStatusController =
          StreamController<ConnectStatusEnum>();
    
      /// 连接状态的流
      Stream<ConnectStatusEnum>? _socketStatusStream;
    
      /// 获取WebSocket消息的流
      Stream<dynamic> getWebSocketChannelStream() {
        //只赋值一次
        _webSocketChannelStream ??= _webSocketChannel!.stream.asBroadcastStream();
        return _webSocketChannelStream!;
      }
    
      /// 获取连接状态的流
      Stream<ConnectStatusEnum> getSocketStatusStream() {
        //只赋值一次
        _socketStatusStream ??= _socketStatusController.stream.asBroadcastStream();
        return _socketStatusStream!;
      }
    
      /// 发起连接,Url实例:"ws://echo.websocket.org";
      Future<bool> connect(String url) async {
        if (_connectStatus == ConnectStatusEnum.connect) {
          //已连接,不需要处理
          return true;
        } else if (_connectStatus == ConnectStatusEnum.close) {
          //未连接,发起连接
          _connectStatus = ConnectStatusEnum.connecting;
          _socketStatusController.add(ConnectStatusEnum.connecting);
          var connectUrl = Uri.parse(url);
          //Web端需要使用该Channel,否则报错
          if (kIsWeb) {
            _webSocketChannel = HtmlWebSocketChannel.connect(connectUrl);
          } else {
            _webSocketChannel = IOWebSocketChannel.connect(connectUrl);
          }
          _connectStatus = ConnectStatusEnum.connect;
          _socketStatusController.add(ConnectStatusEnum.connect);
          return true;
        } else {
          return false;
        }
      }
    
      /// 关闭连接
      Future disconnect() async {
        if (_connectStatus == ConnectStatusEnum.connect) {
          _connectStatus = ConnectStatusEnum.closing;
          if (!_socketStatusController.isClosed) {
            _socketStatusController.add(ConnectStatusEnum.closing);
          }
          await _webSocketChannel?.sink.close(3000, "主动关闭");
          _connectStatus = ConnectStatusEnum.close;
          if (!_socketStatusController.isClosed) {
            _socketStatusController.add(ConnectStatusEnum.close);
          }
        }
      }
    
      /// 重连
      void reconnect(String url) async {
        await disconnect();
        await connect(url);
      }
    
      /// 监听消息
      void listen(ListenMessageCallback messageCallback, {ErrorCallback? onError}) {
        getWebSocketChannelStream().listen((message) {
          messageCallback.call(message);
        }, onError: (error) {
          //连接异常
          _connectStatus = ConnectStatusEnum.close;
          _socketStatusController.add(ConnectStatusEnum.close);
          if (onError != null) {
            onError.call(error);
          }
        });
      }
    
      /// 发送消息
      bool sendMsg(String text) {
        if (_connectStatus == ConnectStatusEnum.connect) {
          _webSocketChannel?.sink.add(text);
          return true;
        }
        return false;
      }
    
      /// 获取当前连接状态
      ConnectStatusEnum getCurrentStatus() {
        if (_connectStatus == ConnectStatusEnum.connect) {
          return ConnectStatusEnum.connect;
        } else if (_connectStatus == ConnectStatusEnum.connecting) {
          return ConnectStatusEnum.connecting;
        } else if (_connectStatus == ConnectStatusEnum.close) {
          return ConnectStatusEnum.close;
        } else if (_connectStatus == ConnectStatusEnum.closing) {
          return ConnectStatusEnum.closing;
        }
        return ConnectStatusEnum.closing;
      }
    
      /// 销毁通道
      void dispose() {
        //断开连接
        disconnect();
        //关闭连接状态的流
        _socketStatusController.close();
      }
    }
    

    页面

    应用入口(main.dart)

    import 'package:flutter/material.dart';
    
    import 'home_page.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    /// 主页面
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter WebSocket',
          theme: ThemeData(
            //主题色
            primarySwatch: Colors.blue,
            //colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
            //是否使用Material3风格
            useMaterial3: false,
          ),
          home: const HomePage(title: 'Flutter WebSocket'),
        );
      }
    }
    

    首页(home_page.dart)

    import 'package:flutter/material.dart';
    import 'package:flutter_web_socket/util/toast_util.dart';
    import 'package:flutter_web_socket/web_socket_page.dart';
    
    /// 首页
    class HomePage extends StatefulWidget {
      const HomePage({super.key, required this.title});
    
      /// 页面标题
      final String title;
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      /// TextField操作控制器
      final TextEditingController _editingController =
          TextEditingController(text: "ws://127.0.0.1:9001/ws");
    
      /// 跳转到WebSocket页面
      void _goWebSocketPage(BuildContext context, String url) {
        Navigator.push(context, MaterialPageRoute(builder: (context) {
          return WebSocketPage(
            url: url,
          );
        }));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.primary,
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.symmetric(
                      vertical: 10.0,
                      horizontal: 15.0
                  ),
                  child: TextField(
                    controller: _editingController,
                    decoration: InputDecoration(
                      //左侧图标
                      icon: const Icon(Icons.person),
                      //提示文字
                      hintText: "请输入连接地址:",
                      //边框
                      border: const OutlineInputBorder(),
                      //右侧按钮
                      suffixIcon: IconButton(
                        icon: const Icon(Icons.close),
                        onPressed: () {
                          //清除输入框内容
                          _editingController.clear();
                        },
                      ),
                    ),
                  ),
                ),
                ElevatedButton(
                  child: const Text("WebSocket测试"),
                  onPressed: () {
                    //连接地址
                    var url = _editingController.text;
                    if (url.isEmpty) {
                      ToastUtil.toast("请输入连接地址");
                      return;
                    }
                    // 跳转到WebSocket页面
                    _goWebSocketPage(context, url);
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    

    聊天页面(web_socket_page.dart)

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_web_socket/util/toast_util.dart';
    import 'package:flutter_web_socket/util/web_socket_manager.dart';
    
    /// WebSocket页面
    class WebSocketPage extends StatefulWidget {
      /// 连接地址
      final String _url;
    
      const WebSocketPage({super.key, required String url}) : _url = url;
    
      @override
      State<StatefulWidget> createState() {
        return WebSocketPageState();
      }
    }
    
    class WebSocketPageState extends State<WebSocketPage> {
      /// WebSocket管理类
      final WebSocketManager _webSocketManager = WebSocketManager();
    
      /// TextField操作控制器
      final TextEditingController _editingController = TextEditingController();
    
      /// ListView的滚动控制器
      final ScrollController _scrollController = ScrollController();
    
      /// 消息列表
      final List<String> _msgList = [];
    
      @override
      void initState() {
        super.initState();
        _webSocketManager.connect(widget._url).then((isConnect) {
          //连接成功,监听消息
          if (isConnect) {
            _webSocketManager.listen((msg) {
              //添加消息到列表中
              _addMsg2List("服务器:$msg");
            }, onError: (error) {
              ToastUtil.toast("连接异常:${error.toString()}");
            });
          }
        });
      }
    
      @override
      void dispose() {
        //断开连接,销毁流对象
        _webSocketManager.dispose();
        super.dispose();
      }
    
      /// 添加消息到消息列表中
      void _addMsg2List(String msg) {
        setState(() {
          _msgList.add(msg);
        });
        //延迟500毫秒,再滚动到地址
        Future.delayed(const Duration(milliseconds: 500), () {
          _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
        });
      }
    
      /// 发送消息
      void _sendMsg(String msg) {
        if (msg.isEmpty) {
          ToastUtil.toast("请输入要发送的消息内容");
          return;
        }
        var isSendSuccess = _webSocketManager.sendMsg(msg);
        //发送成功
        if (isSendSuccess) {
          //添加消息到列表中
          _addMsg2List("我:$msg");
          //清除输入框的内容
          _editingController.clear();
        } else {
          ToastUtil.toast("发送失败");
        }
      }
    
      /// 构建连接状态控件
      Widget _buildConnectStatusWidget() {
        return StreamBuilder<ConnectStatusEnum>(
          builder: (context, snapshot) {
            if (snapshot.data == ConnectStatusEnum.connect) {
              return StreamBuilder(
                builder: (context, newSnapshot) {
                  //WebSocket发生错误,那么重连
                  if (newSnapshot.hasError) {
                    _webSocketManager.reconnect(widget._url);
                  }
                  return const Text(
                    "连接状态:已连接",
                  );
                },
                stream: _webSocketManager.getWebSocketChannelStream(),
              );
            } else if (snapshot.data == ConnectStatusEnum.connecting) {
              //连接中
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const CupertinoActivityIndicator(
                    animating: true,
                    radius: 10,
                  ),
                  Container(
                    margin: const EdgeInsets.only(left: 5.0),
                    child: const Text("连接中..."),
                  )
                ],
              );
            } else if (snapshot.data == ConnectStatusEnum.close) {
              return const Text(
                "连接状态:已关闭",
              );
            } else if (snapshot.data == ConnectStatusEnum.closing) {
              return const Text(
                "连接状态:关闭中",
              );
            }
            return const Text(
              "未连接",
            );
          },
          initialData: ConnectStatusEnum.close,
          stream: _webSocketManager.getSocketStatusStream(),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            leading: IconButton(
                onPressed: () {
                  Navigator.pop(context, null);
                },
                icon: const Icon(Icons.arrow_back)),
            title: const Text("WebSocket测试"),
          ),
          body: Column(
            children: [
              //连接状态
              Column(
                children: [
                  Container(
                    height: 0.2,
                    color: Colors.grey,
                  ),
                  Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: _buildConnectStatusWidget(),
                  ),
                  Container(
                    height: 0.2,
                    color: Colors.grey,
                  )
                ],
              ),
              Expanded(
                flex: 1,
                child: ListView.builder(
                    controller: _scrollController,
                    itemCount: _msgList.length,
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(title: Text(_msgList[index]));
                    }),
              ),
              Center(
                child: Column(
                  children: [
                    Container(
                      height: 0.3,
                      color: Colors.grey,
                    ),
                    Padding(
                      padding: const EdgeInsets.symmetric(
                          vertical: 15.0, horizontal: 18.0),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Expanded(
                            flex: 1,
                            child: TextField(
                              controller: _editingController,
                              decoration: const InputDecoration(
                                //提示文字
                                hintText: "请输入要发送的消息",
                                //边框
                                border: OutlineInputBorder(),
                              ),
                            ),
                          ),
                          Container(
                            margin: const EdgeInsets.only(left: 5.0),
                            child: ElevatedButton(
                              child: const Text("发送"),
                              onPressed: () {
                                var msg = _editingController.text;
                                _sendMsg(msg);
                              },
                            ),
                          )
                        ],
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        );
      }
    }
    

    权限配置

    • Android端的网络权限
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
        <uses-permission android:name="android.permission.INTERNET" />
    
        <!-- 省略其他配置 -->
    </manifest>
    

    相关文章

      网友评论

        本文标题:Flutter之WebSocket

        本文链接:https://www.haomeiwen.com/subject/uumnudtx.html