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

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_subscriber and 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:11:44