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, ); }
- 添加WebSocket错误监听,便于排查连接问题:
4. 服务器启动方式验证
- 必须使用Daphne启动服务器,而非
runserver(后者不支持WebSocket):daphne your_project.asgi:application
内容的提问来源于stack exchange,提问作者Geo48
相关产品推荐
相关产品推荐

