Tornado WebSocket二次连接收消息异常及无Django周期性更新实现咨询
Hey there, let's work through this WebSocket issue where your browser needs a second connection to receive messages. Based on what you've shared about your setup (using Tornado, PeriodicCallback, Redis, and tornado-redis), here are the most likely causes and actionable fixes:
Key Background & Problem Breakdown
You're building a periodic update system, currently testing outside Django, with a minimal WebSocket handler. The core issue is that the first browser connection doesn't receive messages—only the second one does. This almost always ties to subscriber registration logic gaps or asynchronous setup timing issues.
First, Let's Audit Your Handler & Application Logic
Looking at your partial handler code:
class MyHandler(WebSocketHandler): def check_origin(self, origin): return True def open(self, user): self.sprint = user self.uid = uuid.uuid4().hex self.application.add_subscriber(self.sprint, self) def on_message(self, message): c = Pe... # Truncated Redis logic
Here are the top things to check:
1. Fix Subscriber Storage in Your Application
If your add_subscriber method uses a regular dictionary to track subscribers, the first connection might fail to register because the sprint key doesn't exist yet. Use a defaultdict to ensure the subscriber set is initialized automatically:
from collections import defaultdict from tornado.web import Application class MyApplication(Application): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Initialize subscriber storage with empty sets for new sprints self.subscribers = defaultdict(set) def add_subscriber(self, sprint, handler): # Add a debug log to confirm registration works on first connect print(f"Adding subscriber for sprint {sprint}: {handler.uid}") self.subscribers[sprint].add(handler)
2. Ensure PeriodicCallback Uses Fresh Subscriber Data
If your periodic callback caches subscriber lists instead of fetching the latest from the application, it might miss the first connection. Always pull the current subscriber set when sending updates:
from tornado.ioloop import PeriodicCallback import tornado_redis class MyApplication(Application): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.subscribers = defaultdict(set) self.redis_client = tornado_redis.Client() self.redis_client.connect() # Start the periodic callback self.callback = PeriodicCallback(self.send_updates, 5000) # 5-second interval self.callback.start() async def send_updates(self): for sprint, handlers in self.subscribers.items(): # Fetch fresh data from Redis data = await self.redis_client.get(f"sprint:{sprint}") if not data: continue # Send to all active subscribers, clean up closed connections active_handlers = [] for handler in handlers: try: handler.write_message(data) active_handlers.append(handler) except WebSocketClosedError: print(f"Removing closed subscriber {handler.uid}") # Update the subscriber set with only active connections self.subscribers[sprint] = set(active_handlers)
3. Fix Asynchronous Redis Subscription Timing
If you're using Redis pub/sub instead of periodic pulls, make sure the subscription completes before the first message is sent. Use async/await to ensure the Redis connection is ready in your handler's open method:
class MyHandler(WebSocketHandler): async def open(self, user): self.sprint = user self.uid = uuid.uuid4().hex # Initialize Redis subscriber asynchronously self.redis_sub = tornado_redis.Client() await self.redis_sub.connect() # Wait for subscription to complete before registering await self.redis_sub.subscribe(f"sprint:{self.sprint}") self.application.add_subscriber(self.sprint, self) # Start listening for Redis messages in a background task self.io_loop.add_callback(self.listen_for_redis_messages) async def listen_for_redis_messages(self): while True: msg = await self.redis_sub.listen() if msg and msg.kind == 'message': try: self.write_message(msg.body) except WebSocketClosedError: self.application.subscribers[self.sprint].remove(self) break
4. Verify Browser-Side Connection Logic
Sometimes the issue is on the frontend—make sure you're not trying to receive messages before the WebSocket connection is fully open:
const ws = new WebSocket(`ws://localhost:8888/${sprintId}`); ws.onopen = () => { console.log('WebSocket connection established'); // Avoid sending/receiving logic here that relies on immediate readiness }; ws.onmessage = (event) => { console.log('Received update:', event.data); // Process your message here }; ws.onerror = (error) => { console.error('WebSocket error:', error); };
Quick Debugging Tips
- Add print statements or logging in
add_subscriberand your periodic callback to confirm:- The first connection is being registered in the subscriber set
- The callback is iterating over the correct number of subscribers
- Check if Redis is actually publishing messages when you expect it—use
redis-cli PUBLISH sprint:your_sprint "test message"to test manually
内容的提问来源于stack exchange,提问作者DmitriyM

