WebSocket 心跳機制
概述
HabitTrade API 的 WebSocket 連接支援服務端心跳機制,確保連接的穩定性和可靠性。系統會定期向所有活躍連接發送心跳消息,幫助客戶端檢測連接狀態。
心跳消息格式
服務端每 30 秒會自動向所有 WebSocket 連接發送心跳消息:
json
{
"type": "heartbeat",
"data": {
"timestamp": 1703123456789
}
}字段說明
type: 消息類型,心跳消息固定為 "heartbeat"data.timestamp: 心跳發送時的 Unix 時間戳(毫秒級)
適用端點
心跳機制適用於以下 WebSocket 端點:
交易端點
/ws/trade/*- 交易 WebSocket 連接
行情端點
/ws/market/*- 所有市場行情 WebSocket 連接
客戶端處理建議
1. 心跳監控
建議客戶端監控心跳消息來檢測連接健康狀態:
Python
import json
import time
def on_message(ws, message):
data = json.loads(message)
if data.get('type') == 'heartbeat':
print(f"收到心跳: {data['data']['timestamp']}")
global last_heartbeat_time
last_heartbeat_time = time.time() * 1000
# 處理其他業務消息...Java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public void onMessage(String message) {
ObjectMapper mapper = new ObjectMapper();
JsonNode data = mapper.readTree(message);
if ("heartbeat".equals(data.get("type").asText())) {
System.out.println("收到心跳: " + data.get("data").get("timestamp").asLong());
lastHeartbeatTime = System.currentTimeMillis();
}
// 處理其他業務消息...
}C#
using Newtonsoft.Json.Linq;
private void OnMessage(string message)
{
var data = JObject.Parse(message);
if (data["type"]?.ToString() == "heartbeat")
{
Console.WriteLine($"收到心跳: {data["data"]["timestamp"]}");
lastHeartbeatTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
// 處理其他業務消息...
}go
import (
"encoding/json"
"fmt"
"time"
)
type HeartbeatMessage struct {
Type string `json:"type"`
Data struct {
Timestamp int64 `json:"timestamp"`
} `json:"data"`
}
func onMessage(message []byte) {
var msg HeartbeatMessage
json.Unmarshal(message, &msg)
if msg.Type == "heartbeat" {
fmt.Printf("收到心跳: %d\n", msg.Data.Timestamp)
lastHeartbeatTime = time.Now().UnixMilli()
}
// 處理其他業務消息...
}javascript
websocket.onmessage = function(event) {
const message = JSON.parse(event.data);
if (message.type === 'heartbeat') {
console.log('收到心跳:', new Date(message.data.timestamp));
// 更新最後心跳時間
lastHeartbeatTime = Date.now();
}
// 處理其他業務消息...
};2. 連接超時檢測
可以實現客戶端超時檢測機制:
Python
import threading
import time
def check_heartbeat_timeout():
while True:
current_time = time.time() * 1000
if current_time - last_heartbeat_time > 60000:
print("心跳超時,準備重新連接")
reconnect()
time.sleep(10) # 每 10 秒檢查一次
# 啟動心跳檢查線程
heartbeat_thread = threading.Thread(target=check_heartbeat_timeout)
heartbeat_thread.daemon = True
heartbeat_thread.start()Java
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
long now = System.currentTimeMillis();
if (now - lastHeartbeatTime > 60000) {
System.out.println("心跳超時,準備重新連接");
reconnect();
}
}, 10, 10, TimeUnit.SECONDS); // 每 10 秒檢查一次C#
using System;
using System.Threading;
private Timer heartbeatTimer;
private void StartHeartbeatCheck()
{
heartbeatTimer = new Timer(CheckHeartbeatTimeout, null,
TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
}
private void CheckHeartbeatTimeout(object state)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (now - lastHeartbeatTime > 60000)
{
Console.WriteLine("心跳超時,準備重新連接");
Reconnect();
}
}go
import "time"
func startHeartbeatCheck() {
ticker := time.NewTicker(10 * time.Second)
go func() {
for range ticker.C {
now := time.Now().UnixMilli()
if now - lastHeartbeatTime > 60000 {
fmt.Println("心跳超時,準備重新連接")
reconnect()
}
}
}()
}javascript
// 檢查心跳超時(建議設置為 60 秒)
setInterval(() => {
const now = Date.now();
if (now - lastHeartbeatTime > 60000) {
console.log('心跳超時,準備重新連接');
// 重新建立連接
reconnect();
}
}, 10000); // 每 10 秒檢查一次3. 忽略心跳消息
如果不需要心跳功能,可以安全地忽略這些消息:
Python
import json
def on_message(ws, message):
data = json.loads(message)
# 忽略心跳消息
if data.get('type') == 'heartbeat':
return
# 處理業務消息
handle_business_message(data)Java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public void onMessage(String message) {
ObjectMapper mapper = new ObjectMapper();
JsonNode data = mapper.readTree(message);
// 忽略心跳消息
if ("heartbeat".equals(data.get("type").asText())) {
return;
}
// 處理業務消息
handleBusinessMessage(data);
}C#
using Newtonsoft.Json.Linq;
private void OnMessage(string message)
{
var data = JObject.Parse(message);
// 忽略心跳消息
if (data["type"]?.ToString() == "heartbeat")
{
return;
}
// 處理業務消息
HandleBusinessMessage(data);
}go
import "encoding/json"
func onMessage(message []byte) {
var data map[string]interface{}
json.Unmarshal(message, &data)
// 忽略心跳消息
if data["type"] == "heartbeat" {
return
}
// 處理業務消息
handleBusinessMessage(data)
}javascript
websocket.onmessage = function(event) {
const message = JSON.parse(event.data);
// 忽略心跳消息
if (message.type === 'heartbeat') {
return;
}
// 處理業務消息
handleBusinessMessage(message);
};客戶端斷連處理
正確的斷連流程
客戶端主動斷開連接時,需要發送關閉幀以確保連接正確關閉:
Python
def close_connection():
if ws and not ws.closed:
ws.close(code=1000, reason='Client closing connection')
print("已發送關閉幀")Java
public void closeConnection() {
if (webSocket != null && webSocket.isOpen()) {
webSocket.close(1000, "Client closing connection");
System.out.println("已發送關閉幀");
}
}C#
public async Task CloseConnection()
{
if (webSocket != null && webSocket.State == WebSocketState.Open)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure,
"Client closing connection", CancellationToken.None);
Console.WriteLine("已發送關閉幀");
}
}Go
func closeConnection() {
if conn != nil {
err := conn.WriteMessage(websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure,
"Client closing connection"))
if err == nil {
fmt.Println("已發送關閉幀")
}
conn.Close()
}
}javascript
// 正確的斷連方式
function closeConnection() {
if (websocket && websocket.readyState === WebSocket.OPEN) {
// 發送關閉幀
websocket.close(1000, 'Client closing connection');
console.log('已發送關閉幀');
}
}斷連過程說明
完整的 WebSocket 斷連過程包括以下步驟:
- 客戶端發起關閉: 調用
websocket.close()發送關閉幀 - 服務端確認: 服務端收到關閉幀後發送確認
- 連接關閉: 雙方完成握手,TCP 連接正式關閉
- 資源清理: 服務端自動清理相關連接資源
監聽斷連事件
Python
def on_close(ws, close_status_code, close_msg):
print(f"連接已關閉: {close_status_code}, {close_msg}")
# 清理客戶端資源
cleanup()Java
public void onClose(int code, String reason, boolean remote) {
System.out.println("連接已關閉: " + code + ", " + reason);
// 清理客戶端資源
cleanup();
}C#
private void OnClose(WebSocketCloseStatus closeStatus, string statusDescription)
{
Console.WriteLine($"連接已關閉: {closeStatus}, {statusDescription}");
// 清理客戶端資源
Cleanup();
}go
func onClose(code int, text string) {
fmt.Printf("連接已關閉: %d, %s\n", code, text)
// 清理客戶端資源
cleanup()
}javascript
websocket.onclose = function(event) {
console.log('連接已關閉:', event.code, event.reason);
// 清理客戶端資源
cleanup();
};最佳實踐
- 監控心跳: 建議客戶端監控心跳消息以檢測連接狀態
- 設置超時: 建議設置 60 秒的心跳超時時間
- 自動重連: 心跳超時時實現自動重連機制
- 日誌記錄: 記錄心跳狀態有助於問題排查
故障排除
心跳消息丟失
如果長時間未收到心跳消息:
- 檢查網絡連接狀態
- 確認 WebSocket 連接是否正常
- 檢查是否有防火牆或代理干擾
- 考慮重新建立連接
連接頻繁斷開
如果連接經常斷開:
- 檢查客戶端網絡穩定性
- 確認是否正確處理心跳消息
- 檢查客戶端是否有資源限制
- 聯繫技術支援獲取協助

