2026-07-02 05:38:01 +08:00

145 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! TCP 服务端:监听端口、接收设备连接、按 LF 分割 JSON 消息。
use futures::StreamExt;
use std::time::Duration;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::mpsc;
use tokio::time;
use tokio_util::codec::{FramedRead, LinesCodec};
use super::connection::{ConnectionPool, DeviceConnection};
use super::handler::handle_message;
use super::protocol::DeviceMessage;
/// 心跳超时5000ms 未收到消息则断开连接。
const HEARTBEAT_TIMEOUT: Duration = Duration::from_millis(5000);
/// 清理超时连接的间隔。
const CLEANUP_INTERVAL: Duration = Duration::from_secs(10);
/// 启动 TCP 监听循环。
pub async fn run_tcp_server(
addr: String,
pool: ConnectionPool,
redis: redis::aio::ConnectionManager,
) {
let listener = TcpListener::bind(&addr)
.await
.expect("TCP 端口绑定失败");
tracing::info!("TCP 设备服务启动: {}", addr);
// 后台清理过期连接
let cleanup_pool = pool.clone();
tokio::spawn(async move {
let mut interval = time::interval(CLEANUP_INTERVAL);
loop {
interval.tick().await;
let removed = cleanup_pool.cleanup_timeout(HEARTBEAT_TIMEOUT).await;
for dev_id in &removed {
tracing::warn!("[心跳超时] 断开 dev_id={}", dev_id);
}
}
});
loop {
match listener.accept().await {
Ok((stream, peer)) => {
tracing::info!("[新连接] peer={}", peer);
let pool = pool.clone();
let redis = redis.clone();
tokio::spawn(handle_connection(stream, peer, pool, redis));
}
Err(e) => {
tracing::error!("accept 失败: {}", e);
}
}
}
}
/// 处理单条 TCP 连接。
async fn handle_connection(
stream: TcpStream,
peer: std::net::SocketAddr,
pool: ConnectionPool,
redis: redis::aio::ConnectionManager,
) {
let (reader, writer) = tokio::io::split(stream);
let mut lines = FramedRead::new(reader, LinesCodec::new_with_max_length(4096));
// 写通道HTTP 命令下发通过此通道写入 TCP
let (tx, mut rx) = mpsc::unbounded_channel::<String>();
let mut current_dev_id: Option<String> = None;
let mut writer = writer;
loop {
tokio::select! {
// 读取设备消息
line = lines.next() => {
match line {
Some(Ok(text)) => {
if text.trim().is_empty() {
continue;
}
let msg: DeviceMessage = match serde_json::from_str(&text) {
Ok(m) => m,
Err(e) => {
tracing::warn!("[{}] JSON 解析失败: {} raw={}", peer, e, text);
continue;
}
};
// 如果是 login/auth_str需要 dev_id 来关联连接
if (msg.act == "auth_str" || msg.act == "login")
&& let Some(ref dev_id) = msg.dev_id
&& current_dev_id.is_none() {
let conn = DeviceConnection::new(dev_id.clone(), tx.clone());
pool.register(dev_id.clone(), conn).await;
current_dev_id = Some(dev_id.clone());
}
// 更新活跃时间
if let Some(ref dev_id) = current_dev_id {
pool.touch(dev_id).await;
}
// 处理消息
let resp = handle_message(msg, &pool, &redis).await;
if let Some(resp) = resp {
let json = resp.to_json();
if let Err(e) = tx.send(json) {
tracing::error!("[{}] 响应发送失败: {}", peer, e);
break;
}
}
}
Some(Err(e)) => {
tracing::warn!("[{}] 读取错误: {}", peer, e);
break;
}
None => {
tracing::info!("[{}] 连接关闭", peer);
break;
}
}
}
// 向设备写指令
Some(json) = rx.recv() => {
use tokio::io::AsyncWriteExt;
let framed = format!("{}\n", json);
if let Err(e) = writer.write_all(framed.as_bytes()).await {
tracing::error!("[{}] 写入失败: {}", peer, e);
break;
}
}
}
}
// 连接断开,清理
if let Some(ref dev_id) = current_dev_id {
pool.remove(dev_id).await;
tracing::info!("[断连] dev_id={}", dev_id);
}
}