You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter使用web_socket_channel遇连接提前关闭的HttpException求助

解决Flutter WebSocket连接报错及实时聊天实现方案

问题背景

首次在Flutter项目中使用web_socket_channel:2.4.0包测试WebSocket时,出现Unhandled Exception: HttpException: Connection closed before full header was received错误,同时触发WebSocketChannelException。需求是通过WebSocket持续调用聊天API,实现新消息实时更新界面。

错误信息

D/EGL_emulation( 8164): app_time_stats: avg=20620.87ms min=20620.87ms max=20620.87ms count=1
E/flutter ( 8164): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: HttpException: Connection closed before full header was received, uri = http://192.168.5.104:8080/api/v1/language/get-all-active-languages
E/flutter ( 8164): 
E/flutter ( 8164): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: WebSocketChannelException: WebSocketChannelException: HttpException: Connection closed before full header was received, uri = http://192.168.5.104:8080/api/v1/language/get-all-active-languages
E/flutter ( 8164): #0      new IOWebSocketChannel._withoutSocket.<anonymous closure> (package:web_socket_channel/io.dart:119:24)
E/flutter ( 8164): #1      Stream.handleError.<anonymous closure> (dart:async/stream.dart:923:16)
E/flutter ( 8164): #2      _HandleErrorStream._handleError (dart:async/stream_pipe.dart:269:17)
E/flutter ( 8164): #3      _ForwardingStreamSubscription._handleError (dart:async/stream_pipe.dart:157:13)
E/flutter ( 8164): #4      _RootZone.runBinaryGuarded (dart:async/zone.dart:1606:10)
E/flutter ( 8164): #5      _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:358:15)
E/flutter ( 8164): #6      _BufferingStreamSubscription._sendError (dart:async/stream_impl.dart:376:7)
E/flutter ( 8164): #7      _BufferingStreamSubscription._addError (dart:async/stream_impl.dart:280:7)
E/flutter ( 8164): #8      _SyncStreamControllerDispatch._sendError (dart:async/stream_controller.dart:788:19)
E/flutter ( 8164): #9      _StreamController._addError (dart:async/stream_controller.dart:666:7)
E/flutter ( 8164): #10     _RootZone.runBinaryGuarded (dart:async/zone.dart:1606:10)
E/flutter ( 8164): #11     _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:358:15)
E/flutter ( 8164): #12     _BufferingStreamSubscription._sendError (dart:async/stream_impl.dart:376:7)
E/flutter ( 8164): #13     _BufferingStreamSubscription._addError (dart:async/stream_impl.dart:280:7)
E/flutter ( 8164): #14     _SyncStreamControllerDispatch._sendError (dart:async/stream_controller.dart:788:19)
E/flutter ( 8164): #15     _StreamController._addError (dart:async/stream_controller.dart:666:7)
E/flutter ( 8164): #16     new Stream.fromFuture.<anonymous closure> (dart:async/stream.dart:245:18)
E/flutter ( 8164): #17     _RootZone.runBinary (dart:async/zone.dart:1666:54)
E/flutter ( 8164): #18     _FutureListener.handleError (dart:async/future_impl.dart:171:22)
E/flutter ( 8164): #19     Future._propagateToListeners.handleError (dart:async/future_impl.dart:852:47)
E/flutter ( 8164): #20     Future._propagateToListeners (dart:async/future_impl.dart:873:13)
E/flutter ( 8164): #21     Future._completeError (dart:async/future_impl.dart:649:5)
E/flutter ( 8164): #22     Future._asyncCompleteError.<anonymous closure> (dart:async/future_impl.dart:739:7)
E/flutter ( 8164): #23     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter ( 8164): #24     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)

测试代码

final channel = IOWebSocketChannel.connect(
    Uri.parse(
        'ws://192.168.5.104:8080/api/v1/language/get-all-active-languages'),
    headers: {
      "Content-Type": "application/json",
    },
  );

  streamListener() {
    channel.stream.listen((message) {
      print(message);
    });
  }

  @override
  void initState() {
    streamListener();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamBuilder(
        stream: channel.stream,
        builder: (context, snapshot) {
          return Text(snapshot.hasData ? '${snapshot.data}' : 'sdadsa');
        },
      ),
    );
  }

实际聊天API及输出

API地址:{{localUrl}}/chat/get-chat-history/66031ebc40dbc657b9f815e5

{
    "success": true,
    "statusCode": 200,
    "message": "Operation Successful",
    "statusMessage": "Success",
    "data": {
        "chatHistory": [
            {
                "_id": "6653535805176abed962fc2f",
                "sender": {
                    "profileImage": {
                        "url": "https://res.cloudinary.com/dc1duf3lc/image/upload/v1714981559/profiles/hhx8wjwo6pcumey8vikj.jpg",
                        "public_id": "profiles/hhx8wjwo6pcumey8vikj",
                        "isUploaded": true
                    },
                    "_id": "66388ab79b98ca0c2c1c5ca3",
                    "firstName": "test",
                    "lastName": "test"
                },
                "receiver": {
                    "profileImage": {
                        "url": "https://res.cloudinary.com/dc1duf3lc/image/upload/v1711480357/profiles/g8sgdg7fk5gvy7urvwui.jpg",
                        "public_id": "profiles/g8sgdg7fk5gvy7urvwui",
                        "isUploaded": true
                    },
                    "_id": "66031ebc40dbc657b9f815e5",
                    "firstName": "Test",
                    "lastName": "Test farmer"
                },
                "content": "test message",
                "createdAt": "2024-05-26T15:20:56.393Z",
                "updatedAt": "2024-05-26T15:20:56.393Z",
                "__v": 0
            },
            {
                "_id": "6653534005176abed962fc28",
                "sender": {
                    "profileImage": {
                        "url": "https://res.cloudinary.com/dc1duf3lc/image/upload/v1714981559/profiles/hhx8wjwo6pcumey8vikj.jpg",
                        "public_id": "profiles/hhx8wjwo6pcumey8vikj",
                        "isUploaded": true
                    },
                    "_id": "66388ab79b98ca0c2c1c5ca3",
                    "firstName": "test",
                    "lastName": "test"
                },
                "receiver": {
                    "profileImage": {
                        "url": "https://res.cloudinary.com/dc1duf3lc/image/upload/v1711480357/profiles/g8sgdg7fk5gvy7urvwui.jpg",
                        "public_id": "profiles/g8sgdg7fk5gvy7urvwui",
                        "isUploaded": true
                    },
                    "_id": "66031ebc40dbc657b9f815e5",
                    "firstName": "Test",
                    "lastName": "Test farmer"
                },
                "content": "test message",
                "createdAt": "2024-05-26T15:20:32.971Z",
                "updatedAt": "2024-05-26T15:20:32.971Z",
                "__v": 0
            }
        ]
    }
}

解决方案

1. 修正WebSocket端点错误

你当前连接的是普通HTTP接口,WebSocket需要后端提供专门的WS/WSS协议端点,不是HTTP接口。需要和后端确认聊天功能对应的WebSocket路径,格式类似:ws://192.168.5.104:8080/api/v1/chat/connect/66031ebc40dbc657b9f815e5。

2. 优化连接配置与错误处理

  • 移除不必要的Content-Type请求头,WebSocket握手不需要该字段;
  • 为Stream添加错误监听,避免未捕获异常导致崩溃:
channel.stream.listen(
  (message) {
    print(message);
    // 处理消息更新UI
  },
  onError: (error) {
    print('WebSocket错误: $error');
    // 可选:添加自动重连逻辑
  },
  onDone: () {
    print('连接已关闭');
    // 可选:添加重连逻辑
  },
);

3. 实时聊天完整实现流程

  1. 初始化历史记录:先通过HTTP接口{{localUrl}}/chat/get-chat-history/66031ebc40dbc657b9f815e5获取历史聊天记录,初始化界面;
  2. 连接WebSocket:连接后端提供的聊天WebSocket端点,监听新消息推送;
  3. 更新UI:收到后端推送的新消息后,更新本地聊天列表并刷新界面。

4. 修正后的示例代码

import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

class ChatScreen extends StatefulWidget {
  const ChatScreen({super.key});

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  late WebSocketChannel channel;
  List<dynamic> chatHistory = [];

  @override
  void initState() {
    super.initState();
    // 1. 获取历史聊天记录(需实现HTTP请求逻辑)
    _fetchChatHistory();
    // 2. 连接WebSocket(替换为后端实际WS端点)
    channel = IOWebSocketChannel.connect(
      Uri.parse('ws://192.168.5.104:8080/api/v1/chat/connect/66031ebc40dbc657b9f815e5'),
    );
    _listenToWebSocket();
  }

  void _fetchChatHistory() async {
    // 示例HTTP请求逻辑,需引入http包
    // final response = await http.get(Uri.parse('http://192.168.5.104:8080/chat/get-chat-history/66031ebc40dbc657b9f815e5'));
    // if (response.statusCode == 200) {
    //   setState(() {
    //     chatHistory = jsonDecode(response.body)['data']['chatHistory'];
    //   });
    // }
  }

  void _listenToWebSocket() {
    channel.stream.listen(
      (message) {
        // 假设后端推送的是新消息JSON字符串,解析后更新列表
        setState(() {
          chatHistory.insert(0, message); // 最新消息插入到列表顶部
        });
      },
      onError: (error) {
        print('WebSocket错误: $error');
      },
      onDone: () {
        print('WebSocket连接关闭');
      },
    );
  }

  @override
  void dispose() {
    channel.sink.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('聊天')),
      body: ListView.builder(
        reverse: true,
        itemCount: chatHistory.length,
        itemBuilder: (context, index) {
          final message = chatHistory[index];
          return ListTile(
            title: Text(message['content']),
            subtitle: Text(message['sender']['firstName']),
          );
        },
      ),
    );
  }
}

5. 后端配合要求

  • 确保WebSocket服务正常运行,支持按聊天ID建立连接;
  • 新消息产生时,主动推送给对应聊天的在线客户端;
  • 实现WebSocket握手、心跳检测、断开重连等基础逻辑。

内容的提问来源于stack exchange,提问作者Ahmed Raza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 09:14:53