Skip to main content

futu_server/
listener.rs

1// TCP 监听器:接受客户端连接,管理连接池
2
3use std::sync::Arc;
4use std::sync::atomic::{AtomicI64, Ordering};
5use std::time::Instant;
6
7use dashmap::DashMap;
8use tokio::net::TcpListener;
9use tokio::sync::{mpsc, watch};
10
11use futu_codec::header::ProtoFmtType;
12use futu_core::proto_id;
13
14use crate::conn::{ClientConn, ConnState, DisconnectNotify, IncomingRequest};
15use crate::metrics::GatewayMetrics;
16use crate::router::RequestRouter;
17
18/// 服务端最大连接数
19pub const MAX_CONNECTIONS: usize = 128;
20
21/// Inbound request queue capacity shared by raw TCP and WebSocket listeners.
22///
23/// A bounded queue turns slow backend dispatch into socket-level backpressure
24/// instead of letting client frames accumulate without a memory ceiling.
25pub(crate) const REQUEST_QUEUE_CAPACITY: usize = 4096;
26
27/// 服务端配置
28#[derive(Debug, Clone)]
29pub struct ServerConfig {
30    /// TCP 监听地址(如 `127.0.0.1:11111`)
31    pub listen_addr: String,
32    /// 服务端版本号,InitConnect 响应下发给客户端
33    pub server_ver: i32,
34    /// 服务端登录 user_id,InitConnect 响应下发给客户端
35    pub login_user_id: u64,
36    /// KeepAlive 心跳间隔(秒),InitConnect 响应下发给客户端
37    pub keepalive_interval: i32,
38    /// RSA 私钥 PEM 内容(可选,启用后 InitConnect 使用 RSA 加解密)
39    pub rsa_private_key: Option<String>,
40}
41
42#[must_use]
43pub(crate) fn default_server_time_offset_secs() -> Arc<AtomicI64> {
44    Arc::new(AtomicI64::new(0))
45}
46
47#[must_use]
48pub(crate) fn server_now_ts_at(server_time_offset_secs: &AtomicI64, local_now_ts: i64) -> i64 {
49    local_now_ts.saturating_add(server_time_offset_secs.load(Ordering::Relaxed))
50}
51
52#[must_use]
53pub(crate) fn server_now_ts(server_time_offset_secs: &AtomicI64) -> i64 {
54    server_now_ts_at(server_time_offset_secs, chrono::Utc::now().timestamp())
55}
56
57pub(crate) fn set_nodelay_with_log(
58    stream: &tokio::net::TcpStream,
59    peer_addr: std::net::SocketAddr,
60    surface: &'static str,
61) {
62    if let Err(error) = stream.set_nodelay(true) {
63        tracing::debug!(
64            peer = %peer_addr,
65            surface,
66            error = %error,
67            "tcp nodelay setup failed"
68        );
69    }
70}
71
72pub(crate) async fn shutdown_requested(shutdown_rx: &mut watch::Receiver<bool>) {
73    loop {
74        if *shutdown_rx.borrow() {
75            return;
76        }
77        if shutdown_rx.changed().await.is_err() {
78            return;
79        }
80    }
81}
82
83/// API 服务端
84pub struct ApiServer {
85    config: ServerConfig,
86    connections: Arc<DashMap<u64, ClientConn>>,
87    router: Arc<RequestRouter>,
88    subscriptions: Option<Arc<crate::subscription::SubscriptionManager>>,
89    metrics: Arc<GatewayMetrics>,
90    server_time_offset_secs: Arc<AtomicI64>,
91}
92
93impl ApiServer {
94    /// 创建新的服务端实例。不自动启动,需调用 [`ApiServer::run`] 进入接收循环。
95    pub fn new(config: ServerConfig) -> Self {
96        Self {
97            config,
98            connections: Arc::new(DashMap::new()),
99            router: Arc::new(RequestRouter::new()),
100            subscriptions: None,
101            metrics: Arc::new(GatewayMetrics::new()),
102            server_time_offset_secs: default_server_time_offset_secs(),
103        }
104    }
105
106    /// 设置订阅管理器,用于连接断开时自动清理订阅关系
107    pub fn set_subscriptions(&mut self, subs: Arc<crate::subscription::SubscriptionManager>) {
108        self.subscriptions = Some(subs);
109    }
110
111    /// 获取路由器引用(用于注册业务处理器)
112    pub fn router(&self) -> &Arc<RequestRouter> {
113        &self.router
114    }
115
116    /// 获取连接池引用(用于推送分发)
117    pub fn connections(&self) -> &Arc<DashMap<u64, ClientConn>> {
118        &self.connections
119    }
120
121    /// 设置外部监控指标(共享同一个 Arc,让 bridge 和 server 使用同一份计数器)
122    pub fn set_metrics(&mut self, metrics: Arc<GatewayMetrics>) {
123        self.metrics = metrics;
124    }
125
126    /// Inject backend server-time offset for SDK-facing API protocol fields.
127    pub fn set_server_time_offset_secs(&mut self, offset: Arc<AtomicI64>) {
128        self.server_time_offset_secs = offset;
129    }
130
131    /// 获取监控指标引用
132    pub fn metrics(&self) -> &Arc<GatewayMetrics> {
133        &self.metrics
134    }
135
136    /// 启动服务端监听
137    pub async fn run(&self) -> anyhow::Result<()> {
138        let (_shutdown_tx, shutdown_rx) = watch::channel(false);
139        self.run_until_shutdown(shutdown_rx).await
140    }
141
142    /// 启动服务端监听,并在 shutdown 信号到来时停止接受新连接。
143    pub async fn run_until_shutdown(
144        &self,
145        mut shutdown_rx: watch::Receiver<bool>,
146    ) -> anyhow::Result<()> {
147        let listener = TcpListener::bind(&self.config.listen_addr)
148            .await
149            .map_err(|error| {
150                anyhow::anyhow!(
151                    "{}",
152                    crate::bind_hint::bind_error_message(
153                        "FTAPI TCP",
154                        "--port",
155                        &self.config.listen_addr,
156                        error
157                    )
158                )
159            })?;
160        tracing::info!(addr = %self.config.listen_addr, "API server listening");
161
162        let (req_tx, req_rx) = mpsc::channel::<IncomingRequest>(REQUEST_QUEUE_CAPACITY);
163        // The disconnect cleanup signal is intentionally unbounded: each item
164        // is a tiny `conn_id`, and blocking this path behind request-queue
165        // backpressure would risk leaking connection/subscription state.
166        let (disconnect_tx, mut disconnect_rx) = mpsc::unbounded_channel::<DisconnectNotify>();
167
168        // 启动请求处理任务
169        let connections = Arc::clone(&self.connections);
170        let router = Arc::clone(&self.router);
171        let config = self.config.clone();
172        let metrics = Arc::clone(&self.metrics);
173        let server_time_offset_secs = Arc::clone(&self.server_time_offset_secs);
174        tokio::spawn(async move {
175            process_requests(
176                req_rx,
177                connections,
178                router,
179                config,
180                metrics,
181                server_time_offset_secs,
182            )
183            .await;
184        });
185
186        // 启动连接清理任务(TCP 断开通知)
187        let cleanup_connections = Arc::clone(&self.connections);
188        let cleanup_subs = self.subscriptions.clone();
189        let cleanup_metrics = Arc::clone(&self.metrics);
190        tokio::spawn(async move {
191            while let Some(notify) = disconnect_rx.recv().await {
192                let removed = cleanup_connections.remove(&notify.conn_id);
193                if removed.is_some() {
194                    cleanup_metrics
195                        .total_disconnections
196                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
197                    // 清理该连接的所有订阅关系
198                    if let Some(ref subs) = cleanup_subs {
199                        subs.on_disconnect(notify.conn_id);
200                    }
201                    tracing::info!(
202                        conn_id = notify.conn_id,
203                        remaining = cleanup_connections.len(),
204                        "connection removed from pool"
205                    );
206                }
207            }
208        });
209
210        // 启动 KeepAlive 超时检测任务(对应 C++ OnTimeTicker,每 66 秒无活动断连)
211        let ka_connections = Arc::clone(&self.connections);
212        let ka_subs = self.subscriptions.clone();
213        let ka_metrics = Arc::clone(&self.metrics);
214        let mut ka_shutdown_rx = shutdown_rx.clone();
215        tokio::spawn(async move {
216            const CHECK_INTERVAL_SECS: u64 = 15;
217            const TIMEOUT_SECS: u64 = 66;
218            let mut interval =
219                tokio::time::interval(std::time::Duration::from_secs(CHECK_INTERVAL_SECS));
220            interval.tick().await; // 跳过首次立即触发
221            loop {
222                tokio::select! {
223                    _ = shutdown_requested(&mut ka_shutdown_rx) => {
224                        tracing::info!("API server keepalive task stopped by shutdown signal");
225                        break;
226                    }
227                    _ = interval.tick() => {}
228                }
229                let now = Instant::now();
230                let mut timed_out = Vec::new();
231                for entry in ka_connections.iter() {
232                    let conn = entry.value();
233                    if now.duration_since(conn.last_keepalive).as_secs() >= TIMEOUT_SECS {
234                        timed_out.push(conn.conn_id);
235                    }
236                }
237                for conn_id in timed_out {
238                    if ka_connections.remove(&conn_id).is_some() {
239                        ka_metrics
240                            .keepalive_timeouts
241                            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
242                        ka_metrics
243                            .total_disconnections
244                            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
245                        if let Some(ref subs) = ka_subs {
246                            subs.on_disconnect(conn_id);
247                        }
248                        tracing::info!(
249                            conn_id = conn_id,
250                            remaining = ka_connections.len(),
251                            "keepalive timeout, connection removed"
252                        );
253                    }
254                }
255            }
256        });
257
258        // 接受连接循环
259        let connections = Arc::clone(&self.connections);
260        let accept_metrics = Arc::clone(&self.metrics);
261        loop {
262            let (stream, peer_addr) = tokio::select! {
263                _ = shutdown_requested(&mut shutdown_rx) => {
264                    tracing::info!("API server accept loop stopped by shutdown signal");
265                    break;
266                }
267                accepted = listener.accept() => accepted?,
268            };
269
270            if connections.len() >= MAX_CONNECTIONS {
271                accept_metrics
272                    .rejected_connections
273                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
274                tracing::warn!(
275                    peer = %peer_addr,
276                    "max connections reached ({}), rejecting",
277                    MAX_CONNECTIONS
278                );
279                drop(stream);
280                continue;
281            }
282
283            accept_metrics
284                .total_connections
285                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
286
287            let conn_id = crate::conn::ClientConn::generate_conn_id();
288            let aes_key = crate::conn::ClientConn::generate_aes_key();
289            set_nodelay_with_log(&stream, peer_addr, "tcp");
290
291            tracing::info!(
292                conn_id = conn_id,
293                peer = %peer_addr,
294                total = connections.len() + 1,
295                "client connected"
296            );
297
298            let tx = crate::conn::run_connection(
299                stream,
300                conn_id,
301                aes_key,
302                req_tx.clone(),
303                disconnect_tx.clone(),
304                shutdown_rx.clone(),
305            )
306            .await;
307
308            let conn = ClientConn {
309                conn_id,
310                state: ConnState::Connected,
311                aes_key,
312                aes_encrypt_enabled: false,
313                proto_fmt_type: ProtoFmtType::Protobuf,
314                last_keepalive: Instant::now(),
315                recv_notify: false,
316                ai_type: 0,
317                keepalive_count: std::sync::atomic::AtomicU32::new(0),
318                tx,
319                // 原 TCP listener 不做 per-message scope 校验(保持兼容):
320                // key_id=None / scopes=空集 被 ws_listener 的 gate 解释为"legacy 全放行"
321                key_id: None,
322                scopes: std::collections::HashSet::new(),
323                // v1.4.105 D3 (Phase 4) T-B2: TCP listener 同样 legacy 模式 →
324                // allowed_markets None = 无限制 (push_trd_acc Layer 3 不 trigger).
325                allowed_markets: None,
326                // codex round 1 F4 (P2) v1.4.105: 同 legacy 模式, 无 acc_id 限制.
327                allowed_acc_ids: None,
328            };
329
330            connections.insert(conn_id, conn);
331        }
332
333        Ok(())
334    }
335
336    /// 向指定连接发送响应(自动处理 AES 加密)
337    pub async fn send_response(
338        connections: &DashMap<u64, ClientConn>,
339        conn_id: u64,
340        proto_id: u32,
341        serial_no: u32,
342        body: Vec<u8>,
343    ) {
344        if let Some(conn) = connections.get(&conn_id) {
345            let frame = conn.make_frame(proto_id, serial_no, bytes::Bytes::from(body));
346            if conn.tx.send(frame).await.is_err() {
347                tracing::warn!(
348                    conn_id = conn_id,
349                    "failed to send response, connection closed"
350                );
351            }
352        }
353    }
354}
355
356/// 处理所有连接的请求
357async fn process_requests(
358    mut req_rx: mpsc::Receiver<IncomingRequest>,
359    connections: Arc<DashMap<u64, ClientConn>>,
360    router: Arc<RequestRouter>,
361    config: ServerConfig,
362    metrics: Arc<GatewayMetrics>,
363    server_time_offset_secs: Arc<AtomicI64>,
364) {
365    while let Some(mut req) = req_rx.recv().await {
366        let conn_id = req.conn_id;
367        let proto_id_val = req.proto_id;
368        let serial_no = req.serial_no;
369        let req_start = Instant::now();
370
371        metrics
372            .total_requests
373            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
374
375        // 更新 last_keepalive(任何包都算活跃,对应 C++ m_nKeepAlive_Count_Curt++)
376        if let Some(mut conn) = connections.get_mut(&conn_id) {
377            conn.last_keepalive = Instant::now();
378        }
379
380        // v1.4.106 codex 0532 F3 (P2): daemon-internal proto_id (高位
381        // 0x8000_0000 bit) 绝不应从 raw TCP 公开 surface 进入 — 仅 REST
382        // handler 内部合成给 router. 显式 reject + log, 防探测 daemon
383        // 内部 routing.
384        if futu_auth::is_internal_proto_id(proto_id_val) {
385            metrics
386                .total_request_errors
387                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
388            tracing::warn!(
389                conn_id,
390                proto_id = proto_id_val,
391                "rejecting daemon-internal proto_id at raw TCP public surface (audit 0532 F3)"
392            );
393            continue;
394        }
395
396        // 非 InitConnect 请求需要 AES 解密(InitConnect 自身处理 RSA 解密)
397        if proto_id_val != proto_id::INIT_CONNECT
398            && let Some(conn) = connections.get(&conn_id)
399            && conn.aes_encrypt_enabled
400        {
401            match conn.decrypt_body(&req.body) {
402                Ok(decrypted) => {
403                    req.body = bytes::Bytes::from(decrypted);
404                }
405                Err(e) => {
406                    metrics
407                        .total_request_errors
408                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
409                    tracing::warn!(
410                        conn_id = conn_id,
411                        proto_id = proto_id_val,
412                        error = %e,
413                        "AES decrypt request failed, dropping"
414                    );
415                    continue;
416                }
417            }
418        }
419
420        // InitConnect 和 KeepAlive 内部处理
421        let response_body =
422            futu_core::delay_stats::with_api_request(conn_id, serial_no, proto_id_val, || async {
423                match proto_id_val {
424                    proto_id::INIT_CONNECT => match connections.get_mut(&conn_id) {
425                        Some(mut conn) => match conn.handle_init_connect(
426                            &req.body,
427                            config.server_ver,
428                            config.login_user_id,
429                            config.keepalive_interval,
430                            config.rsa_private_key.as_deref(),
431                        ) {
432                            Ok(body) => Some(body),
433                            Err(error) => {
434                                metrics
435                                    .total_request_errors
436                                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
437                                tracing::warn!(
438                                    conn_id,
439                                    proto_id = proto_id_val,
440                                    error = %error,
441                                    "InitConnect handling failed"
442                                );
443                                None
444                            }
445                        },
446                        None => {
447                            metrics
448                                .total_request_errors
449                                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
450                            tracing::warn!(
451                                conn_id,
452                                proto_id = proto_id_val,
453                                "InitConnect request received for missing connection"
454                            );
455                            None
456                        }
457                    },
458                    proto_id::KEEP_ALIVE => match connections.get(&conn_id) {
459                        Some(conn) => match conn
460                            .handle_keepalive_at(&req.body, server_now_ts(&server_time_offset_secs))
461                        {
462                            Ok(body) => Some(body),
463                            Err(error) => {
464                                metrics
465                                    .total_request_errors
466                                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
467                                tracing::warn!(
468                                    conn_id,
469                                    proto_id = proto_id_val,
470                                    error = %error,
471                                    "KeepAlive handling failed"
472                                );
473                                None
474                            }
475                        },
476                        None => {
477                            metrics
478                                .total_request_errors
479                                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
480                            tracing::warn!(
481                                conn_id,
482                                proto_id = proto_id_val,
483                                "KeepAlive request received for missing connection"
484                            );
485                            None
486                        }
487                    },
488                    _ => {
489                        // 委托给路由器
490                        router.dispatch(conn_id, &req).await
491                    }
492                }
493            })
494            .await;
495
496        // 记录延迟
497        metrics.record_latency_ns(req_start.elapsed().as_nanos() as u64);
498
499        // 发送响应
500        if let Some(body) = response_body {
501            metrics
502                .total_response_bytes
503                .fetch_add(body.len() as u64, std::sync::atomic::Ordering::Relaxed);
504            ApiServer::send_response(&connections, conn_id, proto_id_val, serial_no, body).await;
505        } else if proto_id_val != proto_id::INIT_CONNECT && proto_id_val != proto_id::KEEP_ALIVE {
506            metrics
507                .total_request_errors
508                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
509        }
510    }
511}
512
513#[cfg(test)]
514mod tests;