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

Flutter与Django WebSocket握手失败:Connection reset by peer错误排查

Flutter与Django Channels WebSocket连接故障排查

问题现象

Flutter客户端尝试与Django后端WebSocket握手时抛出错误:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: HttpException: Connection reset by peer, uri = http://myIP:8000/ws/chat_app/44HsUd/

同时Django ChatConsumer 的 receive 方法从未触发(终端无test in receive输出)。

相关代码

Django consumers.py

# consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from .utils import get_room

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['unique_id']
        self.room_group_name = f'video_{self.room_name}'

        # Join room group
        self.room_obj = await database_sync_to_async(get_room)(unique_id=self.room_name)

        await (self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )
        self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await (self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        # Receive video URL from client
        print('test in receive')
        text_data_json = json.loads(text_data)
        video_url = text_data_json['video_url']

        # Broadcast video URL to all clients in room
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'video_url',
                'video_url': video_url
            }
    )


    async def video_url(self, event):
        # Send video URL to client
        video_url = event['video_url']
        await self.send(text_data=json.dumps({
            'video_url': video_url
        }))

Flutter VideoStreamPage代码

import 'dart:convert';

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

class VideoStreamPage extends StatefulWidget {
  final String roomName;

  const VideoStreamPage({Key? key, required this.roomName}) : super(key: key);

  @override
  _VideoStreamPageState createState() => _VideoStreamPageState();
}

class _VideoStreamPageState extends State<VideoStreamPage> {
  late WebSocketChannel channel;
  TextEditingController urlController = TextEditingController();

  @override
  void initState() {
    super.initState();
    channel = IOWebSocketChannel.connect(
        'ws://myIP:8000/ws/chat_app/${widget.roomName}/');
  }

  void _sendUrlToServer() {
    String url = urlController.text;
    if (url.isNotEmpty) {
      channel.sink.add(jsonEncode({'video_url': url}));
      urlController.clear();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Video Stream'),
      ),
      body: Column(
        children: [
          Expanded(
            child: StreamBuilder(
              stream: channel.stream,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return YoutubePlayer(
                    controller: YoutubePlayerController(
                      initialVideoId: snapshot.data['video_url'],
                      flags: YoutubePlayerFlags(
                        autoPlay: true,
                      ),
                    ),
                    showVideoProgressIndicator: true,
                  );
                } else if (snapshot.hasError) {
                  return Center(
                    child: Text("Error: ${snapshot.error}"),
                  );
                } else {
                  return Center(
                    child: CircularProgressIndicator(),
                  );
                }
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: urlController,
                    decoration: InputDecoration(
                      hintText: 'Enter YouTube URL',
                    ),
                  ),
                ),
                SizedBox(width: 8),
                ElevatedButton(
                  onPressed: _sendUrlToServer,
                  child: Text('Send'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

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

排查步骤

1. 网络与端口校验

  • 确认myIP为服务器可公网/局域网访问的IP,禁止使用localhost或127.0.0.1(客户端与服务器不在同一设备时)。
  • 检查服务器防火墙/云安全组是否放行8000端口的TCP流量。
  • 用命令测试WebSocket握手:
    curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: myIP:8000" -H "Origin: http://myIP:8000" http://myIP:8000/ws/chat_app/44HsUd/
    

2. Django Channels配置检查

  • 确认settings.py中正确注册Channels:
    INSTALLED_APPS = [
        # ...
        'channels',
        'your_app',
    ]
    
    ASGI_APPLICATION = 'your_project.asgi.application'
    
    CHANNEL_LAYERS = {
        'default': {
            'BACKEND': 'channels.layers.InMemoryChannelLayer', # 开发环境可用,生产需替换为Redis
        },
    }
    
  • 验证asgi.py路由配置:
    import os
    from django.core.asgi import get_asgi_application
    from channels.routing import ProtocolTypeRouter, URLRouter
    from channels.auth import AuthMiddlewareStack
    import your_app.routing
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
    
    application = ProtocolTypeRouter({
      "http": get_asgi_application(),
      "websocket": AuthMiddlewareStack(
          URLRouter(
              your_app.routing.websocket_urlpatterns
          )
      ),
    })
    
  • 确认routing.py的URL匹配规则与客户端请求一致:
    from django.urls import re_path
    from . import consumers
    
    websocket_urlpatterns = [
        re_path(r'ws/chat_app/(?P<unique_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
    ]
    

3. 代码逻辑修复

  • Django端:
    • connect方法中添加异常捕获,避免数据库查询失败导致握手中断:
      async def connect(self):
          self.room_name = self.scope['url_route']['kwargs']['unique_id']
          self.room_group_name = f'video_{self.room_name}'
      
          try:
              self.room_obj = await database_sync_to_async(get_room)(unique_id=self.room_name)
              await self.channel_layer.group_add(
                  self.room_group_name,
                  self.channel_name
              )
              await self.accept()
          except Exception as e:
              print(f"Connect error: {e}")
              await self.close()
      
    • 移除await (self.channel_layer.group_add)的多余括号,规范写法。
  • Flutter端:
    • 添加WebSocket错误监听,便于排查连接问题:
      @override
      void initState() {
        super.initState();
        channel = IOWebSocketChannel.connect(
            'ws://myIP:8000/ws/chat_app/${widget.roomName}/');
        channel.stream.listen(
          (data) {},
          onError: (error) => print('WebSocket error: $error'),
          onDone: () => print('WebSocket closed'),
        );
      }
      
    • 解析服务器返回的字符串数据为JSON,并正确提取YouTube视频ID:
      if (snapshot.hasData) {
        final data = jsonDecode(snapshot.data.toString());
        return YoutubePlayer(
          controller: YoutubePlayerController(
            initialVideoId: YoutubePlayer.convertUrlToId(data['video_url']) ?? '',
            flags: YoutubePlayerFlags(autoPlay: true),
          ),
          showVideoProgressIndicator: true,
        );
      }
      

4. 服务器启动方式验证

  • 必须使用Daphne启动服务器,而非runserver(后者不支持WebSocket):
    daphne your_project.asgi:application
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:15:25