ผมเคยเจอปัญหานี้จนแทบจะทิ้งโปรเจกต์ทั้งหมด — บอทเทรดที่เขียนไว้ ตัดการเชื่อมต่อ WebSocket ทุก 5-10 นาที โดยไม่มีสัญญาณเตือน ส่งผลให้พลาดคำสั่งซื้อขายที่สำคัญ สูญเสียโอกาสไปเยอะมาก จากประสบการณ์ตรงของผม วันนี้จะมาแชร์วิธีแก้ปัญหาทีละขั้นตอน เข้าใจง่าย ไม่ต้องมีพื้นฐาน API มาก่อนก็ทำได้

WebSocket คืออะไร ทำไมต้องเข้าใจก่อนแก้ปัญหา

WebSocket เปรียบเหมือนท่อสื่อสารระหว่างโปรแกรมของเรากับเซิร์ฟเวอร์ของ Binance ต่างจากการเรียก API ปกติที่ส่งคำขอแล้วรอรับคำตอบ WebSocket จะเปิดความเชื่อมต่อค้างไว้ และเซิร์ฟเวอร์จะส่งข้อมูลมาให้เรื่อย ๆ แบบเรียลไทม์ เหมาะมากสำหรับงานที่ต้องรับข้อมูลราคาตลอดเวลา

ปัญหาคือ ท่อนี้ไม่ได้เปิดไว้ตลอดไป มีหลายสาเหตุที่ทำให้เกิดการตัดการเชื่อมต่อ

สาเหตุหลัก 5 ข้อที่ทำให้ Binance WebSocket ตัดการเชื่อมต่อ

1. Server ไม่ได้รับ Heartbeat

Binance เซิร์ฟเวอร์จะตัดการเชื่อมต่อถ้าไม่มีข้อมูลวิ่งผ่านท่อเกิน 3 นาที ถ้าโปรแกรมเรานิ่งไม่มีการส่งอะไรเลย server จะคิดว่าเราหลุดแล้ว เลยตัดเราออก

2. อินเทอร์เน็ตหลุดหรือเปลี่ยน IP

ถ้า IP เปลี่ยนระหว่างใช้งาน (เช่น รีสตาร์ทเราเตอร์) connection เดิมจะใช้ไม่ได้ทันที ต้องเปิดใหม่

3. Rate Limit ถูกบล็อก

Binance มีข้อจำกัดจำนวน connection ต่อ IP ถ้าสร้าง connection ใหม่เร็วเกินไปโดยไม่ปิดตัวเดิม จะโดนบล็อกชั่วคราว

4. เวอร์ชัน API เปลี่ยน

Binance อัปเดต API เป็นประจำ ถ้าโค้ดเราใช้ endpoint เก่าอาจทำให้การเชื่อมต่อล้มเหลว

5. ข้อผิดพลาดจากโค้ดเราเอง

Exception ที่ไม่ได้จัดการ ทำให้โปรแกรมหยุดทำงานโดยไม่รู้ตัว

วิธีตรวจจับว่า WebSocket ตัดแล้ว

ก่อนจะแก้ปัญหา ต้องรู้ก่อนว่ามันตัดตอนไหน ผมแนะนำให้เพิ่มโค้ด logging ตามนี้

import websocket
import logging
import time

ตั้งค่า logging

logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class MyWebSocket: def __init__(self, url): self.url = url self.ws = None self.last_ping = time.time() def on_message(self, ws, message): logger.info(f"ได้รับข้อความ: {message}") self.last_ping = time.time() def on_error(self, ws, error): logger.error(f"เกิดข้อผิดพลาด: {error}") def on_close(self, ws, close_status_code, close_msg): logger.warning(f"การเชื่อมต่อปิดแล้ว - รหัส: {close_status_code}, ข้อความ: {close_msg}") def on_open(self, ws): logger.info("เชื่อมต่อสำเร็จแล้ว") def connect(self): self.ws = websocket.WebSocketApp( self.url, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close, on_open=self.on_open ) self.ws.run_forever(ping_interval=30, ping_timeout=10)

สังเกตว่าในโค้ดมี ping_interval=30 คือส่ง ping ไปที่ server ทุก 30 วินาที ป้องกันไม่ให้ server ตัดเราออกเพราะไม่มี heartbeat

วิธีแก้ปัญหาที่ 1: เพิ่ม Auto Reconnect

นี่คือสิ่งสำคัญที่สุด ต้องทำให้โปรแกรมพยายามเชื่อมต่อใหม่อัตโนมัติเมื่อหลุด

import websocket
import time
import threading

class AutoReconnectWebSocket:
    def __init__(self, url, max_retries=10, retry_delay=5):
        self.url = url
        self.max_retries = max_retries
        self.retry_delay = retry_delay
        self.ws = None
        self.running = True
        self.retry_count = 0
        
    def on_message(self, ws, message):
        # ประมวลผลข้อความที่ได้รับ
        print(f"ได้รับ: {message}")
        
    def on_error(self, ws, error):
        print(f"ข้อผิดพลาด: {error}")
        
    def on_close(self, ws, close_status_code, close_msg):
        print(f"ปิดการเชื่อมต่อ - {close_status_code}: {close_msg}")
        self.retry_count += 1
        
        # พยายามเชื่อมต่อใหม่ถ้ายังไม่เกินจำนวนครั้งที่กำหนด
        if self.running and self.retry_count <= self.max_retries:
            print(f"จะพยายามเชื่อมต่อใหม่ใน {self.retry_delay} วินาที... (ครั้งที่ {self.retry_count})")
            time.sleep(self.retry_delay)
            self.connect()
        elif self.retry_count > self.max_retries:
            print("เชื่อมต่อไม่สำเร็จหลายครั้ง หยุดการพยายาม")
            
    def on_open(self, ws):
        print("เชื่อมต่อสำเร็จ - รีเซ็ตจำนวนครั้งที่ลองใหม่")
        self.retry_count = 0
        
    def connect(self):
        self.ws = websocket.WebSocketApp(
            self.url,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close,
            on_open=self.on_open
        )
        # ping_interval ต้องน้อยกว่า 3 นาที (180 วินาที)
        self.ws.run_forever(ping_interval=60, ping_timeout=10)
        
    def stop(self):
        self.running = False
        if self.ws:
            self.ws.close()

วิธีใช้

if __name__ == "__main__": # ตัวอย่าง: เชื่อมต่อ Binance stream ราคา BTC url = "wss://stream.binance.com:9443/ws/btcusdt@trade" ws = AutoReconnectWebSocket(url, max_retries=10, retry_delay=5) try: ws.connect() except KeyboardInterrupt: ws.stop() print("หยุดการทำงาน")

วิธีแก้ปัญหาที่ 2: ใช้หลาย Connection แยกกัน

ถ้าต้องรับข้อมูลหลาย stream (เช่น ราคาหลายเหรียญ) อย่าใส่ทุกอย่างใน connection เดียว ให้แยก connection ตามกลุ่ม

import websocket
import threading
import time
import json

class BinanceMultiStream:
    def __init__(self):
        self.streams = {}
        self.connections = {}
        
    def subscribe(self, streams, callback):
        """
        streams: list ของชื่อ stream เช่น ['btcusdt@trade', 'ethusdt@trade']
        callback: function ที่จะถูกเรียกเมื่อได้รับข้อความ
        """
        # รวม streams เป็น path
        stream_path = '/'.join(streams)
        url = f"wss://stream.binance.com:9443/stream?streams={stream_path}"
        
        # สร้าง callback ที่กรองข้อความไปยัง function ที่ถูกต้อง
        def wrapped_callback(ws, message):
            data = json.loads(message)
            stream_name = data.get('stream', '')
            stream_data = data.get('data', {})
            callback(stream_name, stream_data)
            
        ws = websocket.WebSocketApp(
            url,
            on_message=wrapped_callback,
            on_error=lambda ws, err: print(f"ข้อผิดพลาด: {err}"),
            on_close=lambda ws, code, msg: print(f"ปิด: {code} - {msg}"),
            on_open=lambda ws: print(f"เปิด streams: {streams}")
        )
        
        self.connections[stream_path] = ws
        thread = threading.Thread(target=ws.run_forever, kwargs={'ping_interval': 60})
        thread.daemon = True
        thread.start()
        
    def unsubscribe(self, streams):
        stream_path = '/'.join(streams)
        if stream_path in self.connections:
            self.connections[stream_path].close()
            del self.connections[stream_path]
            
    def close_all(self):
        for ws in self.connections.values():
            ws.close()
        self.connections.clear()

วิธีใช้

def my_callback(stream, data): print(f"Stream: {stream}") print(f"ราคา: {data.get('p', 'N/A')}, ปริมาณ: {data.get('q', 'N/A')}") ws_manager = BinanceMultiStream()

รับข้อมูลราคา BTC และ ETH

ws_manager.subscribe(['btcusdt@trade', 'ethusdt@trade'], my_callback)

รอรับข้อมูล

try: while True: time.sleep(1) except KeyboardInterrupt: ws_manager.close_all()

วิธีแก้ปัญหาที่ 3: จัดการ Rate Limit อย่างถูกต้อง

Binance มีกฎเกณฑ์เรื่องจำนวน connection ต่อ IP ดังนี้

import time
import threading
from collections import deque

class RateLimitManager:
    def __init__(self, max_connections=5, cooldown_seconds=1):
        self.max_connections = max_connections
        self.cooldown_seconds = cooldown_seconds
        self.active_connections = 0
        self.connection_timestamps = deque()
        self.lock = threading.Lock()
        
    def acquire(self):
        """ขออนุญาตสร้าง connection ใหม่"""
        with self.lock:
            # ลบ timestamp เก่าที่เกิน cooldown
            now = time.time()
            while self.connection_timestamps and now - self.connection_timestamps[0] >= self.cooldown_seconds:
                self.connection_timestamps.popleft()
                
            # ตรวจสอบจำนวน connection ที่ยัง active
            self.active_connections = len(self.connection_timestamps)
            
            if self.active_connections >= self.max_connections:
                # ต้องรอ
                wait_time = self.cooldown_seconds - (now - self.connection_timestamps[0]) + 0.1
                print(f"ต้องรอ {wait_time:.2f} วินาที ก่อนสร้าง connection ใหม่")
                time.sleep(wait_time)
                return self.acquire()  # ลองใหม่
                
            # อนุญาตให้สร้าง connection
            self.connection_timestamps.append(time.time())
            return True
            
    def release(self):
        """บอกว่า connection ถูกปิดแล้ว"""
        with self.lock:
            if self.connection_timestamps:
                self.connection_timestamps.popleft()
                

วิธีใช้ร่วมกับ WebSocket

rate_limiter = RateLimitManager(max_connections=5, cooldown_seconds=1) def create_connection_with_limit(url, on_message): rate_limiter.acquire() def on_close(ws, close_status_code, close_msg): rate_limiter.release() ws = websocket.WebSocketApp( url, on_message=on_message, on_close=on_close, on_error=lambda ws, err: rate_limiter.release() ) return ws

การตั้งค่า Server ที่เหมาะสม

ถ้าใช้งานในระยะยาว ควรตั้งค่า server ให้เหมาะสม

# สคริปต์เช็คและ restart process อัตโนมัติ
import os
import sys
import subprocess
import time

def check_process(name):
    """ตรวจสอบว่า process ยังทำงานอยู่ไหม"""
    try:
        result = subprocess.run(['pgrep', '-f', name], capture_output=True)
        return result.returncode == 0
    except:
        return False

def main():
    script_name = sys.argv[1] if len(sys.argv) > 1 else 'bot.py'
    
    while True:
        if not check_process(script_name):
            print(f"ไม่พบ process {script_name} - กำลัง restart")
            subprocess.Popen([sys.executable, script_name])
            time.sleep(5)  # รอให้ process เริ่มทำงาน
        time.sleep(30)  # เช็คทุก 30 วินาที

if __name__ == "__main__":
    main()

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

กรณีที่ 1: ข้อผิดพลาด "ConnectionRefusedError" หรือ "ConnectionResetError"

สาเหตุ: Firewall หรือ network บล็อกการเชื่อมต่อ หรือ internet หลุด

# วิธีแก้: เพิ่ม retry logic พร้อม exponential backoff
import time

def connect_with_retry(url, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            ws = websocket.create_connection(url, timeout=10)
            print("เชื่อมต่อสำเร็จ")
            return ws
        except (websocket.WebSocketConnectionClosedException, 
                ConnectionRefusedError,
                ConnectionResetError) as e:
            # Exponential backoff: รอนานขึ้นเรื่อย ๆ
            wait_time = (2 ** attempt) + random.uniform(0, 1)
            print(f"เชื่อมต่อไม่สำเร็จ (ครั้งที่ {attempt + 1}): {e}")
            print(f"จะลองใหม่ใน {wait_time:.2f} วินาที")
            time.sleep(wait_time)
            
    raise Exception(f"เชื่อมต่อไม่สำเร็จหลังจากลอง {max_attempts} ครั้ง")

กรณีที่ 2: ได้รับข้อความ '{"error": "Max connections"}'

สาเหตุ: เกินจำนวน connection สูงสุดที่ Binance อนุญาต

# วิธีแก้: ตรวจสอบและปิด connection เก่าก่อน
import json

def on_message(ws, message):
    try:
        data = json.loads(message)
        if 'error' in data:
            error_code = data['error'].get('code', '')
            if error_code == -1003:  # Too many requests
                print("เกิน rate limit - รอ 60 วินาที")
                time.sleep(60)
                # ส่ง subscription ใหม่
                ws.send(json.dumps({"method": "SUBSCRIBE", "params": ["btcusdt@trade"], "id": 1}))
            return
        # ประมวลผลข้อความปกติ
        process_data(data)
    except json.JSONDecodeError:
        pass

กรณีที่ 3: Process หยุดทำงานโดยไม่มี error log

สาเหตุ: Unhandled exception หรือ memory leak

# วิธีแก้: เพิ่ม global exception handler และ logging
import sys
import traceback
import logging

ตั้งค่า logging

logging.basicConfig( filename='websocket.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s' ) def global_exception_handler(exc_type, exc_value, exc_traceback): if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return logging.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) print("เกิดข้อผิดพลาดที่ไม่คาดคิด - ดู log เพิ่มเติม") # ส่ง notification (ถ้าต้องการ) send_alert(str(exc_value))

ลงทะเบียน exception handler

sys.excepthook = global_exception_handler

ตัวอย่างการส่ง alert ผ่าน HolySheep AI

def send_alert(message): import requests # ส่งข้อความไปยัง LINE, Discord หรือ Telegram # หรือใช้ AI วิเคราะห์ปัญหา pass

กรณีที่ 4: ข้อมูลที่ได้รับซ้ำหรือขาดหาย

สาเหตุ: Connection หลุดแล้ว reconnect แต่ miss event ระหว่างนั้น

# วิธีแก้: ใช้ REST API ดึงข้อมูลล่าสุดหลัง reconnect
class WebSocketWithSync:
    def __init__(self, api_key=None, api_secret=None):
        self.last_event_id = None
        self.base_url = "https://api.binance.com"
        
    def on_open(self, ws):
        # เมื่อเปิด connection ใหม่ ดึงข้อมูลล่าสุดก่อน
        latest_data = self.get_latest_trades()
        print(f"ดึงข้อมูลล่าสุด {len(latest_data)} รายการหลัง reconnect")
        
    def get_latest_trades(self, symbol='BTCUSDT', limit=20):
        """ดึง recent trades ผ่าน REST API"""
        import requests
        url = f"{self.base_url}/api/v3/trades"
        params = {'symbol': symbol, 'limit': limit}
        
        try:
            response = requests.get(url, params=params)
            return response.json()
        except Exception as e:
            print(f"ดึงข้อมูลไม่สำเร็จ: {e}")
            return []

เครื่องมือที่ช่วย Debug WebSocket

เวลาแก้ปัญหา ควรมีเครื่องมือตรวจสอบที่ดี

# วิธีติดตั้ง wscat

pip install ws

ทดสอบ connection กับ Binance

wscat -c wss://stream.binance.com:9443/ws/btcusdt@trade

ควรเห็นข้อความแบบนี้

Connected to wss://stream.binance.com:9443/ws/btcusdt@trade

< {"e":"trade","E":1234567890,"s":"BTCUSDT","p":"50000.00","q":"0.001"}

สรุป Checklist ก่อนติดตั้งระบบจริง

สิ่งที่ควรทำเพิ่มเติม

จากประสบการณ์ของผม การแก้ WebSocket disconnect เป็นแค่จุดเริ่มต้น ยิ่งพัฒนาระบบต่อ จะเจอความท้าทายอื่น ๆ อีกมาก เช่น การจัดการ order, การวิเคราะห์ข้อมูล, การตัดสินใจซื้อขายอย่างมีประสิทธิภาพ

ผมเริ่มใช้ HolySheep AI ในการช่วยวิเคราะห์ข้อมูลและเขียนโค้ดที่ซับซ้อน ประหยัดเวลาได้มาก โดย