从0开始写基于tokio的TcpClient

Rust TCP 客户端设计的两种路径:从迭代中看架构选择

设计一个 Rust TCP 客户端时,"简单"往往是最难实现的目标。本文记录了两个不同架构方向的实现过程:一个基于 Channel 解耦与 watch 取消令牌,另一个基于控制通道与回调函数。两者都经历了多轮迭代才达到可用状态,文字记录过程中暴露的问题。


第一部分:版本一------Channel 解耦 + watch 取消机制

1.1 初始骨架

最初的代码结构很直观:用 tokio::sync::mpsc 双通道解耦网络层与业务层,tokio::select! 同时驱动读写两个异步任务。

rust 复制代码
use std::sync::Arc;
use log::error;
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt as _},
    net::tcp::{ReadHalf, WriteHalf},
    sync::{Mutex, mpsc::{Receiver, Sender}},
};

pub enum GameMsg {
    Start
}

impl GameMsg {
    pub fn from_bytes(_: &[u8]) -> Self {
        Self::Start
    }
    pub fn to_bytes(&self) -> Vec<u8> {
        match self { Self::Start => vec![0] }
    }
}

pub struct TcpClient {
    host: String,
    port: u16,
    sender_to_client: tokio::sync::mpsc::Sender<GameMsg>,
    recv_from_server: Arc<Mutex<tokio::sync::mpsc::Receiver<GameMsg>>>,
    sender_to_server: tokio::sync::mpsc::Sender<GameMsg>,
    recv_from_client: Arc<Mutex<tokio::sync::mpsc::Receiver<GameMsg>>>,
}

impl TcpClient {
    pub fn new(host: String, port: u16) -> Self {
        let (sender_to_client, recv_from_server) = 
            tokio::sync::mpsc::channel::<GameMsg>(100);
        let (sender_to_server, recv_from_client) = 
            tokio::sync::mpsc::channel::<GameMsg>(100);
        Self {
            host, port,
            sender_to_client,
            recv_from_server: Arc::new(Mutex::new(recv_from_server)),
            sender_to_server,
            recv_from_client: Arc::new(Mutex::new(recv_from_client)),
        }
    }

    pub async fn loop_connect(&self) {
        loop {
            if let Err(e) = self.connect_server().await {
                error!("Failed to connect: {}", e);
            }
            tokio::time::sleep(std::time::Duration::from_secs(3)).await;
        }
    }

    async fn connect_server(&self) -> anyhow::Result<()> {
        let addr = format!("{}:{}", self.host, self.port);
        let mut stream = tokio::net::TcpStream::connect(addr).await?;
        let (read, recv) = stream.split();
        let sender_clone = self.sender_to_client.clone();

        let read_async = Self::read_msg(read, sender_clone);
        let write_async = Self::write_msg(recv, self.recv_from_client.clone());

        tokio::select! {
            _ = read_async => {},
            _ = write_async => {}
        }
        Ok(())
    }

    async fn write_msg(
        mut write_half: WriteHalf<'_>, 
        recv: Arc<Mutex<Receiver<GameMsg>>>
    ) -> anyhow::Result<()> {
        loop {
            if let Some(game_msg) = recv.lock().await.recv().await {
                let bytes = game_msg.to_bytes();
                write_half.write_u32(bytes.len() as u32).await?;
                write_half.write_all(&bytes).await?;
                write_half.flush().await?;
            } else { break; }
        }
        Ok(())
    }

    async fn read_msg(
        mut read_half: ReadHalf<'_>, 
        sender: Sender<GameMsg>
    ) -> anyhow::Result<()> {
        loop {
            let data_len = read_half.read_u32().await?;
            let mut data = vec![0; data_len as usize];
            read_half.read_exact(&mut data).await?;
            let msg = GameMsg::from_bytes(&data);
            sender.send(msg).await?;
        }
    }
}

问题:

  • select!_ = 通配符,错误被静默丢弃,connect_server 永远返回 Ok(())
  • 缺少优雅关闭机制,程序退出可能卡在 sleepconnect
  • Arc<Mutex<Receiver>> API 繁琐,业务层读取要写 client.get_recv_from_server().lock().await.recv().await
  • read_msg 无长度上限,对端发恶意大包直接 OOM

1.2 引入 oneshot 取消(失败尝试)

rust 复制代码
pub struct TcpClient {
    // ... 其他字段
    cancel_send_token: Option<tokio::sync::oneshot::Sender<()>>,
}

impl TcpClient {
    pub fn stop_server(&mut self) {
        if let Some(sender) = self.cancel_send_token.take() {
            let _ = sender.send(());
        }
    }

    pub fn start_server(&mut self) {
        let (cancel_sender, cancel_recv) = tokio::sync::oneshot::channel::<()>();
        self.cancel_send_token = Some(cancel_sender);
        tokio::spawn(async move {
            loop_connect(addr, cancel_recv, sender, recv).await;
        });
    }
}

async fn loop_connect(
    addr: String,
    mut cancel: tokio::sync::oneshot::Receiver<()>,
    sender_to_client: tokio::sync::mpsc::Sender<GameMsg>,
    recv_from_client: Arc<Mutex<tokio::sync::mpsc::Receiver<GameMsg>>>,
) {
    loop {
        let cancel_arc = Arc::new(cancel);          // 编译错误
        let cancel_clone = cancel_arc.clone();      // oneshot::Receiver 没有 Clone

        tokio::select! {
            cancel = async move { cancel_clone } => {  // 逻辑错误:不会等待
                info!("Cancel loop: {:?}", cancel);
                break;
            }
            e = connect_server(&addr, sender_to_client.clone(), recv_from_client.clone()) => {
                error!("Failed: {:?}", e);
            }
        }
        tokio::time::sleep(std::time::Duration::from_secs(3)).await;
    }
}

问题:

  • oneshot::Receiver 不能 CloneArc::clone 编译失败
  • async move { cancel_clone } 不会等待,第一次循环就 break
  • sleep(3)select! 外部,stop 时可能卡 3 秒
  • update() 有竞态:旧任务未结束就启动新任务

1.3 改用 watch<i32> 状态机

rust 复制代码
pub struct TcpClient {
    cancel_token_send: tokio::sync::watch::Sender<i32>,
    cancel_token_recv: tokio::sync::watch::Receiver<i32>,
}

impl TcpClient {
    pub async fn update(&mut self, host: String, port: u16) -> anyhow::Result<()> {
        self.stop_server();
        self.cancel_token_recv.changed().await?;  // 等待从 1 变到 2
        self.host = host;
        self.port = port;
        self.start_server();
        Ok(())
    }

    pub fn stop_server(&mut self) {
        self.cancel_token_send.send_replace(1);  // 请求取消
    }

    pub fn start_server(&mut self) {
        self.cancel_token_send.send_replace(0);  // 重置为运行中
    }
}

async fn loop_connect(
    addr: String,
    mut cancel: tokio::sync::watch::Receiver<i32>,
    cancel_sender: tokio::sync::watch::Sender<i32>,
    // ...
) {
    loop {
        let res = connect_server(&addr, &mut cancel, /* ... */).await;
        match res {
            Ok(s) => {
                if s.eq("cancel") { break; }
            },
            Err(e) => error!("connect error: {}", e),
        }
        tokio::time::sleep(std::time::Duration::from_secs(3)).await;
    }
    cancel_sender.send_replace(2);  // 通知已退出
}

async fn connect_server(
    addr: &str,
    cancel: &mut tokio::sync::watch::Receiver<i32>,
    // ...
) -> anyhow::Result<String> {
    let mut stream = tokio::net::TcpStream::connect(addr).await?;
    let (read, write) = stream.split();

    let res = tokio::select! {
        res = cancel.changed() => {
            let _ = res?;
            let val = cancel.borrow();
            if *val == 1 {
                return Ok("cancel".to_string());  // 直接 return
            }
            Err(anyhow::anyhow!("cancel expect 1, got {}", *val))
        },
        res = read_async => { /* ... */ },
        res = write_async => { /* ... */ }
    };
    stream.shutdown().await?;  // unreachable!所有分支都 return
    res
}

问题:

  • update 可能死锁:stop1 后,connect_server 可能卡在连接中,等不到 2
  • watch::Sender 广播覆盖:start_serversend_replace(0) 通知所有 Receiver,旧任务可能收到 0 而非 1
  • stream.shutdown() unreachable:select! 所有分支都 return
  • connect 无超时:服务器不可达时可能阻塞数十秒

1.4 简化为 watch<bool> + JoinHandle

rust 复制代码
pub struct TcpClient {
    cancel_token_send: tokio::sync::watch::Sender<bool>,
    cancel_token_recv: tokio::sync::watch::Receiver<bool>,
    handle: Option<tokio::task::JoinHandle<()>>,
}

impl TcpClient {
    pub async fn update(&mut self, host: String, port: u16) -> anyhow::Result<()> {
        self.stop_server();
        if let Some(h) = self.handle.take() {
            h.await?;  // 等待旧任务结束
        }
        self.host = host;
        self.port = port;
        self.start_server();
        Ok(())
    }

    pub fn stop_server(&mut self) {
        self.cancel_token_send.send_replace(true);
    }

    pub fn start_server(&mut self) {
        self.cancel_token_send.send_replace(false);
        let handle = tokio::spawn(async move {
            loop_connect(addr, cancel_recv, sender, recv).await;
        });
        self.handle = Some(handle);
    }
}

async fn connect_server(
    addr: &str,
    cancel: &mut tokio::sync::watch::Receiver<bool>,
    // ...
) -> anyhow::Result<String> {
    let mut stream = tokio::net::TcpStream::connect(addr).await?;
    let (read, write) = stream.split();

    let res = tokio::select! {
        res = cancel.changed() => {
            let _ = res?;
            let val = cancel.borrow();
            if *val {
                return Ok("cancel".to_string());
            }
            Err(anyhow::anyhow!("cancel expect true, got {}", *val))
        },
        res = read_async => {
            match res {
                Ok(_) => Ok("read exit ok".to_string()),
                Err(e) => Err(e),
            }
        },
        res = write_async => {
            match res {
                Ok(_) => Ok("write exit ok".to_string()),
                Err(e) => Err(e),
            }
        }
    };
    let _ = stream.shutdown().await;
    res
}

问题:

  • update 仍可能永远卡住:select! 不会取消 connect_server 内部已启动的 TcpStream::connect
  • stream.shutdown() 的可达性争议:经确认,当分支无 return 时可达

1.5 修正 select! 分支结构

rust 复制代码
let res = tokio::select! {
    res = cancel.changed() => {
        let _ = res?;
        let val = cancel.borrow();
        if *val {
            Ok("cancel".to_string())
        } else {
            Err(anyhow::anyhow!("cancel expect true, got {}", *val))
        }
    },
    res = read_async => {
        match res {
            Ok(_) => Ok("read exit ok".to_string()),
            Err(e) => Err(e),
        }
    },
    res = write_async => {
        match res {
            Ok(_) => Ok("write exit ok".to_string()),
            Err(e) => Err(e),
        }
    }
};
let _ = stream.shutdown().await;  // 现在确实可达
res

此时结构已正确,但仍有隐患:

  • TcpStream::connect 依然没有超时
  • send_to_socketrecv_from_socket 函数名写反了

1.6 最终代码

rust 复制代码
use std::sync::Arc;
use log::{error, info, debug};
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt as _},
    net::tcp::{ReadHalf, WriteHalf},
    sync::{Mutex, mpsc::{Receiver, Sender}, watch},
};

const MAX_PACKET_SIZE: u32 = 5 * 1024 * 1024;

#[derive(Debug)]
pub enum GameMsg {
    Start,
}

impl GameMsg {
    pub fn from_bytes(_: &[u8]) -> Self {
        Self::Start
    }
    pub fn to_bytes(&self) -> Vec<u8> {
        match self { Self::Start => vec![0] }
    }
}

pub struct TcpClient {
    host: String,
    port: u16,
    send_to_server_tx: Sender<GameMsg>,
    send_to_server_rx: Arc<Mutex<Receiver<GameMsg>>>,
    recv_from_server_tx: Sender<GameMsg>,
    recv_from_server_rx: Arc<Mutex<Receiver<GameMsg>>>,
    cancel_tx: watch::Sender<bool>,
    cancel_rx: watch::Receiver<bool>,
    task_handle: Option<tokio::task::JoinHandle<()>>,
}

impl TcpClient {
    pub fn new(host: String, port: u16) -> Self {
        let (send_to_server_tx, send_to_server_rx) = 
            tokio::sync::mpsc::channel::<GameMsg>(100);
        let (recv_from_server_tx, recv_from_server_rx) = 
            tokio::sync::mpsc::channel::<GameMsg>(100);
        let (cancel_tx, cancel_rx) = watch::channel::<bool>(false);
        Self {
            host, port,
            send_to_server_tx,
            send_to_server_rx: Arc::new(Mutex::new(send_to_server_rx)),
            recv_from_server_tx,
            recv_from_server_rx: Arc::new(Mutex::new(recv_from_server_rx)),
            cancel_tx, cancel_rx,
            task_handle: None,
        }
    }

    pub async fn update(&mut self, host: String, port: u16) -> anyhow::Result<()> {
        info!("Updating server to {}:{}", host, port);
        self.stop_server_and_wait().await;
        if let Some(h) = self.task_handle.take() {
            h.await?;
        }
        info!("Old server task exited.");
        self.host = host;
        self.port = port;
        self.start_server();
        Ok(())
    }

    pub fn get_sender(&self) -> Sender<GameMsg> {
        self.send_to_server_tx.clone()
    }

    pub fn get_receiver(&self) -> Arc<Mutex<Receiver<GameMsg>>> {
        self.recv_from_server_rx.clone()
    }

    pub fn stop_server(&mut self) {
        if !*self.cancel_rx.borrow() {
            info!("Sending stop signal...");
            self.cancel_tx.send_replace(true);
        }
    }

    pub async fn stop_server_and_wait(&mut self) {
        self.stop_server();
        if let Some(h) = self.task_handle.take() {
            h.await.unwrap();
        }
    }

    pub fn start_server(&mut self) {
        let addr = format!("{}:{}", self.host, self.port);
        let net_to_logic_tx = self.recv_from_server_tx.clone();
        let logic_to_net_rx = self.send_to_server_rx.clone();
        let cancel_rx = self.cancel_rx.clone();
        self.cancel_tx.send_replace(false);

        info!("Spawning connection task for {}", addr);
        let handle = tokio::spawn(async move {
            connection_loop(addr, cancel_rx, net_to_logic_tx, logic_to_net_rx).await;
        });
        self.task_handle = Some(handle);
    }
}

async fn connection_loop(
    addr: String,
    mut cancel_rx: watch::Receiver<bool>,
    net_to_logic_tx: Sender<GameMsg>,
    logic_to_net_rx: Arc<Mutex<Receiver<GameMsg>>>,
) {
    info!("Connection loop started for {}", addr);
    loop {
        let res = handle_connection(
            &addr, &mut cancel_rx,
            net_to_logic_tx.clone(), logic_to_net_rx.clone()
        ).await;
        match res {
            Ok(s) => {
                if s.eq("cancel") {
                    info!("Cancelled by user.");
                    break;
                }
                info!("Connection closed: {}", s);
            },
            Err(e) => error!("Connection error: {}. Retry in 3s...", e),
        }
        tokio::time::sleep(std::time::Duration::from_secs(3)).await;
    }
    info!("Connection loop ended for {}", addr);
}

async fn handle_connection(
    addr: &str,
    cancel_rx: &mut watch::Receiver<bool>,
    net_to_logic_tx: Sender<GameMsg>,
    logic_to_net_rx: Arc<Mutex<Receiver<GameMsg>>>,
) -> anyhow::Result<String> {
    info!("Connecting to {}", addr);
    let mut stream = tokio::net::TcpStream::connect(addr).await?;
    info!("Connected to {}", addr);

    let (read, write) = stream.split();
    let read_loop = recv_from_socket(read, net_to_logic_tx);
    let write_loop = send_to_socket(write, logic_to_net_rx);

    let res = tokio::select! {
        _ = cancel_rx.changed() => {
            let val = cancel_rx.borrow();
            if *val {
                info!("Cancelled by stop signal.");
                Ok("cancel".to_string())
            } else {
                Err(anyhow::anyhow!("Unexpected cancel value"))
            }
        },
        res = read_loop => {
            match res {
                Ok(()) => {
                    info!("Read ended (peer closed).");
                    Ok("read exit ok".to_string())
                }
                Err(e) => { error!("Read error: {}", e); Err(e) }
            }
        },
        res = write_loop => {
            match res {
                Ok(()) => {
                    info!("Write ended (senders dropped).");
                    Ok("write exit ok".to_string())
                }
                Err(e) => { error!("Write error: {}", e); Err(e) }
            }
        }
    };

    info!("Shutting down stream for {}", addr);
    let _ = stream.shutdown().await;
    res
}

async fn send_to_socket(
    mut write_half: WriteHalf<'_>,
    logic_to_net_rx: Arc<Mutex<Receiver<GameMsg>>>,
) -> anyhow::Result<()> {
    loop {
        if let Some(game_msg) = logic_to_net_rx.lock().await.recv().await {
            let bytes = game_msg.to_bytes();
            debug!("Sending msg, {} bytes", bytes.len());
            write_half.write_u32(bytes.len() as u32).await?;
            write_half.write_all(&bytes).await?;
            write_half.flush().await?;
        } else {
            info!("Channel closed, stopping write.");
            break;
        }
    }
    Ok(())
}

async fn recv_from_socket(
    mut read_half: ReadHalf<'_>, 
    net_to_logic_tx: Sender<GameMsg>
) -> anyhow::Result<()> {
    loop {
        let data_len = read_half.read_u32().await?;
        if data_len > MAX_PACKET_SIZE {
            return Err(anyhow::anyhow!(
                "Packet too large: {} bytes (max {})",
                data_len, MAX_PACKET_SIZE
            ));
        }
        debug!("Received header, {} bytes", data_len);
        let mut data = vec![0; data_len as usize];
        read_half.read_exact(&mut data).await?;
        let msg = GameMsg::from_bytes(&data);
        net_to_logic_tx.send(msg).await?;
    }
}

第二部分:版本二------控制通道 + 回调处理

2.1 设计思路

版本一的问题在于:

  • Arc<Mutex<Receiver>> API 繁琐
  • update 等待旧任务退出可能卡住
  • watch 广播特性导致状态覆盖

版本二改用独立控制通道(TcpMsg)分发命令,业务层通过回调函数(MsgHandler)处理消息,取消和重连在同一个 select! 中处理。

2.2 消息定义

rust 复制代码
#[derive(Debug)]
pub enum GameMsg {
    ClientId(u32),
    StartFunWithStringScript(String),
    StartFunFromAssest(String),
    StopFun,
    Unkown
}

impl GameMsg {
    pub fn from_bytes(bytes: &[u8]) -> Self {
        let msg_type = Self::get_msg_type(&bytes[0..4]);
        match msg_type {
            0 => GameMsg::ClientId(0),
            1 => GameMsg::StartFunWithStringScript(
                String::from_utf8_lossy(&bytes[4..]).to_string()),
            2 => GameMsg::StartFunFromAssest(
                String::from_utf8_lossy(&bytes[4..]).to_string()),
            3 => GameMsg::StopFun,
            _ => Unkown
        }
    }

    fn get_msg_type(bytes: &[u8]) -> u32 {
        ((bytes[0] as u32) << 24)
            | ((bytes[1] as u32) << 16)
            | ((bytes[2] as u32) << 8)
            | (bytes[3] as u32)
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        todo!()  // 待实现
    }
}

2.3 控制消息与回调类型

rust 复制代码
enum TcpMsg {
    UpdateAddr(String),
    Stop,
}

pub type MsgHandler = Box<dyn FnMut(GameMsg) -> () + Send + 'static>;

2.4 客户端结构

rust 复制代码
pub struct TcpClient {
    tcp_msg_sender: Option<Sender<TcpMsg>>,
    msg_to_server_sender: Option<Sender<GameMsg>>,
    game_msg_join_handle: Option<JoinHandle<()>>,
    msg_handler: Option<MsgHandler>
}

impl TcpClient {
    pub fn new() -> Self {
        Self {
            tcp_msg_sender: None,
            msg_to_server_sender: None,
            game_msg_join_handle: None,
            msg_handler: None
        }
    }

    pub fn set_msg_handler(&mut self, handler: MsgHandler) {
        self.msg_handler = Some(handler);
    }

2.5 启动逻辑

rust 复制代码
    pub fn start(&mut self, host: &str, port: u16) {
        if self.msg_handler.is_none() {
            error!("msg handler is not set");
            return;
        }

        let (msg_from_server_sender, mut msg_from_server_receiver) =
            tokio::sync::mpsc::channel::<GameMsg>(1024);
        let (msg_to_server_sender, msg_to_server_receiver) =
            tokio::sync::mpsc::channel::<GameMsg>(1024);
        let (tcp_msg_sender, tcp_msg_receiver) =
            tokio::sync::mpsc::channel::<TcpMsg>(1024);

        self.msg_to_server_sender = Some(msg_to_server_sender);
        self.tcp_msg_sender = Some(tcp_msg_sender);

        let addr = format!("{}:{}", host, port);

        TOKIO_RUNTIME.spawn(connect_server_loop(
            addr,
            msg_to_server_receiver,
            msg_from_server_sender,
            tcp_msg_receiver
        ));

        if let Some(mut handler) = self.msg_handler.take() {
            let handle = TOKIO_RUNTIME.spawn(async move {
                while let Some(msg) = msg_from_server_receiver.recv().await {
                    handler(msg);
                }
                error!("msg_from_server_receiver closed");
            });
            self.game_msg_join_handle = Some(handle);
        }
    }

2.6 控制接口

rust 复制代码
    pub fn update_server_addr(&self, host: &str, port: u16) {
        let addr = format!("{}:{}", host, port);
        if let Some(sender) = &self.tcp_msg_sender {
            if let Err(e) = sender.blocking_send(TcpMsg::UpdateAddr(addr)) {
                error!("send update addr failed: {:?}", e);
            }
        }
    }

    pub fn stop(&self) {
        if let Some(handle) = &self.game_msg_join_handle {
            info!("abort game msg handle");
            handle.abort();
        }
        if let Some(sender) = &self.tcp_msg_sender {
            if let Err(e) = sender.blocking_send(TcpMsg::Stop) {
                error!("send stop failed: {:?}", e);
            } else {
                info!("send stop msg to tcp client");
            }
        } else {
            error!("tcp client not started");
        }
    }

    pub fn send_msg_to_server(&self, msg: GameMsg) {
        if let Some(sender) = &self.msg_to_server_sender {
            if let Err(e) = sender.blocking_send(msg) {
                error!("send msg to server failed: {:?}", e);
            }
        } else {
            error!("tcp client not started");
        }
    }

2.7 连接主循环

rust 复制代码
async fn connect_server_loop(
    mut address: String,
    mut msg_to_server_receiver: Receiver<GameMsg>,
    mut msg_from_server_sender: Sender<GameMsg>,
    mut cancel_receiver: Receiver<TcpMsg>
) {
    loop {
        tokio::select! {
            msg_res = cancel_receiver.recv() => {
                if let Some(msg) = msg_res {
                    match msg {
                        TcpMsg::UpdateAddr(addr) => {
                            address = addr;
                            info!("update server address to {}", address);
                        }
                        TcpMsg::Stop => {
                            info!("stop tcp client");
                            break;
                        }
                    }
                } else {
                    error!("cancel_receiver closed");
                    break;
                }
            }
            res = connect_server(
                &address,
                &mut msg_to_server_receiver,
                &mut msg_from_server_sender
            ) => {
                match res {
                    Ok(_) => info!("disconnected from server"),
                    Err(err) => error!("Failed: {:?}", err),
                };
                info!("try to reconnect in 3 seconds");
                tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
            }
        }
    }
}

2.8 单次连接处理

rust 复制代码
async fn connect_server(
    addr: &str,
    msg_to_server_receiver: &mut Receiver<GameMsg>,
    msg_from_server_sender: &mut Sender<GameMsg>
) -> anyhow::Result<()> {
    let mut stream = TcpStream::connect(addr).await?;
    info!("Connected to server: {}", addr);

    let (mut reader, mut writer) = stream.split();

    let res = tokio::select! {
        res = send_msg_to_server_loop(&mut writer, msg_to_server_receiver) => res,
        res = recv_msg_from_server_loop(&mut reader, msg_from_server_sender) => res,
    };

    let _ = writer.shutdown().await;
    res
}

2.9 写循环

rust 复制代码
async fn send_msg_to_server_loop(
    write_half: &mut WriteHalf<'_>,
    msg_to_server_receiver: &mut Receiver<GameMsg>
) -> anyhow::Result<()> {
    loop {
        let msg = msg_to_server_receiver.recv().await;
        if let Some(msg) = msg {
            let bytes = msg.to_bytes();
            let len = bytes.len() as u32;
            write_half.write_u32(len).await?;
            write_half.write_all(&bytes).await?;
            write_half.flush().await?;
        } else {
            return Err(anyhow::anyhow!("receiver closed"));
        }
    }
}

2.10 读循环

rust 复制代码
async fn recv_msg_from_server_loop(
    read_half: &mut ReadHalf<'_>,
    msg_from_server_sender: &mut Sender<GameMsg>
) -> anyhow::Result<()> {
    loop {
        let data_len = read_half.read_u32().await?;
        if data_len > MAX_PACKET_SIZE {
            return Err(anyhow::anyhow!("Packet too large: {}", data_len));
        }
        let mut buf = vec![0; data_len as usize];
        read_half.read_exact(&mut buf).await?;
        let msg = GameMsg::from_bytes(&buf);
        info!("recv msg: {:?}", msg);

        if let Err(e) = msg_from_server_sender.try_send(msg) {
            error!("Failed to forward: {}, discard", e);
        }
    }
}

第三部分:两个版本对比

维度 版本一:Channel + watch 版本二:控制通道 + 回调
业务层接收 Arc<Mutex<Receiver>>,手动 lock + recv 回调函数 FnMut(GameMsg),自动触发
业务层发送 Sender::send Sender::blocking_send
取消机制 watch::Sender<bool> + JoinHandle::await 独立 mpsc 控制通道 (TcpMsg)
地址更新 update().await 等待旧任务退出 update_server_addr() 非阻塞,通过控制通道通知
停止方式 stop_server_and_wait().await 等待退出 stop() 发信号 + abort() 强制终止回调任务
重连控制 后台循环自动重连,sleep 期间不响应取消 同:sleep 期间不响应控制命令
代码复杂度 较高:Arc<Mutex>watch、生命周期管理 较低:控制通道直接分发命令
耦合度 网络层与业务层通过 Channel 解耦,但 API 繁琐 网络层直接调用业务层回调,耦合更高但使用更简单

第四部分:关键教训

4.1 tokio::select! 取消的是 Future,不是系统调用

根据 tokio::select! 的文档:

"returning when the first branch completes, cancelling the remaining branches"

这里的 "cancelling" 指的是drop 掉未完成的 Future,而不是中断 Future 内部已经发起的系统调用。

rust 复制代码
async fn connect_server(addr: &str) -> anyhow::Result<TcpStream> {
    // TcpStream::connect 内部:
    // 1. 创建 socket(系统调用)
    // 2. 发起非阻塞 connect(系统调用,返回 EINPROGRESS)
    // 3. 注册到 epoll 等待可写事件
    // 4. 事件到达后检查连接状态
    TcpStream::connect(addr).await
}

 tokio::select! {
    res = connect_server(addr) => res,
    _ = sleep(Duration::from_secs(3)) => {},
}
// sleep 先触发,connect_server 的 Future 被 drop

Future 被 drop 后会发生什么:

阶段 状态
socket 文件描述符 仍在内核中
TCP 三次握手 继续进行,不受 Future drop 影响
epoll 注册 被清理,不再接收事件
Future 内的后续代码 不会执行

结果: TcpStream::connect 发起的系统调用继续运行,但 Future 被 drop 后没人处理结果。JoinHandle::await 要等这个系统调用完成(或超时)才能返回,通常 20~75 秒。

rust 复制代码
// 解决:用 tokio::time::timeout 控制整个 connect 的超时
// 超时后 timeout 返回 Err,但底层的系统调用仍在运行
// 不过 JoinHandle 可以正常结束,因为 timeout 的 Future 已完成
let stream = tokio::time::timeout(
    Duration::from_secs(5),
    TcpStream::connect(addr)
).await??;

4.2 watch::Sender 广播给所有 Receiver 克隆

rust 复制代码
// 问题:旧任务和新任务共享同一个 watch 值
let cancel_rx = self.cancel_rx.clone();  // 旧任务
self.cancel_tx.send_replace(false);       // 新任务启动,但旧任务也收到 false

// 解决:版本二改用独立 mpsc 控制通道,每个命令只被消费一次

4.3 select! 分支中的 return 会导致后续代码 unreachable

rust 复制代码
// 错误:shutdown 不会执行
let res = tokio::select! {
    _ = cancel => { return Ok("cancel".to_string()); }
    res = read => res,
};
stream.shutdown().await?;  // unreachable

// 正确
let res = tokio::select! {
    _ = cancel => Ok("cancel".to_string()),
    res = read => res,
};
stream.shutdown().await?;  // 可达

4.4 协议层必须设长度上限

rust 复制代码
const MAX_PACKET_SIZE: u32 = 5 * 1024 * 1024;
let data_len = read_half.read_u32().await?;
if data_len > MAX_PACKET_SIZE {
    return Err(anyhow::anyhow!("Packet too large"));
}

4.5 回调 vs Channel 的选择

场景 推荐
业务层需要精细控制接收时机 Channel(版本一)
业务层只需被动处理消息 回调(版本二)
追求简单 回调(版本二)
需要背压、不能丢消息 Channel + send().await

4.6 blocking_send 的适用场景

版本二在 update_server_addrstopsend_msg_to_server 中都用 blocking_send,因为调用方在同步上下文(如游戏主循环)。如果在异步上下文中,应改用 send().await 避免阻塞线程。


结论

版本一通过 Arc<Mutex<Receiver>>watch 实现了严格的生命周期管理(update 等待旧任务退出),但 API 繁琐、有死锁风险。版本二通过控制通道和回调简化了使用方式,但耦合更高、部分问题(超时、sleep 期间不响应)仍未解决。

两个版本的核心改进路径一致:错误传播 → 取消机制 → 任务生命周期管理 → 资源释放可达性确认 。选择取决于业务层是否需要精细控制接收时机,以及是否能容忍 blocking_send 的线程阻塞。