Skip to main content

futu_server/
listener.rs

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