本部分实现了一个基于 request-response 协议的文件共享系统。通过本示例,你将学习如何使用 libp2p 的 request-response 协议实现文件列表查询和文件传输功能,以及如何与 DHT 和 Gossipsub 等其他协议集成,构建一个完整的 P2P 文件共享应用。文件共享是 P2P 网络的核心功能之一,它允许节点之间直接交换文件,无需中央服务器的参与,提高了数据传输的效率和可靠性。
核心功能和目标:
- 实现基于 request-response 协议的文件共享功能
- 支持文件列表查询和文件传输
- 与 DHT 和 Gossipsub 协议集成
- 构建完整的 P2P 文件共享应用
在整个项目中的位置和作用:
- 作为第六个实践示例
- 展示如何使用 request-response 协议
- 为项目添加文件共享能力
- 实现节点之间的直接文件交换
学习该部分的预期收益:
- 掌握 request-response 协议的工作原理
- 学会实现文件共享功能
- 理解如何与其他协议集成
- 为构建完整的 P2P 应用奠定基础
6.1 创建 FileShare 项目
cd crates
cargo new file_share
6.2 修改 crates/file_share/Cargo.toml 文件
[package]
name = "file_share"
version.workspace = true
edition.workspace = true
publish.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
tracing.workspace = true
tracing-subscriber.workspace = true
tokio.workspace = true
anyhow.workspace = true
libp2p = { workspace = true, features = [
"tcp",
"noise",
"yamux",
"tokio",
"mdns",
"identify",
"gossipsub",
"kad",
"request-response",
"cbor",
"macros",
] }
serde ={ workspace = true, features = ["derive"]}
serde_json.workspace = true
chrono.workspace = true
依赖说明:
- request-response:提供 request-response 协议实现,用于文件列表查询和文件传输
- cbor:提供 CBOR 序列化/反序列化功能,用于请求和响应的数据编码
- kad:提供 Kademlia DHT 协议实现,用于分布式数据存储
- gossipsub:提供 Gossipsub 发布/订阅协议实现,用于消息广播
- serde:提供序列化/反序列化功能
- serde_json:提供 JSON 序列化/反序列化功能
- chrono:提供时间处理功能
6.3 修改 crates/file_share/src/main.rs 文件
实现基于 request-response 协议的文件共享系统:
use std::{collections::HashMap, time::Duration};
use libp2p::{
PeerId, StreamProtocol, Swarm, Transport, core, futures::StreamExt, gossipsub, identify,
identity, kad, mdns, noise, request_response, swarm, tcp, yamux,
};
use tokio::io::AsyncBufReadExt;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
enum FileRequest {
List,
File { filename: String },
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
enum FileResponse {
List(Vec<String>),
File { filename: String, data: Vec<u8> },
Error(String),
}
#[derive(swarm::NetworkBehaviour)]
struct FileShareNode {
mdns: mdns::tokio::Behaviour,
identify: identify::Behaviour,
gossipsub: gossipsub::Behaviour,
kademlia: kad::Behaviour<kad::store::MemoryStore>,
file_share: request_response::cbor::Behaviour<FileRequest, FileResponse>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct UserInfo {
peer_id: String,
nickname: String,
joined_at: u64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct ChatMessage {
from: String,
content: String,
timestamp: u64,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 初始化日志追踪器,启用ANSI颜色输出
tracing_subscriber::fmt().with_ansi(true).init();
// 输出启动信息
tracing::info!("start......");
let key = identity::Keypair::generate_ed25519();
let peer_id = PeerId::from(key.public());
tracing::info!("Local peer id: {:?}", peer_id);
let transport = tcp::tokio::Transport::new(tcp::Config::default())
.upgrade(core::upgrade::Version::V1)
.authenticate(noise::Config::new(&key)?)
.multiplex(yamux::Config::default())
.timeout(Duration::from_secs(60))
.boxed();
let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), peer_id)?;
let identify = identify::Behaviour::new(identify::Config::new(
"demo/1.0.0".to_string(),
key.public(),
));
let mut gossipsub = gossipsub::Behaviour::new(
gossipsub::MessageAuthenticity::Signed(key.clone()),
gossipsub::Config::default(),
)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let chat_topic = gossipsub::IdentTopic::new("chat");
gossipsub.subscribe(&chat_topic)?;
let kademlia = kad::Behaviour::new(peer_id, kad::store::MemoryStore::new(peer_id));
let file_share = request_response::cbor::Behaviour::new(
[(
StreamProtocol::new("/file/1.0.0"),
request_response::ProtocolSupport::Full,
)],
request_response::Config::default(),
);
let behaviour = FileShareNode {
mdns,
identify,
gossipsub,
kademlia,
file_share,
};
let mut swarm = Swarm::new(
transport,
behaviour,
peer_id,
swarm::Config::with_tokio_executor().with_idle_connection_timeout(Duration::from_secs(60)),
);
swarm
.behaviour_mut()
.kademlia
.set_mode(Some(kad::Mode::Server));
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
let mut local_files = HashMap::new();
local_files.insert(
"readme.txt".to_string(),
"欢迎使用libp2p文件共享系统".as_bytes().to_vec(),
);
local_files.insert(
"notes.md".to_string(),
"# 学习笔记
- libp2p很强大
- Rust异步编程"
.as_bytes()
.to_vec(),
);
let user_info = UserInfo {
peer_id: peer_id.to_string(),
nickname: format!("文件节点-{}", &peer_id.to_string()[0..8]),
joined_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
};
let user_info_msg = serde_json::to_string(&user_info)?;
let record_key = kad::RecordKey::new(&format!("user:{}", peer_id));
let record = kad::Record::new(record_key, user_info_msg.into_bytes());
if let Err(e) = swarm
.behaviour_mut()
.kademlia
.put_record(record, kad::Quorum::One)
{
tracing::warn!("存储记录失败(网络中可能没有其他节点): {:?}", e);
}
let join_msg = ChatMessage {
from: peer_id.to_string(),
content: "大家好!我提供文件共享服务".to_string(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
};
let msg = serde_json::to_string(&join_msg)?;
if let Err(e) = swarm
.behaviour_mut()
.gossipsub
.publish(chat_topic.clone(), msg.as_bytes())
{
tracing::warn!("发布加入消息失败: {:?}", e);
}
tracing::info!("🚀 文件共享节点启动");
tracing::info!("可用命令:");
tracing::info!(" /files <peer_id> - 请求文件列表");
tracing::info!(" /get <peer_id> <filename> - 下载文件");
tracing::info!(" /myfiles - 查看本地文件");
let stdin = tokio::io::stdin();
let mut reader = tokio::io::BufReader::new(stdin).lines();
loop {
tokio::select! {
line = reader.next_line() => {
if let Ok(Some(line)) = line {
let cmd = line.trim();
if cmd.starts_with("/files ") {
let target_peer = cmd.trim_start_matches("/files ").trim();
if !target_peer.is_empty() {
if let Ok(peer_id) = target_peer.parse() {
let request = FileRequest::List;
swarm.behaviour_mut().file_share.send_request(&peer_id, request);
tracing::info!("正在请求 {} 的文件列表...", target_peer);
}
}
} else if cmd.starts_with("/get ") {
let args: Vec<&str> = cmd.trim_start_matches("/get ").splitn(2, ' ').collect();
if args.len() == 2 {
let target_peer = args[0];
let filename = args[1];
if let Ok(peer_id) = target_peer.parse() {
let request = FileRequest::File { filename: filename.to_string() };
swarm.behaviour_mut().file_share.send_request(&peer_id, request);
tracing::info!("正在从 {} 请求文件 {}...", target_peer, filename);
}
}
} else if cmd == "/myfiles" {
tracing::info!("我的文件列表:");
for (filename, _) in &local_files {
tracing::info!(" - {}", filename);
}
} else if !cmd.is_empty() && !cmd.starts_with('/') {
let msg = ChatMessage {
from: peer_id.to_string(),
content: line.trim().to_string(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
};
let msg = serde_json::to_string(&msg)?;
if let Err(e) = swarm.behaviour_mut().gossipsub.publish(chat_topic.clone(), msg.as_bytes()) {
tracing::error!("❌ 发布消息失败: {:?}", e);
}
}
}
}
event = swarm.select_next_some() => {
match event {
swarm::SwarmEvent::NewListenAddr { address, .. } => tracing::info!("👂 监听地址: {address}"),
swarm::SwarmEvent::Behaviour(event) => match event {
FileShareNodeEvent::Mdns(event) => match event {
mdns::Event::Discovered(list) => {
let local_peer_id = *swarm.local_peer_id();
for (peer_id, multiaddr) in list {
// 避免连接到自己
if peer_id != local_peer_id {
tracing::info!("🔍 发现新节点: {}", peer_id);
tracing::info!("🔗 节点地址: {}", multiaddr);
// 尝试连接,忽略连接错误
match swarm.dial(multiaddr) {
Ok(_) => tracing::info!("🔗 连接请求已发送: {}", peer_id),
Err(e) => tracing::error!("❌ 连接错误: {:?}", e),
}
}
}
}
mdns::Event::Expired(list) => {
for (peer_id, _multiaddr) in list {
tracing::warn!("👋 节点离线: {}", peer_id);
}
}
},
FileShareNodeEvent::Identify(event) => match event {
identify::Event::Received { peer_id, info, .. } => {
tracing::info!("📣 节点信息: {peer_id} {info:?}");
}
identify::Event::Sent { peer_id, .. } => {
tracing::info!("📣 发送节点信息: {peer_id}");
}
identify::Event::Error { peer_id, error, .. } => {
tracing::error!("❌ 节点信息错误: {peer_id} {error:?}");
}
identify::Event::Pushed { peer_id, info, .. } => {
tracing::info!("📣 节点信息已推送: {peer_id} {info:?}");
}
},
FileShareNodeEvent::Gossipsub(event) => match event {
gossipsub::Event::Message {message, ..} => {
if let Ok(msg) = serde_json::from_slice::<ChatMessage>(&message.data) {
use chrono::TimeZone;
let time = chrono::Local.timestamp_opt(msg.timestamp as i64, 0).single().map(|t| t.format("%H:%M:%S").to_string()).unwrap_or_else(|| msg.timestamp.to_string());
tracing::info!("[{}] {}: {}", time, msg.from, msg.content);
}
},
gossipsub::Event::Subscribed { topic, peer_id, ..} => {
tracing::info!("✅ 节点: {peer_id} 订阅成功: {topic}");
},
_ => {}
},
FileShareNodeEvent::Kademlia(event) => match event {
kad::Event::OutboundQueryProgressed { result, .. } => {
match result {
kad::QueryResult::GetRecord(Ok(kad::GetRecordOk::FoundRecord(kad::PeerRecord {
record: kad::Record {key, value, ..},
..
}))) => {
tracing::info!("🔍 发现记录 {:?} {:?}", std::str::from_utf8(key.as_ref()).unwrap(), std::str::from_utf8(&value).unwrap());
}
_ => {}
}
},
_ => {}
}
FileShareNodeEvent::FileShare(event) => match event {
request_response::Event::Message { peer, message, .. } => match message {
request_response::Message::Request { request, channel, .. } => {
tracing::info!("收到来自 {} 的文件请求: {:?}", peer, request);
match request {
FileRequest::List => {
let file_list: Vec<String> = local_files.keys().cloned().collect();
let response = FileResponse::List(file_list);
if let Err(e) = swarm.behaviour_mut().file_share.send_response(channel, response){
tracing::error!("❌ 发送文件列表响应错误: {e:?}");
}
},
FileRequest::File { filename } => {
if let Some(data) = local_files.get(&filename) {
let response = FileResponse::File {
filename: filename.clone(),
data: data.clone(),
};
if let Err(e) = swarm.behaviour_mut().file_share.send_response(channel, response) {
tracing::error!("❌ 发送文件响应错误: {e:?}")
}
} else {
let response = FileResponse::Error(format!("文件 {} 不存在", filename));
if let Err(e) = swarm.behaviour_mut().file_share.send_response(channel, response){
tracing::error!("❌ 发送文件响应错误: {e:?}")
}
}
}
}
},
request_response::Message::Response { response, .. } => {
tracing::info!("收到文件响应: {:?}", response);
match response {
FileResponse::List(files) => {
tracing::info!("文件列表:");
for file in files {
tracing::info!(" - {}", file);
}
}
FileResponse::File { filename, data } => {
tracing::info!("收到文件 {} ({} 字节)", filename, data.len());
// 这里可以保存文件
}
FileResponse::Error(err) => {
tracing::error!("错误: {}", err);
}
}
}
}
_ => {}
},
}
swarm::SwarmEvent::ConnectionEstablished { peer_id, .. } => {
tracing::info!("✅ 连接成功: {peer_id}")
}
swarm::SwarmEvent::ConnectionClosed { peer_id, .. } => {
tracing::error!("❌ 连接关闭: {peer_id}")
}
swarm::SwarmEvent::IncomingConnection { .. } => tracing::info!("🔗 接受连接"),
swarm::SwarmEvent::IncomingConnectionError { error, .. } => {
tracing::error!("❌ 接受连接错误: {error:?}")
}
swarm::SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
tracing::error!("❌ 连接 {peer_id:?} 错误: {error:?}")
}
swarm::SwarmEvent::Dialing { peer_id, .. } => tracing::info!("🔗 正在连接: {peer_id:?}"),
_ => {}
}
}
}
}
}
代码详细解析:
-
请求和响应结构:
FileRequest枚举:定义文件操作请求,包括List(请求文件列表)和File { filename: String }(请求特定文件)FileResponse枚举:定义文件操作响应,包括List(Vec<String>)(返回文件列表)、File { filename: String, data: Vec<u8> }(返回文件内容)和Error(String)(返回错误信息)
-
网络行为组合:
- 创建
FileShareNode结构体,组合了mdns::tokio::Behaviour、identify::Behaviour、gossipsub::Behaviour、kad::Behaviour和request_response::cbor::Behaviour - 使用
#[derive(swarm::NetworkBehaviour)]宏自动实现NetworkBehaviourtrait - 定义
FileShareNodeEvent枚举,包含所有行为的事件变体
- 创建
-
request-response 协议初始化:
- 创建
request_response::cbor::Behaviour实例,使用 CBOR 序列化格式 - 配置协议 ID 为 "/file/1.0.0"
- 设置协议支持为
ProtocolSupport::Full,表示既可以发送请求也可以接收请求
- 创建
-
本地文件存储:
- 使用
HashMap存储本地文件,键为文件名,值为文件内容(字节向量) - 初始化两个示例文件:"readme.txt" 和 "notes.md"
- 使用
-
命令处理:
/files <peer_id>:请求指定节点的文件列表/get <peer_id> <filename>:从指定节点下载指定文件/myfiles:查看本地文件列表- 其他输入:作为聊天消息发送
-
文件请求处理:
- 处理
FileRequest::List请求:返回本地文件列表 - 处理
FileRequest::File { filename }请求:返回指定文件的内容,如果文件不存在则返回错误
- 处理
-
文件响应处理:
- 处理
FileResponse::List响应:显示文件列表 - 处理
FileResponse::File { filename, data }响应:显示收到的文件信息 - 处理
FileResponse::Error响应:显示错误信息
- 处理
-
其他事件处理:
- 处理 mDNS 事件,包括发现新节点和节点离线
- 处理 Identify 事件,包括接收和发送身份信息
- 处理 Gossipsub 事件,包括接收和发布消息
- 处理 Kademlia 事件,包括 DHT 查询结果
- 处理连接事件,包括连接建立和关闭
6.4 技术原理
request-response 协议工作原理: request-response 是一种点对点通信协议,其工作原理如下:
- 协议注册:节点注册支持的协议 ID 和版本
- 请求发送:发送方向接收方发送请求消息,包含请求类型和参数
- 请求处理:接收方处理请求,执行相应的操作
- 响应发送:接收方向发送方发送响应消息,包含操作结果
- 响应处理:发送方处理响应,获取操作结果
request-response 协议的优点是:
- 简单直接:点对点通信,无需中间节点
- 可靠性:请求-响应模式确保消息被处理
- 灵活性:可以定义任意的请求和响应格式
- 效率:针对特定操作的通信,避免不必要的数据传输
文件传输机制:
- 文件列表查询:通过
FileRequest::List请求获取远程节点的文件列表 - 文件下载:通过
FileRequest::File { filename }请求获取特定文件的内容 - 响应处理:远程节点根据请求类型返回相应的响应
- 数据序列化:使用 CBOR 格式序列化请求和响应数据
序列化格式:
- CBOR:Concise Binary Object Representation,一种紧凑的二进制数据格式
- 优点:比 JSON 更紧凑,序列化/反序列化速度更快
- 适用场景:适合网络传输和存储
- 与 JSON 对比:体积更小,解析更快,支持更多数据类型
协议集成:
- mDNS:用于本地网络节点发现
- Identify:用于交换节点身份信息
- Gossipsub:用于广播消息和通知
- Kademlia DHT:用于分布式存储节点信息
文件传输流程:
- 节点 A 发现节点 B(通过 mDNS 或 DHT)
- 节点 A 向节点 B 发送文件列表请求
- 节点 B 返回文件列表
- 节点 A 选择要下载的文件,发送文件请求
- 节点 B 读取文件内容并返回
- 节点 A 接收文件内容并处理
6.5 性能优化
文件存储优化:
- 存储介质选择:根据文件大小和访问频率选择合适的存储介质
- 文件索引:实现高效的文件索引机制,提高文件查找速度
- 缓存策略:实现文件缓存,减少重复读取和传输
- 元数据管理:优化文件元数据的存储和管理,提高文件信息检索效率
文件传输优化:
- 分块传输:实现文件分块传输,支持大文件传输和断点续传
- 并行传输:利用多路复用技术,实现并行文件传输
- 压缩传输:对文件内容进行压缩,减少网络传输量
- 流量控制:实现流量控制机制,避免网络拥塞
网络优化:
- 连接复用:复用现有连接,减少连接建立开销
- 路由优化:选择最佳网络路径,减少传输延迟
- 批量操作:批量处理文件请求,减少网络往返
- 协议优化:优化 request-response 协议,减少协议开销
内存优化:
- 内存分配:优化内存分配和释放,减少内存碎片
- 缓冲区管理:合理管理缓冲区大小,避免内存浪费
- 垃圾回收:及时清理过期数据,释放内存资源
- 数据结构:使用高效的数据结构存储文件信息
CPU 优化:
- 并行处理:利用多线程并行处理文件操作
- 异步 IO:使用异步 IO 提高 IO 操作效率
- 计算优化:优化文件处理算法,减少 CPU 开销
- 批处理:批量处理文件操作,减少系统调用
6.6 扩展与应用
功能扩展:
- 文件上传:实现文件上传功能,允许向其他节点发送文件
- 文件搜索:实现分布式文件搜索功能,基于 DHT 存储文件索引
- 文件版本控制:实现文件版本管理,支持文件历史版本查询和恢复
- 文件加密:实现端到端文件加密,提高文件传输安全性
- 文件分享:实现文件分享链接,方便文件共享
协议扩展:
- 自定义协议:基于 request-response 协议实现自定义文件传输协议
- 协议版本管理:实现协议版本协商机制,支持向后兼容
- 协议优化:针对特定场景优化协议设计,提高传输效率
- 与其他协议集成:与 BitTorrent 等其他文件传输协议集成
应用场景:
- 分布式存储:基于 P2P 网络实现分布式文件存储系统
- 内容分发:利用 P2P 网络实现高效的内容分发
- 文件备份:实现分布式文件备份,提高数据可靠性
- 协作编辑:支持多用户协作编辑文件
- 媒体流:实现基于 P2P 的媒体流传输
代码扩展:
- 模块化设计:将文件共享功能封装为独立模块,便于集成到其他项目
- 可配置性:添加配置文件支持,方便参数调整
- 可测试性:添加单元测试和集成测试,确保功能可靠性
- 可监控性:添加监控指标,便于系统监控和性能分析
- 可扩展性:设计插件系统,支持功能扩展
6.7 运行项目
运行步骤:
# 在多个终端运行相同程序
cargo run --package file_share
运行效果:
- 节点启动后会自动发现本地网络中的其他节点
- 每个节点会将自己的用户信息存储到 DHT 中
- 你可以使用
/myfiles命令查看本地文件列表 - 你可以使用
/files <peer_id>命令请求其他节点的文件列表 - 你可以使用
/get <peer_id> <filename>命令从其他节点下载文件 - 节点之间仍然可以正常聊天,消息通过 Gossipsub 协议广播到所有节点
- 文件传输通过 request-response 协议实现,确保文件传输的可靠性
示例输出:
🔑 本地节点ID: 12D3KooW...
👂 监听地址: /ip4/127.0.0.1/tcp/51234
🔍 发现新节点: 12D3KooX...
🔗 连接请求已发送: 12D3KooX...
✅ 连接成功: 12D3KooX...
[14:30:45] 12D3KooX...: 大家好!我提供文件共享服务
/myfiles
我的文件列表:
- readme.txt
- notes.md
/files 12D3KooX...
正在请求 12D3KooX... 的文件列表...
收到文件响应: List(["readme.txt", "notes.md"])
文件列表:
- readme.txt
- notes.md
/get 12D3KooX... readme.txt
正在从 12D3KooX... 请求文件 readme.txt...
收到文件响应: File { filename: "readme.txt", data: [230, 149, 165, 229, 165, 189, 230, 150, 135, 231, 148, 168, 108, 105, 98, 112, 50, 112, 230, 149, 165, 229, 165, 189, 230, 150, 135, 231, 148, 168, 232, 175, 149, 228, 184, 173, 230, 149, 189, 231, 172, 166] }
收到文件 readme.txt (42 字节)
6.8 测试场景
场景 1:本地网络文件共享
- 在同一台机器上启动多个终端,运行文件共享节点
- 观察节点之间的自动发现、连接建立和文件共享
- 测试
/files命令请求文件列表 - 测试
/get命令下载文件
场景 2:多机器文件共享
- 在同一局域网内的不同机器上运行文件共享节点
- 观察跨机器的文件列表请求和文件下载
- 测试网络延迟对文件传输的影响
场景 3:文件不存在测试
- 启动两个节点,尝试从一个节点下载不存在的文件
- 观察错误处理和错误消息显示
场景 4:文件传输性能测试
- 启动多个节点,传输不同大小的文件
- 测试文件传输的速度和可靠性
- 观察大文件传输的处理情况
预期结果:
- 节点能够自动发现并连接到其他节点
- 文件列表请求能够成功返回远程节点的文件列表
- 文件下载能够成功获取远程文件的内容
- 错误处理能够正确处理文件不存在等情况
- 大文件传输能够稳定完成,无数据损坏
6.9 常见问题与解决方案
问题1:文件请求失败
- 原因:网络连接问题、节点离线或协议版本不兼容
- 解决方案:
- 检查网络连接是否正常
- 确保目标节点在线
- 确保使用相同版本的 libp2p 库
- 检查防火墙设置,确保端口开放
问题2:文件传输速度慢
- 原因:网络带宽限制、文件过大或节点性能不足
- 解决方案:
- 优化网络连接,确保稳定的网络环境
- 对于大文件,考虑实现分块传输和断点续传
- 优化节点性能,增加内存和 CPU 资源
- 使用压缩传输减少数据量
问题3:文件传输中断
- 原因:网络不稳定、节点崩溃或连接超时
- 解决方案:
- 实现断点续传功能,支持从断点恢复传输
- 增加连接超时时间,提高传输稳定性
- 实现传输状态监控,及时发现并处理中断
- 优化网络路由,选择更稳定的网络路径
问题4:文件内容损坏
- 原因:网络传输错误、数据校验失败或序列化/反序列化错误
- 解决方案:
- 实现数据校验机制,确保文件完整性
- 使用可靠的序列化格式,如 CBOR 或 Protocol Buffers
- 增加错误处理,及时检测和处理数据损坏
- 实现文件哈希验证,确保文件内容正确
问题5:命令执行失败
- 原因:命令格式错误、参数不正确或节点不支持
- 解决方案:
- 检查命令格式是否正确,如
/files <peer_id>或/get <peer_id> <filename> - 确保提供了正确的节点 ID 和文件名
- 确保目标节点支持文件共享功能
- 查看终端输出的错误信息,了解具体失败原因
- 检查命令格式是否正确,如
问题6:节点发现失败
- 原因:网络隔离、mDNS 配置错误或防火墙阻止
- 解决方案:
- 确保所有节点在同一局域网内
- 检查防火墙设置,允许 mDNS 流量(UDP 5353 端口)
- 尝试使用 Kademlia DHT 进行节点发现
- 手动指定节点地址进行连接
问题7:内存使用过高
- 原因:文件缓存过大、连接数过多或内存泄漏
- 解决方案:
- 优化文件缓存策略,限制缓存大小
- 实现连接池管理,控制并发连接数
- 检查代码中的内存泄漏问题
- 增加节点内存资源或优化内存使用