ผมเคยเจอปัญหานี้จนแทบจะทิ้งโปรเจกต์ทั้งหมด — บอทเทรดที่เขียนไว้ ตัดการเชื่อมต่อ 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 ดังนี้
- WebSocket connections: สูงสุด 5 connection ต่อ IP
- ถ้าต้องการเชื่อมต่อ stream หลายตัว ให้รวมใน connection เดียว (ดูวิธีที่ 2)
- รออย่างน้อย 1 วินาที ก่อนสร้าง connection ใหม่หลังจากปิด
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 ให้เหมาะสม
- Location: เลือก server ใกล้ Singapore หรือ Hong Kong จะเร็วกว่า server อื่น
- Environment: ใช้ VPS หรือ cloud server ที่มี uptime สูง อย่าใช้ computer บ้าน
- Monitoring: ตั้งสคริปต์เช็คว่า process ยังทำงานอยู่หรือไม่
# สคริปต์เช็คและ 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: โปรแกรม command line สำหรับทดสอบ WebSocket
- Chrome DevTools: Network tab ดู WebSocket frames
- WebSocket King: เว็บไซต์ทดสอบ connection
# วิธีติดตั้ง 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 ก่อนติดตั้งระบบจริง
- ✅ ตั้งค่า
ping_intervalไม่เกิน 60 วินาที - ✅ เพิ่ม auto reconnect logic
- ✅ จำกัดจำนวน connection ไม่เกิน 5 ต่อ IP
- ✅ เพิ่ม logging ทุก event
- ✅ ตั้งค่า monitoring และ alert
- ✅ ใช้ server ที่เสถียร (VPS, cloud)
- ✅ เตรียม REST API เป็น backup หลัง reconnect
สิ่งที่ควรทำเพิ่มเติม
จากประสบการณ์ของผม การแก้ WebSocket disconnect เป็นแค่จุดเริ่มต้น ยิ่งพัฒนาระบบต่อ จะเจอความท้าทายอื่น ๆ อีกมาก เช่น การจัดการ order, การวิเคราะห์ข้อมูล, การตัดสินใจซื้อขายอย่างมีประสิทธิภาพ
ผมเริ่มใช้ HolySheep AI ในการช่วยวิเคราะห์ข้อมูลและเขียนโค้ดที่ซับซ้อน ประหยัดเวลาได้มาก โดย