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

连接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.

问题原因分析

  1. 心跳包格式错误:Discord网关要求心跳包的d字段为上次收到事件的s(序列号),首次心跳可传null,但代码中传的是字符串"null",不符合JSON规范,网关会判定无效并断开连接。
  2. 心跳触发逻辑错误:代码启动线程后立刻发送心跳,但正确逻辑应该是在身份验证完成后,按照heartbeat_interval定时发送,无需提前发送首次心跳。
  3. 未处理空响应:当连接关闭时,ws.recv()会返回空值,代码中直接尝试解析会导致后续逻辑崩溃。
  4. 缺少重连机制:Discord网关可能因负载或会话超时主动断开连接,代码未处理重连逻辑。

修复方案

  1. 修正心跳包d字段:保存每次收到事件的s值,心跳时传入该值,首次心跳传None(对应JSON的null)。
  2. 调整心跳触发时机:在身份验证完成后再启动心跳线程,严格按照heartbeat_interval定时发送。
  3. 添加空响应处理:在接收函数中检查响应为空时,触发重连逻辑。
  4. 处理网关重连事件:监听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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 03:42:19