0%

flutter封装网络层(http和WebSocket请求)


一转眼来到国庆假期,核酸未停疫情还在,假期是个好好充电的日子,休息之余总结之前的工作度过一个充实的假期,但愿疫情快点过去,大家能回归到正常生活吧
上一篇写到了flutter的开发框架封装,其中用到了DioManager这个类,这个类主要是利用flutter框架提供的dio对网络层http请求做一个封装,另外由于业务需要,也封装了WebSocket请求,这里整理了一下具体的封装流程,欢迎一起交流讨论~


http请求封装

封装数据传输Bean基础类

这里以WanAndroid的Api为例

  • data是泛型T,返回的数据内容
  • errorCode是后端返回的int类型的业务状态码
  • errorMsg是错误信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    class BaseResult<T> {
    BaseResult({
    this.data,
    this.errorCode,
    this.errorMsg,
    });

    factory BaseResult.fromJson(Map<String, dynamic> json) {
    return BaseResult(
    data: json['data'],
    errorCode: json['errorCode'],
    errorMsg: json['errorMsg']);
    }

    /**
    * 不显示错误toast
    */
    bool isSuccess() {
    return errorCode == 0;
    }

    T? data;
    int? errorCode;
    String? errorMsg;

    Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    if (data != null) {
    map['data'] = data;
    }
    map['errorCode'] = errorCode;
    map['errorMsg'] = errorMsg;
    return map;
    }
    }

    单例模式

    构造函数私有化,懒汉式构造单例
    1
    2
    3
    4
    5
    6
    7
    8
    static DioManager? instance;

    static DioManager? getInstance() {
    instance ??= DioManager._internal();
    return instance;
    }

    DioManager._internal();

    声明baseUrl和全局Header

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static String getAppUrl() {
    return "https://www.wanandroid.com";
    }

    static Map<String, dynamic> baseHeaders = {
    "X-SYSTEM": "Android",
    "LANG": "zh_CN",
    "x-app-token": "",
    "X-VERSION": ""
    };

    初始化DioManager

    dio对象全局唯一,DioManager的构造函数中需要对options进行相关配置,全局header和baseUrl以及连接超时,接收超时,发送超时,添加日志拦截器,设置数据的返回格式为Json
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Dio dio = Dio();

    DioManager() {
    dio.options.headers = baseHeaders;
    dio.options.baseUrl = getAppUrl();
    dio.options.connectTimeout = 15000;
    dio.options.receiveTimeout = 15000;
    dio.options.sendTimeout = 30000;
    // 是否开启请求日志
    if (Global.isDebug) {
    dio.interceptors.addAll([LogInterceptor()]);
    }
    dio.options.responseType = ResponseType.json;
    }

    封装get和post请求

  • post请求有表单和body两种方式,isForm为true时为表单请求默认为false
  • _request方法中统一处理,调用dio的request方法传path,data,queryParameters和options发起请求,根据post和get请求配置options的method,根据post表单和body两种请求方式配置contentType,如果是get请求携带的参数传queryParameters,post请求携带的参数传data
  • dio的request需要try-catch捕获异常,如果捕获到异常且needToast为true时通过toast展示异常code
  • 获取到data数据后,如果是string则直接反序列化,否则调用Bean基础类BaseResult的fromJson反序列化,反序列化操作需要try-catch捕获异常,如果成功直接返回结果,否则返回序列化失败,如果当前是错误状态码且needToast为true时通过toast展示toast
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    Future<BaseResult> getRequest(String url, {Map<String, dynamic>? params}) {
    return _request(
    url,
    queryParameters: params,
    options: Options(method: "GET"),
    );
    }

    Future<BaseResult> postRequest(String url,
    {dynamic params, bool isForm = false}) {
    final contentType =
    isForm ? Headers.formUrlEncodedContentType : Headers.jsonContentType;
    final options = Options(
    method: "POST",
    extra: {"dio_content_type": contentType},
    contentType: contentType);
    return _request(url, data: params, options: options);
    }

    Future<BaseResult> _request(String path,
    {data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    bool needToast = true}) async {
    final Response response;
    try {
    response = await dio.request(path,
    data: data, queryParameters: queryParameters, options: options);
    } on DioError catch (error, stackTrace) {
    var checkedError = _checkError(error);
    final statusCode =
    checkedError.response?.statusCode ?? ResultCode.NO_NETWORK;
    if (needToast) {
    showToast("network_error(${statusCode.toString()})");
    }
    if (Global.isDebug) Future.error(checkedError, stackTrace);
    return BaseResult(errorCode: statusCode, errorMsg: error.message);
    }

    try {
    //防止解析失败
    var data = response.data;
    if (data is String) {
    data = json.decode(data);
    }
    var baseResult = BaseResult.fromJson(data);
    if (!baseResult.isSuccess() && needToast) {
    showToast(baseResult.errorMsg ?? "");
    }
    return baseResult;
    } catch (ex, stackTrace) {
    if (Global.isDebug) Future.error(ex, stackTrace);
    return BaseResult(
    errorCode: ResultCode.JSON_ERROR, errorMsg: ex.toString());
    }
    }

    错误码统一处理

    如果返回的DioError的response为空或statusCode为空统一处理为没有网络,其余的错误类型转换为自定义的错误码便于混合开发时统计
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    DioError _checkError(DioError error) {
    // 如果response为空构造没有网络的response
    // 如果statusCode为空直接赋值为没有网络
    /// 统一认为是没有网络
    Response errorResponse = error.response ??
    Response(
    statusCode: ResultCode.NO_NETWORK,
    requestOptions: error.requestOptions);

    errorResponse.statusCode ??= ResultCode.NO_NETWORK;

    /// 自定义请求超时错误码
    if (error.type == DioErrorType.connectTimeout) {
    errorResponse.statusCode = ResultCode.CONNECT_TIMEOUT;
    } else if (error.type == DioErrorType.receiveTimeout) {
    errorResponse.statusCode = ResultCode.RECEIVE_TIMEOUT;
    } else if (error.type == DioErrorType.sendTimeout) {
    errorResponse.statusCode = ResultCode.SEND_TIMEOUT;
    }
    error.response = errorResponse;
    return error;
    }

    class ResultCode {
    /// 业务错误
    static const BIZ_ERROR = -111;

    /// json解析异常
    static const JSON_ERROR = -100;

    /// 没有网络
    static const NO_NETWORK = -101;

    ///连接超时
    static const CONNECT_TIMEOUT = -102;

    /// 接收超时
    static const RECEIVE_TIMEOUT = -103;

    ///写数据超时
    static const SEND_TIMEOUT = -104;
    }
    最后附上DioManager完整的源码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    class DioManager {
    static DioManager? instance;

    factory DioManager() {
    instance ??= DioManager();
    return instance!;
    }

    static String getAppUrl() {
    return "https://www.wanandroid.com";
    }

    static Map<String, dynamic> baseHeaders = {
    "X-SYSTEM": "Android",
    "LANG": "zh_CN",
    "x-app-token": "",
    "X-VERSION": ""
    };

    Dio dio = Dio();

    DioManager._internal() {
    dio.options.headers = baseHeaders;
    dio.options.baseUrl = getAppUrl();
    dio.options.connectTimeout = 15000;
    dio.options.receiveTimeout = 15000;
    dio.options.sendTimeout = 30000;
    // 是否开启请求日志
    if (Global.isDebug) {
    dio.interceptors.addAll([LogInterceptor()]);
    }
    dio.options.responseType = ResponseType.json;
    }

    Future<BaseResult> getRequest(String url, {Map<String, dynamic>? params}) {
    return _request(
    url,
    queryParameters: params,
    options: Options(method: "GET"),
    );
    }

    Future<BaseResult> postRequest(String url,
    {dynamic params, bool isForm = false}) {
    final contentType =
    isForm ? Headers.formUrlEncodedContentType : Headers.jsonContentType;
    final options = Options(
    method: "POST",
    extra: {"dio_content_type": contentType},
    contentType: contentType);
    return _request(url, data: params, options: options);
    }

    Future<BaseResult> _request(String path,
    {data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    bool needToast = true}) async {
    final Response response;
    try {
    response = await dio.request(path,
    data: data, queryParameters: queryParameters, options: options);
    } on DioError catch (error, stackTrace) {
    var checkedError = _checkError(error);
    final statusCode =
    checkedError.response?.statusCode ?? ResultCode.NO_NETWORK;
    if (needToast) {
    showToast("network_error(${statusCode.toString()})");
    }
    if (Global.isDebug) Future.error(checkedError, stackTrace);
    return BaseResult(errorCode: statusCode, errorMsg: error.message);
    }

    try {
    //防止解析失败
    var data = response.data;
    if (data is String) {
    data = json.decode(data);
    }
    var baseResult = BaseResult.fromJson(data);
    if (!baseResult.isNotShowNetErrorToast() && needToast) {
    showToast(baseResult.errorMsg ?? "");
    }
    return baseResult;
    } catch (ex, stackTrace) {
    if (Global.isDebug) Future.error(ex, stackTrace);
    return BaseResult(
    errorCode: ResultCode.JSON_ERROR, errorMsg: ex.toString());
    }
    }

    DioError _checkError(DioError error) {
    /// 统一认为是没有网络
    // 如果response为空构造没有网络的response
    // 如果statusCode为空直接赋值为没有网络
    Response errorResponse = error.response ??
    Response(
    statusCode: ResultCode.NO_NETWORK,
    requestOptions: error.requestOptions);

    errorResponse.statusCode ??= ResultCode.NO_NETWORK;

    /// 自定义请求超时错误码
    if (error.type == DioErrorType.connectTimeout) {
    errorResponse.statusCode = ResultCode.CONNECT_TIMEOUT;
    } else if (error.type == DioErrorType.receiveTimeout) {
    errorResponse.statusCode = ResultCode.RECEIVE_TIMEOUT;
    } else if (error.type == DioErrorType.sendTimeout) {
    errorResponse.statusCode = ResultCode.SEND_TIMEOUT;
    }
    error.response = errorResponse;
    return error;
    }
    }

    /// 跟宿主定义的http网络错误码保持一致
    class ResultCode {
    /// 业务错误
    static const BIZ_ERROR = -111;

    /// json解析异常
    static const JSON_ERROR = -100;

    /// 没有网络
    static const NO_NETWORK = -101;

    ///连接超时
    static const CONNECT_TIMEOUT = -102;

    ///It occurs when receiving timeout.
    static const RECEIVE_TIMEOUT = -103;

    ///写数据超时
    static const SEND_TIMEOUT = -104;
    }

    WebSocket请求封装

    封装数据传输Bean基础类

    根据服务端返回的内容封装一个bean类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class WsBaseBean {
    Decimal? changeRate;
    Decimal? lastTradedPrice;
    String? symbolCode;
    Decimal? volValue;

    WsBaseBean(
    {this.changeRate, this.lastTradedPrice, this.symbolCode, this.volValue});

    factory WsBaseBean.fromJson(Map<String, dynamic> json) {
    return WsBaseBean(
    changeRate: Decimal.tryParse("${json["changeRate"] ?? 0.0}"),
    lastTradedPrice: Decimal.tryParse("${json["lastTradedPrice"] ?? 0.0}"),
    symbolCode: json["symbolCode"],
    volValue: Decimal.tryParse("${json["volValue"] ?? 0.0}"));
    }
    }

    单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /// 单例对象
    static WebSocketUtility? _socket;

    /// 内部构造方法,可避免外部暴露构造函数,进行实例化
    WebSocketUtility._internal();

    /// 获取单例内部方法
    factory WebSocketUtility() {
    // 只能有一个实例
    _socket ??= WebSocketUtility._internal();
    return _socket!;
    }

    声明Websocket地址和连接状态

    1
    2
    3
    4
    5
    6
    7
    const String _SOCKET_URL = 'ws://192.168.3.123:8181/test';

    enum SocketStatus {
    socketStatusConnected, // 已连接
    socketStatusFailed, // 失败
    socketStatusClosed, // 连接关闭
    }

    声明变量

  • 定义回调,接连错误,接收消息,开始连接
  • 定义IOWebSocketChannel实现WebSocket通信
  • 定义心跳定时器和心跳间隔,每隔固定时长发送心跳
  • 定义重连定时器和当前重连次数和最大重连次数,未达到最大重连次数时每隔固定时长重新连接
  • 定义当前socket的连接状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    IOWebSocketChannel? _webSocket; // WebSocket
    SocketStatus? _socketStatus; // socket状态
    Timer? _heartBeat; // 心跳定时器
    final int _heartTimes = 3000; // 心跳间隔(毫秒)
    final int _reconnectMaxCount = 60; // 重连次数,默认60次
    int _reconnectTimes = 0; // 重连计数器
    Timer? _reconnectTimer; // 重连定时器
    late Function onError; // 连接错误回调
    late Function onOpen; // 开始连接回调
    late Function onMessage; // 接收消息回调

    开始连接

  • 初始化IOWebSocketChannel开始连接
  • 设置_socketStatus连接状态
  • 重置重连计数和定时器
  • 调用onOpen回调
  • 接收消息回调处理,收到消息调onMessage回调,连接错误调onError回调,连接推出时尝试重连
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /// 开启WebSocket连接
    void openSocket() {
    closeSocket();
    _webSocket = IOWebSocketChannel.connect(_SOCKET_URL);
    print('WebSocket连接成功: $_SOCKET_URL');
    // 连接成功,返回WebSocket实例
    _socketStatus = SocketStatus.socketStatusConnected;
    // 连接成功,重置重连计数器
    _reconnectTimes = 0;
    if (_reconnectTimer != null) {
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    }
    onOpen();
    // 接收消息
    _webSocket?.stream.listen((data) => onMessage(data), onError: (e) {
    WebSocketChannelException ex = e;
    _socketStatus = SocketStatus.socketStatusFailed;
    onError(ex.message);
    closeSocket();
    }, onDone: () {
    print('closed');
    reconnect();
    });
    }

    销毁心跳

    取消心跳定时器
    1
    2
    3
    4
    5
    6
    void destroyHeartBeat() {
    if (_heartBeat != null) {
    _heartBeat?.cancel();
    _heartBeat = null;
    }
    }

    初始化并开启心跳

    如果心跳定时器已经初始化则销毁,重新初始化并开启心跳发送任务
    1
    2
    3
    4
    5
    6
    void initHeartBeat() {
    destroyHeartBeat();
    _heartBeat = Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
    sendMessage('{"module": "HEART_CHECK", "message": "请求心跳"}');
    });
    }

    发送WebSocket消息

    如果当前的连接状态是已连接则发送消息,否则打印当前状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /// 发送WebSocket消息
    void sendMessage(message) {
    if (_webSocket != null) {
    switch (_socketStatus) {
    case SocketStatus.socketStatusConnected:
    print('发送中:' + message);
    _webSocket?.sink.add(message);
    break;
    case SocketStatus.socketStatusClosed:
    print('连接已关闭');
    break;
    case SocketStatus.socketStatusFailed:
    print('发送失败');
    break;
    default:
    break;
    }
    }
    }

    重连机制

    如果当前未达到最大连接次数则重连次数+1,开启重连定时器,执行WebSocket连接,否则如果重连定时器不为空则销毁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /// 重连机制
    void reconnect() {
    if (_reconnectTimes < _reconnectMaxCount) {
    _reconnectTimes++;
    _reconnectTimer =
    Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
    openSocket();
    });
    } else {
    if (_reconnectTimer != null) {
    print('重连次数超过最大次数');
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    }
    return;
    }
    }
    最后附上WebSocketUtility完整的源码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    /// WebSocket地址
    const String _SOCKET_URL = 'ws://192.168.3.123:8181/test';

    /// WebSocket状态
    enum SocketStatus {
    socketStatusConnected, // 已连接
    socketStatusFailed, // 失败
    socketStatusClosed, // 连接关闭
    }

    class WebSocketUtility {
    /// 单例对象
    static WebSocketUtility? _socket;

    /// 内部构造方法,可避免外部暴露构造函数,进行实例化
    WebSocketUtility._internal();

    /// 获取单例内部方法
    factory WebSocketUtility() {
    // 只能有一个实例
    _socket ??= WebSocketUtility._internal();
    return _socket!;
    }

    IOWebSocketChannel? _webSocket; // WebSocket
    SocketStatus? _socketStatus; // socket状态
    Timer? _heartBeat; // 心跳定时器
    final int _heartTimes = 3000; // 心跳间隔(毫秒)
    final int _reconnectMaxCount = 60; // 重连次数,默认60次
    int _reconnectTimes = 0; // 重连计数器
    Timer? _reconnectTimer; // 重连定时器
    late Function onError; // 连接错误回调
    late Function onOpen; // 连接开启回调
    late Function onMessage; // 接收消息回调

    /// 初始化WebSocket
    void initWebSocket(
    {required Function onOpen,
    required Function onMessage,
    required Function onError}) {
    this.onOpen = onOpen;
    this.onMessage = onMessage;
    this.onError = onError;
    openSocket();
    }

    /// 开启WebSocket连接
    void openSocket() {
    closeSocket();
    _webSocket = IOWebSocketChannel.connect(_SOCKET_URL);
    print('WebSocket连接成功: $_SOCKET_URL');
    // 连接成功,返回WebSocket实例
    _socketStatus = SocketStatus.socketStatusConnected;
    // 连接成功,重置重连计数器
    _reconnectTimes = 0;
    if (_reconnectTimer != null) {
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    }
    onOpen();
    // 接收消息
    _webSocket?.stream.listen((data) => onMessage(data), onError: (e) {
    WebSocketChannelException ex = e;
    _socketStatus = SocketStatus.socketStatusFailed;
    onError(ex.message);
    closeSocket();
    }, onDone: () {
    print('closed');
    reconnect();
    });
    }

    /// 初始化心跳
    void initHeartBeat() {
    destroyHeartBeat();
    _heartBeat = Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
    sendMessage('{"module": "HEART_CHECK", "message": "请求心跳"}');
    });
    }

    /// 销毁心跳
    void destroyHeartBeat() {
    if (_heartBeat != null) {
    _heartBeat?.cancel();
    _heartBeat = null;
    }
    }

    /// 关闭WebSocket
    void closeSocket() {
    if (_webSocket != null) {
    print('WebSocket连接关闭');
    _webSocket?.sink.close();
    destroyHeartBeat();
    _socketStatus = SocketStatus.socketStatusClosed;
    }
    }

    /// 发送WebSocket消息
    void sendMessage(message) {
    if (_webSocket != null) {
    switch (_socketStatus) {
    case SocketStatus.socketStatusConnected:
    print('发送中:' + message);
    _webSocket?.sink.add(message);
    break;
    case SocketStatus.socketStatusClosed:
    print('连接已关闭');
    break;
    case SocketStatus.socketStatusFailed:
    print('发送失败');
    break;
    default:
    break;
    }
    }
    }

    /// 重连机制
    void reconnect() {
    if (_reconnectTimes < _reconnectMaxCount) {
    _reconnectTimes++;
    _reconnectTimer =
    Timer.periodic(Duration(milliseconds: _heartTimes), (timer) {
    openSocket();
    });
    } else {
    if (_reconnectTimer != null) {
    print('重连次数超过最大次数');
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    }
    return;
    }
    }
    }

    在Widget中使用

    在initState中初始化WebSocket并在连接后开启心跳发送任务与服务端保持通信连接,在消息接收中反序列化实时刷新页面数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    class SocketTestWidget extends StatefulWidget {
    const SocketTestWidget({Key? key}) : super(key: key);

    @override
    State<SocketTestWidget> createState() => _SocketTestWidgetState();
    }

    class _SocketTestWidgetState extends State<SocketTestWidget> {
    dynamic data;

    @override
    void initState() {
    super.initState();
    WebSocketUtility().initWebSocket(onOpen: () {
    WebSocketUtility().initHeartBeat();
    }, onMessage: (data) {
    WsBaseBean wsBaseBean = WsBaseBean.fromJson(jsonDecode(data));
    setState(() {
    this.data = wsBaseBean.changeRate;
    });
    }, onError: (e) {
    print("WebSocketUtility" + e);
    });
    }

    @override
    void dispose() {
    super.dispose();
    }

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: const Text("SocketTest"),
    ),
    body: Center(
    child: ListView(
    children: [Text('收到数据: ${data}')],
    ),
    ),
    );
    }
    }
    看下flutter客户端的控制台输出效果
    效果图

最后我们可以用python写个简单的服务端脚本测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import asyncio
import websockets
import time
import json
import random
websocket_users = set()

# 接收客户端消息并处理
async def recv_user_msg(websocket):
while True:
recv_text = await websocket.recv()
print("recv_text:", recv_text)
changeRate = random.randint(2000, 3000) + random.random()
lastTradedPrice = random.randint(1000, 2000) + random.random()
volValue = random.randint(4000, 5000) + random.random()
recv_text = {'changeRate' : changeRate, 'lastTradedPrice' : lastTradedPrice, 'symbolCode' : "BTC-USDT", 'volValue' : volValue}
data = json.dumps(recv_text)
print(data)
response_text = data
print("response_text:", f"Server return: {data}")
await websocket.send(response_text)
time.sleep(5)

# 服务器端主逻辑
async def run(websocket, path):
while True:
try:
await recv_user_msg(websocket)
except websockets.ConnectionClosed:
print("ConnectionClosed...", path) # 链接断开
print("websocket_users old:", websocket_users)
websocket_users.remove(websocket)
print("websocket_users new:", websocket_users)
break
except websockets.InvalidState:
print("InvalidState...") # 无效状态
break
except Exception as e:
print("Exception:", e)


if __name__ == '__main__':
print("192.168.3.123:8181 websocket...")
asyncio.get_event_loop().run_until_complete(websockets.serve(run, "192.168.3.123", 8181))
asyncio.get_event_loop().run_forever()

看下服务端的控制台输出效果
效果图