Skip to main content

raft_rust/net/
transport.rs

1//! TCP 监听与出站 peer 发送。
2
3// peer 地址表与连接缓存
4use std::collections::HashMap;
5// 按连接缓冲读写
6use std::io::{BufReader, BufWriter};
7// 监听与已连接流
8use std::net::{SocketAddr, TcpListener, TcpStream};
9// 跨线程共享连接表与 writer
10use std::sync::{Arc, Mutex};
11// 每连接一线程处理
12use std::thread;
13// 连接/读写超时
14use std::time::Duration;
15
16// 入站事件投递给 Raft 驱动线程
17use crossbeam::channel::Sender;
18// 连接生命周期日志
19use log::{debug, info, warn};
20// 客户端请求关联 ID
21use uuid::Uuid;
22
23// 帧读写与线路消息
24use super::codec::{read_msg, write_msg, WireMsg};
25// 网络错误映射
26use crate::error::{Error, Result};
27// Raft 信封与客户端请求/响应
28use crate::raft::{Envelope, NodeID, Request, Response};
29
30/// 向已知 peer 发送 Raft 信封(懒连接 + 失败重试一次)。
31// Clone 便于多个发送点共享同一出站连接池
32#[derive(Clone)]
33// 出站邮箱:地址表 + 复用连接池
34pub struct PeerOutbox {
35    // 节点 ID → 静态配置地址(启动时固定)
36    addrs: Arc<HashMap<NodeID, SocketAddr>>,
37    // 节点 ID → 复用的 TCP 连接(写失败后剔除)
38    conns: Arc<Mutex<HashMap<NodeID, TcpStream>>>,
39// PeerOutbox 字段定义结束
40}
41
42// 出站发送与连接管理实现
43impl PeerOutbox {
44    // 用配置中的 peer 列表构造出站邮箱
45    pub fn new(peers: Vec<(NodeID, SocketAddr)>) -> Self {
46        // 初始化地址表与空连接池
47        Self {
48            // 列表转 HashMap,便于 O(1) 查地址
49            addrs: Arc::new(peers.into_iter().collect()),
50            // 初始无连接,首次发送时懒建立
51            conns: Arc::new(Mutex::new(HashMap::new())),
52        // Self 构造结束
53        }
54    // new 结束
55    }
56
57    // 发送一条 Raft 信封;失败则清连接重试一次
58    pub fn send_raft(&self, env: Envelope) -> Result<()> {
59        // 目标节点(信封已带 to)
60        let to = env.to;
61        // 包装为线路层 Raft 消息
62        let msg = WireMsg::Raft(env);
63        // 首次尝试
64        if let Err(e) = self.send_to(to, &msg) {
65            // 断线后清连接再试一次。
66            self.invalidate(to);
67            // 重试仍失败则返回第二次错误
68            if let Err(e2) = self.send_to(to, &msg) {
69                // 再次失败,丢弃坏连接
70                self.invalidate(to);
71                // 向上抛出最终错误
72                return Err(e2);
73            // 二次重试分支结束
74            }
75            // 抑制首次错误的 unused 告警(已重试成功)
76            let _ = e;
77        // 首次失败分支结束
78        }
79        // 首次或重试成功
80        Ok(())
81    // send_raft 结束
82    }
83
84    // 向指定 peer 写一条消息,必要时懒连接
85    fn send_to(&self, id: NodeID, msg: &WireMsg) -> Result<()> {
86        // 查配置地址;未知 ID 视为配置/路由错误
87        let addr = self
88            // 从地址表按节点 ID 查询
89            .addrs
90            // 取引用
91            .get(&id)
92            // 复制 SocketAddr 所有权
93            .copied()
94            // 未配置 peer 则报 IO 路由错误
95            .ok_or_else(|| Error::IO(format!("unknown peer {id}")))?;
96        // 锁定连接表
97        let mut guard = self.conns.lock().expect("lock");
98        // 无缓存连接则建立
99        if !guard.contains_key(&id) {
100            // 按配置地址建立新 TCP
101            let stream = Self::connect(addr)?;
102            // 放入连接池供后续复用
103            guard.insert(id, stream);
104        // 懒连接分支结束
105        }
106        // 取出可变引用写入
107        let stream = guard.get_mut(&id).unwrap();
108        // 写帧;失败则移除连接以便下次重连
109        match write_msg(stream, msg) {
110            // 写成功直接返回
111            Ok(()) => Ok(()),
112            // 写失败进入清理路径
113            Err(e) => {
114                // 写失败时丢掉连接,下次懒重连。
115                guard.remove(&id);
116                // 把原始写错误继续向上抛
117                Err(e)
118            // Err 分支结束
119            }
120        // match write_msg 结束
121        }
122    // send_to 结束
123    }
124
125    // 带超时建立 TCP,并设置 nodelay/读写超时
126    fn connect(addr: SocketAddr) -> Result<TcpStream> {
127        // 2 秒连接超时,避免领导发送路径长时间卡住
128        let stream = TcpStream::connect_timeout(&addr, Duration::from_secs(2))
129            // 连接失败附带目标地址信息
130            .map_err(|e| Error::IO(format!("connect {addr}: {e}")))?;
131        // 关闭 Nagle,降低小包延迟(心跳/投票)
132        stream.set_nodelay(true).ok();
133        // 避免对端卡住时 write 永久阻塞领导发送路径。
134        stream.set_write_timeout(Some(Duration::from_secs(2))).ok();
135        // 读超时:本 outbox 主要写,但设置以免异常路径阻塞
136        stream.set_read_timeout(Some(Duration::from_secs(2))).ok();
137        // 返回已配置超时的流
138        Ok(stream)
139    // connect 结束
140    }
141
142    /// 主动丢弃某 peer 的缓存连接(例如连续发送失败时)。
143    pub fn invalidate(&self, id: NodeID) {
144        // 从连接池移除,下次 send 会重新 connect
145        self.conns.lock().expect("lock").remove(&id);
146    // invalidate 结束
147    }
148// PeerOutbox impl 结束
149}
150
151/// 入站事件:Raft 信封,或客户端请求(需异步回 ClientReply)。
152pub enum Inbound {
153    // 来自 peer 的 Raft 协议消息,交给 Node::step
154    Raft(Envelope),
155    // 来自 CLI/客户端的业务请求;处理完后通过 reply 回写
156    Client {
157        // 与 ClientReply 匹配的请求 ID
158        id: Uuid,
159        // 客户端请求体
160        request: Request,
161        // 单次响应通道,由连接处理线程阻塞等待
162        reply: Sender<std::result::Result<Response, Error>>,
163    // Client 变体字段结束
164    },
165// Inbound 枚举结束
166}
167
168/// 在后台接受连接,把解码后的消息送入 `inbound_tx`。
169pub fn spawn_listener(addr: SocketAddr, inbound_tx: Sender<Inbound>) -> Result<()> {
170    // 绑定监听地址
171    let listener = TcpListener::bind(addr).map_err(|e| Error::IO(format!("bind {addr}: {e}")))?;
172    // 便于运维确认节点已就绪
173    info!("Listening on {addr}");
174    // 独立 accept 线程,避免阻塞 Raft tick 循环
175    thread::spawn(move || {
176        // 持续接受连接
177        for conn in listener.incoming() {
178            // 区分 accept 成功与失败
179            match conn {
180                // 新连接建立成功
181                Ok(stream) => {
182                    // 关闭 Nagle
183                    stream.set_nodelay(true).ok();
184                    // 读超时 30s:空闲连接最终会退出 handle_conn
185                    stream.set_read_timeout(Some(Duration::from_secs(30))).ok();
186                    // 写超时 2s:避免回写 ClientReply 卡死
187                    stream.set_write_timeout(Some(Duration::from_secs(2))).ok();
188                    // 克隆 inbound 发送端给连接线程
189                    let tx = inbound_tx.clone();
190                    // 每连接一线程,简化同步模型
191                    thread::spawn(move || handle_conn(stream, tx));
192                // Ok 分支结束
193                }
194                // accept 失败只告警,继续监听
195                Err(e) => warn!("accept error: {e}"),
196            // match conn 结束
197            }
198        // for incoming 结束
199        }
200    // accept 线程闭包结束
201    });
202    // 监听线程已启动,主路径立即返回
203    Ok(())
204// spawn_listener 结束
205}
206
207// 单连接读写循环:Raft 直接转发;Client 同步等待响应再回写
208fn handle_conn(stream: TcpStream, inbound_tx: Sender<Inbound>) {
209    // 读半边用 BufReader
210    let mut reader = BufReader::new(stream.try_clone().expect("clone"));
211    // 写半边共享给可能的超时回写路径
212    let writer = Arc::new(Mutex::new(BufWriter::new(stream)));
213    // 直到读失败/通道关闭
214    loop {
215        // 读一帧
216        let msg = match read_msg(&mut reader) {
217            // 解码成功得到业务消息
218            Ok(m) => m,
219            // 读/解码失败:对端关闭或超时
220            Err(e) => {
221                // 对端关闭或超时
222                debug!("connection closed: {e}");
223                // 退出本连接循环
224                break;
225            // Err 分支结束
226            }
227        // match read_msg 结束
228        };
229        // 按消息类型分发
230        match msg {
231            // Raft 消息:投递给驱动线程
232            WireMsg::Raft(env) => {
233                // 驱动线程已退出则结束连接
234                if inbound_tx.send(Inbound::Raft(env)).is_err() {
235                    // 入站通道已关,停止读循环
236                    break;
237                // send 失败分支结束
238                }
239            // Raft 分支结束
240            }
241            // 客户端请求:建立 reply 通道并等待处理结果
242            WireMsg::Client { id, request } => {
243                // 容量 1:一次请求一次响应
244                let (reply_tx, reply_rx) = crossbeam::channel::bounded(1);
245                // 投递到 Raft 线程;失败则断连
246                if inbound_tx
247                    // 封装为 Inbound::Client 投递给驱动
248                    .send(Inbound::Client { id, request, reply: reply_tx })
249                    // 通道关闭视为节点关闭
250                    .is_err()
251                // if 条件后进入失败体
252                {
253                    // 驱动已退出,关闭本连接
254                    break;
255                // send 失败分支结束
256                }
257                // 等待 Raft 线程处理完再写回(阻塞本连接线程,简单可靠)。
258                match reply_rx.recv_timeout(Duration::from_secs(30)) {
259                    // 正常拿到响应,写 ClientReply
260                    Ok(response) => {
261                        // 独占写半边,串行回写
262                        let mut w = writer.lock().expect("writer");
263                        // 写失败则关闭连接
264                        if write_msg(&mut *w, &WireMsg::ClientReply { id, response }).is_err() {
265                            // 对端写失败,结束连接
266                            break;
267                        // write 失败分支结束
268                        }
269                    // Ok 响应分支结束
270                    }
271                    // 超时:向客户端返回 IO 超时错误
272                    Err(_) => {
273                        // 超时仍尝试回写错误响应
274                        let mut w = writer.lock().expect("writer");
275                        // 忽略回写失败(连接可能已断)
276                        let _ = write_msg(
277                            // 解引用 Mutex 得到可写 BufWriter
278                            &mut *w,
279                            // 构造超时错误的 ClientReply
280                            &WireMsg::ClientReply {
281                                // 与请求相同的关联 ID
282                                id,
283                                // 30s 内未完成(选举/复制慢或领导者不可达)
284                                response: Err(Error::IO("request timed out".into())),
285                            // ClientReply 构造结束
286                            },
287                        // write_msg 调用结束
288                        );
289                    // 超时分支结束
290                    }
291                // match recv_timeout 结束
292                }
293            // Client 请求分支结束
294            }
295            // 服务端入站不应出现 ClientReply
296            WireMsg::ClientReply { .. } => {
297                // 服务端不应收到
298            }
299        // match msg 结束
300        }
301    // 连接读循环结束
302    }
303// handle_conn 结束
304}
305
306/// CLI:向单个 peer 发送客户端请求并等待响应。
307pub fn run_client_request(
308    // 目标节点地址
309    addr: SocketAddr,
310    // 业务请求
311    request: Request,
312    // 等待响应超时
313    timeout: Duration,
314// 返回业务响应或网络/协议错误
315) -> Result<Response> {
316    // 建立短连接
317    let stream = TcpStream::connect_timeout(&addr, Duration::from_secs(2))
318        // 连接失败附带地址
319        .map_err(|e| Error::IO(format!("connect {addr}: {e}")))?;
320    // 关闭 Nagle
321    stream.set_nodelay(true).ok();
322    // 读超时使用调用方指定值
323    stream.set_read_timeout(Some(timeout)).ok();
324    // 写超时同样限制
325    stream.set_write_timeout(Some(timeout)).ok();
326    // 可变以便 write_msg/read_msg
327    let mut stream = stream;
328    // 生成本次请求关联 ID
329    let id = Uuid::new_v4();
330    // 发送 Client 帧
331    write_msg(&mut stream, &WireMsg::Client { id, request })?;
332    // 读取并校验响应
333    match read_msg(&mut stream)? {
334        // ID 匹配则解包 Result
335        WireMsg::ClientReply { id: rid, response } if rid == id => response,
336        // 类型或 ID 不符视为协议错误
337        other => Err(Error::InvalidData(format!("unexpected reply: {other:?}"))),
338    // match 响应结束
339    }
340// run_client_request 结束
341}