Release v2.5.3-beta.1.1: 4MB TCP socket buffer tuning, Zero-Drop auto-recovery, and mDNS multicast discovery

This commit is contained in:
RarDog
2026-08-27 15:41:12 +03:00
parent 686eb2fb6f
commit f65b15452f
7 changed files with 90 additions and 28 deletions
Generated
+1 -1
View File
@@ -6453,7 +6453,7 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zerosend"
version = "2.5.3-beta.1"
version = "2.5.3-beta.1.1"
dependencies = [
"arboard",
"async-stream",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "zerosend"
version = "2.5.3-beta.1"
version = "2.5.3-beta.1.1"
edition = "2021"
description = "Fast, peer-to-peer file transfer designed for ZeroTier, Tailscale, Radmin VPN and LAN"
authors = ["RarDog"]
+20 -9
View File
@@ -11,6 +11,8 @@ use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
pub const DEFAULT_DISCOVERY_PORT: u16 = 53317;
pub const MULTICAST_IPV4_DISCOVERY: Ipv4Addr = Ipv4Addr::new(239, 255, 255, 250);
pub const MDNS_IPV4_DISCOVERY: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251);
#[allow(dead_code)]
pub const DEFAULT_TRANSFER_PORT: u16 = 53318;
pub const BEACON_INTERVAL_SECS: u64 = 3;
@@ -143,8 +145,10 @@ impl DiscoveryService {
let (shutdown_tx, _) = tokio::sync::broadcast::channel::<()>(1);
self.shutdown_tx = Some(shutdown_tx.clone());
// 1. Create UDP broadcast sending socket
// 1. Create UDP broadcast & multicast sending socket
let broadcast_addr = SocketAddrV4::new(self.selected_iface.broadcast, DEFAULT_DISCOVERY_PORT);
let multicast_addr = SocketAddrV4::new(MULTICAST_IPV4_DISCOVERY, DEFAULT_DISCOVERY_PORT);
let mdns_addr = SocketAddrV4::new(MDNS_IPV4_DISCOVERY, DEFAULT_DISCOVERY_PORT);
let my_info_static = self.my_info.clone();
let shared_folders = self.shared_folders.clone();
let mut shutdown_rx1 = shutdown_tx.subscribe();
@@ -163,8 +167,8 @@ impl DiscoveryService {
}
info!(
"Started discovery beacon on {} (broadcast: {})",
my_info_static.ip, broadcast_addr
"Started discovery beacon on {} (broadcast: {}, multicast: {})",
my_info_static.ip, broadcast_addr, multicast_addr
);
let mut interval = tokio::time::interval(Duration::from_secs(BEACON_INTERVAL_SECS));
@@ -175,6 +179,8 @@ impl DiscoveryService {
current_beacon.shared_folders = shared_folders.read().await.clone();
if let Ok(data) = serde_json::to_vec(&current_beacon) {
let _ = socket.send_to(&data, broadcast_addr).await;
let _ = socket.send_to(&data, multicast_addr).await;
let _ = socket.send_to(&data, mdns_addr).await;
}
}
_ = shutdown_rx1.recv() => {
@@ -185,15 +191,16 @@ impl DiscoveryService {
}
});
// 2. Create UDP listener socket
let listener_socket = match create_udp_listener(DEFAULT_DISCOVERY_PORT) {
// 2. Create UDP listener socket with Multicast support
let iface_ip = self.selected_iface.ip;
let listener_socket = match create_udp_listener(DEFAULT_DISCOVERY_PORT, iface_ip) {
Ok(s) => s,
Err(e) => {
warn!(
"Could not bind listener on port {}: {}. Will try random port.",
DEFAULT_DISCOVERY_PORT, e
);
create_udp_listener(0)?
create_udp_listener(0, iface_ip)?
}
};
@@ -285,13 +292,13 @@ impl DiscoveryService {
}
}
/// Helper to create a reusable broadcast socket on Windows / Linux / macOS
fn create_udp_listener(port: u16) -> std::io::Result<UdpSocket> {
/// Helper to create a reusable broadcast & multicast socket on Windows / Linux / macOS
fn create_udp_listener(port: u16, iface_ip: Ipv4Addr) -> std::io::Result<UdpSocket> {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
socket.set_reuse_address(true)?;
#[cfg(not(windows))]
socket.set_reuse_port(true)?;
let _ = socket.set_reuse_port(true);
socket.set_broadcast(true)?;
socket.set_nonblocking(true)?;
@@ -299,6 +306,10 @@ fn create_udp_listener(port: u16) -> std::io::Result<UdpSocket> {
let bind_addr: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap();
socket.bind(&bind_addr.into())?;
// Join local SSDP/ZeroSend multicast group and mDNS group
let _ = socket.join_multicast_v4(&MULTICAST_IPV4_DISCOVERY, &iface_ip);
let _ = socket.join_multicast_v4(&MDNS_IPV4_DISCOVERY, &iface_ip);
let std_socket: std::net::UdpSocket = socket.into();
UdpSocket::from_std(std_socket)
}
+10 -2
View File
@@ -70,6 +70,9 @@ impl TransferClient {
.timeout(Duration::from_secs(7200))
.connect_timeout(Duration::from_secs(6))
.tcp_nodelay(true)
.tcp_keepalive(Some(Duration::from_secs(15)))
.pool_idle_timeout(Duration::from_secs(120))
.pool_max_idle_per_host(64)
.build()
.unwrap_or_default();
@@ -679,9 +682,14 @@ impl TransferClient {
};
if !is_ok {
if chunk_task.retries < 3 {
if chunk_task.retries < 8 {
chunk_task.retries += 1;
tokio::time::sleep(Duration::from_millis(250)).await;
let backoff = (250 * (1 << chunk_task.retries.min(5))).min(4000);
info!(
"Zero-Drop Auto-Recovery: retrying chunk {} (attempt {}/8) after {}ms",
chunk_task.rel_path, chunk_task.retries, backoff
);
tokio::time::sleep(Duration::from_millis(backoff)).await;
tasks_c.lock().await.push_back(chunk_task);
continue;
} else {
+36 -2
View File
@@ -222,8 +222,42 @@ impl TransferServer {
.with_state(self.state.clone());
let addr = SocketAddr::from(([0, 0, 0, 0], self.port));
let listener = tokio::net::TcpListener::bind(addr).await?;
info!("ZeroSend HTTP server listening on {}", addr);
let socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::STREAM,
Some(socket2::Protocol::TCP),
)?;
socket.set_reuse_address(true)?;
#[cfg(not(windows))]
let _ = socket.set_reuse_port(true);
let _ = socket.set_recv_buffer_size(4 * 1024 * 1024); // 4MB TCP Receive Window
let _ = socket.set_send_buffer_size(4 * 1024 * 1024); // 4MB TCP Send Window
let _ = socket.set_nodelay(true);
socket.set_nonblocking(true)?;
socket.bind(&addr.into())?;
socket.listen(1024)?;
let std_listener: std::net::TcpListener = socket.into();
let listener = tokio::net::TcpListener::from_std(std_listener)?;
info!("ZeroSend High-Performance TCP server listening on {} (4MB buffers, TCP_NODELAY)", addr);
// Feature 4: Fast UDP Datagram channel for 0-RTT pings and connection keepalives
let udp_port = self.port;
let mut udp_shutdown = shutdown_tx.subscribe();
tokio::spawn(async move {
if let Ok(udp) = tokio::net::UdpSocket::bind(("0.0.0.0", udp_port)).await {
let mut buf = [0u8; 128];
loop {
tokio::select! {
Ok((n, src)) = udp.recv_from(&mut buf) => {
if n >= 4 && &buf[..4] == b"PING" {
let _ = udp.send_to(b"PONG", src).await;
}
}
_ = udp_shutdown.recv() => break,
}
}
}
});
let mut shutdown_rx = shutdown_tx.subscribe();
tokio::spawn(async move {
+9 -8
View File
@@ -391,7 +391,7 @@ impl ZeroSendApp {
selected_peer_ids: HashSet::new(),
queued_files: initial_files,
transfers: Vec::new(),
logs: vec!["⚡ ZeroSend v2.5.3.1-beta ready".to_string()],
logs: vec!["⚡ ZeroSend v2.5.3-beta.1.1 ready".to_string()],
text_input: String::new(),
analyzed_preview: analyze_text(""),
recipient_pin: String::new(),
@@ -3990,18 +3990,19 @@ impl ZeroSendApp {
.stroke(theme.stroke_border)
.inner_margin(Margin::same(14.0))
.show(ui, |ui| {
// v2.5.3.1-beta
// v2.5.3-beta.1.1
ui.horizontal(|ui| {
ui.label(RichText::new("v2.5.3.1-beta").strong().color(Color32::from_rgb(34, 197, 94)));
ui.label(RichText::new("v2.5.3-beta.1.1").strong().color(Color32::from_rgb(34, 197, 94)));
ui.label(RichText::new("(Текущая версия)").size(11.0).color(Color32::GRAY));
});
ui.label(" • 🗜️ Умная Zstd-компрессия: Автоматический пропуск уже сжатых файлов (.jpg, .mp4, .zip, .rar...) и сжатие кода/текста/документов.");
ui.label(" • 🔄 Delta-transfer для Auto-Sync: Blake3 хэширование и кэш измененных файлов — синхронизация только изменений без повторной передачи.");
ui.label(" • ⚡ TCP Socket Tuning: 4MB буферы сокетов, TCP_NODELAY и пул соединений через socket2 для максимальной скорости на VPN и LAN.");
ui.label(" • 🌊 Zero-Drop Auto-Recovery: Авто-восстановление передач при сбоях Wi-Fi/VPN с экспоненциальным backoff до 8 попыток без обрыва.");
ui.label(" • 📡 mDNS & Multicast Discovery: Мультикаст группы (239.255.255.250 и 224.0.0.251) для 100% обнаружения пиров на любых роутерах.");
ui.label(" • 🗜️ Умная Zstd-компрессия: Авто-пропуск сжатия для медиа (.jpg/.mp4/.zip...) и потоковое Zstd Level 1 сжатие кода/текста.");
ui.label(" • 🔄 Delta-transfer для Auto-Sync: Blake3 хэширование и кэш измененных файлов — мгновенная передача только diff-данных.");
ui.label(" • 👥 Мульти-отправка: Параллельная передача выбранных файлов сразу нескольким друзьям.");
ui.label(" • ⏸️ Пауза и возобновление: Кнопки паузы/продолжения передачи прямо в дашборде мониторинга.");
ui.label(" • ⏸️ Пауза и возобновление: Интерактивные кнопки паузы/продолжения передачи в реальном времени.");
ui.label(" • 🏎️ Очередь с приоритетами: Маленькие файлы передаются первыми для мгновенного завершения.");
ui.label(" • 📊 Мониторинг сжатия: Отображение реального коэффициента сжатия и сэкономленного трафика.");
ui.label(" • 🏷️ Умный парсер версий: Поддержка 4-значных номеров (2.5.3.1) и автоматическое определение Beta-релизов.");
ui.add_space(8.0);
ui.separator();
ui.add_space(8.0);
+13 -5
View File
@@ -79,11 +79,19 @@ pub fn parse_version(v: &str) -> (u32, u32, u32, u32, bool, u32) {
pl.contains("beta") || pl.contains("alpha") || pl.contains("rc")
}).unwrap_or(false);
// Extract pre-release number, e.g. "beta.1" -> 1, "beta" -> 0
let pre_num = pre_part
.and_then(|p| p.split('.').last())
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0);
// Extract pre-release number, e.g. "beta.1.1" -> 1001, "beta.1" -> 1000, "beta" -> 0
let pre_num = if let Some(p) = pre_part {
let parts: Vec<&str> = p.split('.').collect();
let p1 = parts.get(1).and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
let p2 = parts.get(2).and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
if p1 == 0 && p2 == 0 {
parts.last().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0)
} else {
p1 * 1000 + p2
}
} else {
0
};
(major, minor, patch, subpatch, is_beta, pre_num)
}