连接Discord Gateway时WebSocket关闭错误排查求助
Discord WebSocket连接丢失问题排查与解决
问题背景
我正在用Python开发Discord Bot,因discord.py无法满足控制需求,基于websocket-client模块构建自定义API。运行代码时出现WebSocket连接关闭错误,代码及报错信息如下:
运行代码
import requests from websocket import create_connection import json import time from threading import Thread token = "<my token>" class DiscordBot: def __init__(self, token): self.token = token self.header = {"authorization": token, "content-type":"application/json"} def connect(self): self.ws = create_connection("wss://gateway.discord.gg/?v=9&encoding=json") event = self.__recievejsonresponse__() self.heartbeat_interval = event["d"]["heartbeat_interval"]/1000 heartbeatProc = Thread(target=self.heartbeat) heartbeatProc.start() payload = { "op":2, "d":{ "token":self.token, "intents":8, "properties":{ "os":"linux", "browser":"chromium", "device":"pc" }, } } self.__sendjsonrequest__(payload) self.__recieveloop__() def __recieveloop__(self): while True: event = self.__recievejsonresponse__() print(event) self.prevEvent = event try: #print(f"{author}:{content}") op_code = event["op"] if op_code == 11: print("heartbeat recievedd") except: pass def heartbeat(self): print("heartbeating") heartbeatJSON = { "op": 1, "d": "null" } self.__sendjsonrequest__(heartbeatJSON) while True: time.sleep(self.heartbeat_interval) heartbeatJSON = { "op": 1, "d": "null" } self.__sendjsonrequest__(heartbeatJSON) def __recievejsonresponse__(self): response = self.ws.recv() if response: return json.loads(response) def __sendjsonrequest__(self, request): self.ws.send(json.dumps(request))
错误日志
{'t': None, 's': None, 'op': 11, 'd': None} heartbeat recievedd None Traceback (most recent call last): File "c:\Downloads\learn.py", line 153, in <module> myBot.connect() File "c:\Downloads\learn.py", line 34, in connect self.__recieveloop__() File "c:\Downloads\learn.py", line 38, in __recieveloop__ event = self.__recievejsonresponse__() File "c:\Downloads\learn.py", line 110, in __recievejsonresponse__ response = self.ws.recv() File "C:\Apps\Python310\site-packages\websocket\_core.py", line 362, in recv opcode, data = self.recv_data() File "C:\Apps\Python310\site-packages\websocket\_core.py", line 385, in recv_data opcode, frame = self.recv_data_frame(control_frame) File "C:\Apps\Python310\site-packages\websocket\_core.py", line 406, in recv_data_frame frame = self.recv_frame() File "C:\Apps\Python310\site-packages\websocket\_core.py", line 445, in recv_frame return self.frame_buffer.recv_frame() File "C:\Apps\Python310\site-packages\websocket\_abnf.py", line 338, in recv_frame self.recv_header() File "C:\Apps\Python310\site-packages\websocket\_abnf.py", line 294, in recv_header header = self.recv_strict(2) File "C:\Apps\Python310\site-packages\websocket\_abnf.py", line 373, in recv_strict bytes_ = self.recv(min(16384, shortage)) File "C:\Apps\Python310\site-packages\websocket\_core.py", line 529, in _recv return recv(self.sock, bufsize) File "C:\Apps\Python310\site-packages\websocket\_socket.py", line 122, in recv raise WebSocketConnectionClosedException( websocket._exceptions.WebSocketConnectionClosedException: Connection to remote host was lost.
问题原因分析
- 心跳包格式错误:Discord网关要求心跳包的
d字段为上次收到事件的s(序列号),首次心跳可传null,但代码中传的是字符串"null",不符合JSON规范,网关会判定无效并断开连接。 - 心跳触发逻辑错误:代码启动线程后立刻发送心跳,但正确逻辑应该是在身份验证完成后,按照
heartbeat_interval定时发送,无需提前发送首次心跳。 - 未处理空响应:当连接关闭时,
ws.recv()会返回空值,代码中直接尝试解析会导致后续逻辑崩溃。 - 缺少重连机制:Discord网关可能因负载或会话超时主动断开连接,代码未处理重连逻辑。
修复方案
- 修正心跳包
d字段:保存每次收到事件的s值,心跳时传入该值,首次心跳传None(对应JSON的null)。 - 调整心跳触发时机:在身份验证完成后再启动心跳线程,严格按照
heartbeat_interval定时发送。 - 添加空响应处理:在接收函数中检查响应为空时,触发重连逻辑。
- 处理网关重连事件:监听
op 7(重新连接)和op 10(心跳间隔更新)事件,执行对应操作。
修正后的代码
import requests from websocket import create_connection import json import time from threading import Thread from websocket._exceptions import WebSocketConnectionClosedException token = "<my token>" class DiscordBot: def __init__(self, token): self.token = token self.header = {"authorization": token, "content-type":"application/json"} self.heartbeat_interval = None self.last_seq = None self.ws = None self.heartbeat_thread = None self.running = False def connect(self): while not self.running: try: # 建立WebSocket连接 self.ws = create_connection("wss://gateway.discord.gg/?v=9&encoding=json") # 接收初始op 10事件(包含心跳间隔) event = self.__recievejsonresponse__() if event["op"] == 10: self.heartbeat_interval = event["d"]["heartbeat_interval"] / 1000 print(f"Heartbeat interval set to {self.heartbeat_interval}s") # 启动心跳线程 self.running = True self.heartbeat_thread = Thread(target=self.heartbeat, daemon=True) self.heartbeat_thread.start() # 发送身份验证payload payload = { "op": 2, "d": { "token": self.token, "intents": 8, "properties": { "os": "linux", "browser": "chromium", "device": "pc" }, } } self.__sendjsonrequest__(payload) print("Authenticated successfully") # 启动接收循环 self.__recieveloop__() except WebSocketConnectionClosedException: print("Connection lost, reconnecting...") self.running = False time.sleep(5) # 重连前等待5秒 except Exception as e: print(f"Error connecting: {e}") self.running = False time.sleep(5) def __recieveloop__(self): while self.running: try: event = self.__recievejsonresponse__() if not event: continue print(event) # 更新最后收到的序列号 if event["s"] is not None: self.last_seq = event["s"] op_code = event["op"] if op_code == 11: print("Heartbeat acknowledged") elif op_code == 7: # 网关要求重新连接 print("Received reconnect command, restarting connection") self.running = False self.ws.close() elif op_code == 10: # 更新心跳间隔 self.heartbeat_interval = event["d"]["heartbeat_interval"] / 1000 print(f"Updated heartbeat interval to {self.heartbeat_interval}s") except Exception as e: print(f"Error receiving event: {e}") self.running = False def heartbeat(self): while self.running: try: time.sleep(self.heartbeat_interval) if self.running: heartbeat_payload = { "op": 1, "d": self.last_seq # 用最后收到的序列号,首次为None } self.__sendjsonrequest__(heartbeat_payload) print("Sent heartbeat") except Exception as e: print(f"Heartbeat error: {e}") break def __recievejsonresponse__(self): try: response = self.ws.recv() if response: return json.loads(response) else: self.running = False return None except WebSocketConnectionClosedException: self.running = False return None def __sendjsonrequest__(self, request): try: self.ws.send(json.dumps(request)) except WebSocketConnectionClosedException: self.running = False # 启动Bot if __name__ == "__main__": myBot = DiscordBot(token) myBot.connect()
内容的提问来源于stack exchange,提问作者birdwhocancode
相关产品推荐
相关产品推荐

