Discord机器人跨服务器消息转发遇Gateway心跳阻塞断开问题求助
问题分析与解决方案
你的机器人出现discord.gateway: Shard ID None heartbeat blocked for more than 10 seconds警告,核心原因是同步代码阻塞了Discord.py的异步事件循环,导致网关心跳无法按时发送,而非单纯的请求量过大。以下是代码中的关键问题和修复方案:
关键问题点
- 同步WebSocket操作阻塞事件循环:你使用的
websocket库是同步实现,receive_json_response里的ws.recv()会卡住整个异步事件循环,让Discord.py无法处理心跳和其他事件。 - 同步HTTP请求拖慢响应:
get_attachment_media里的requests.get是同步请求,会阻塞事件循环,导致消息转发和心跳延迟。 - 错误的心跳实现:用
time.sleep的同步心跳函数,再通过asyncio.run强行运行,直接阻塞了on_ready后的事件循环启动。 - 宽泛的异常捕获:
except: pass会掩盖所有错误,包括WebSocket连接失败、事件解析错误等,无法定位问题。
修复后的代码
import asyncio import json import discord import aiohttp from io import BytesIO from discord.ext import tasks # 配置 bot_token = "你的机器人token" user_token = "你的用户token" target_channel_id = 你的目标频道ID # 替换成实际数字ID client = discord.Client(intents=discord.Intents.all()) # 需开启消息相关意图 # 加载监听频道列表 with open('channels.txt', 'r', encoding='utf-8') as f: monitored_channels = set(f.read().strip().split('\n')) # 用集合提升查询速度 async def send_json_request(ws, request): await ws.send(json.dumps(request)) async def receive_json_response(ws): response = await ws.recv() if response: return json.loads(response) async def get_attachment_media(media): media_array = [] async with aiohttp.ClientSession() as session: for item in media: async with session.get(item['url']) as resp: if resp.status == 200: content = await resp.read() im = BytesIO(content) media_array.append(discord.File(im, filename=item['filename'])) return media_array async def heartbeat(ws, interval): print("心跳任务启动") while True: await asyncio.sleep(interval) heartbeat_json = {"op": 1, "d": None} await send_json_request(ws, heartbeat_json) print("发送心跳") @tasks.loop(seconds=0.1) # 缩短间隔但用异步接收,不会阻塞 async def message_listener(ws): try: event = await receive_json_response(ws) if not event or 'd' not in event: return # 忽略自己发送的消息 if event['d'].get('author', {}).get('id') == '你的用户ID': return channel_id = event['d'].get('channel_id') if channel_id in monitored_channels: content = event['d'].get('content', '') attachments = event['d'].get('attachments', []) media_files = await get_attachment_media(attachments) target_channel = client.get_channel(target_channel_id) if target_channel: await target_channel.send(content, files=media_files) # 处理心跳响应 if event.get('op') == 11: print("收到心跳响应") except Exception as e: print(f"监听任务出错: {str(e)}") @client.event async def on_ready(): print(f"机器人已登录: {client.user}") # 建立异步WebSocket连接 async with aiohttp.ClientSession() as session: async with session.ws_connect("wss://gateway.discord.gg/?v=6&encoding=json") as ws: # 接收初始化事件 init_event = await receive_json_response(ws) heartbeat_interval = init_event['d']['heartbeat_interval'] / 1000 # 发送身份认证 await send_json_request(ws, { "op": 2, "d": { "token": user_token, "properties": { "$os": 'linux', "$browser": 'chrome', "$device": 'pc' } } }) # 启动心跳任务和消息监听任务 heartbeat_task = asyncio.create_task(heartbeat(ws, heartbeat_interval)) listener_task = asyncio.create_task(message_listener(ws)) # 等待任务完成(保持连接) await asyncio.gather(heartbeat_task, listener_task) client.run(bot_token)
核心修改说明
- 替换为异步库:用
aiohttp的异步WebSocket和HTTP请求,彻底避免阻塞事件循环。 - 异步心跳实现:用
asyncio.sleep代替time.sleep,心跳任务作为异步协程运行,不会阻塞主循环。 - 优化频道查询:将监听频道列表转为
set,查询时间从O(n)降到O(1)。 - 精准异常捕获:替换宽泛的
except: pass,打印具体错误信息方便调试。 - 正确的任务管理:用
asyncio.create_task启动异步任务,而非asyncio.run,避免阻塞事件循环。 - 意图权限:将
Intents.default()改为Intents.all()(或按需开启消息相关意图),确保能接收消息事件。
额外注意事项
- Discord禁止使用用户token运行自动化程序,可能导致账号封禁,建议用机器人token监听目标服务器,而非用户token。
- 如果转发量较大,建议添加消息发送的延迟(比如
await asyncio.sleep(1)),避免触发Discord的速率限制。 - 确保在Discord开发者后台开启了
Message Content Intent权限,否则无法读取消息内容。
内容的提问来源于stack exchange,提问作者Ayvengo
相关产品推荐
相关产品推荐

