Compare commits

...

2 Commits

Author SHA1 Message Date
136c7877d3 Refactor TFTP file transfer with improved reliability and error handling
- Implement more robust file transfer mechanism with configurable retry and timeout settings
- Add detailed logging for transfer progress and error scenarios
- Enhance block transfer logic with better error recovery
- Simplify transfer socket management and cleanup process
- Improve overall transfer reliability and error tracking
2025-02-16 03:50:27 +03:00
467d582095 Improve file transfer progress tracking and display
- Add dynamic transfer speed calculation
- Compute and display estimated remaining transfer time
- Enhance remaining bytes display with more informative status
- Update transfers table with more detailed transfer progress information
2025-02-16 03:43:34 +03:00
2 changed files with 96 additions and 107 deletions

View File

@@ -1068,15 +1068,27 @@ class SerialAppGUI(tk.Tk):
# Вычисляем прогресс
progress = f"{bytes_sent}/{filesize} байт"
remaining = filesize - bytes_sent
remaining_bytes = filesize - bytes_sent
elapsed_time = time.time() - start_time
# Вычисляем скорость передачи (байт/сек)
if elapsed_time > 0:
transfer_speed = bytes_sent / elapsed_time
# Вычисляем оставшееся время
if transfer_speed > 0:
remaining_time = remaining_bytes / transfer_speed
remaining_str = f"{remaining_bytes} байт (~{int(remaining_time)}с)"
else:
remaining_str = f"{remaining_bytes} байт (неизвестно)"
else:
remaining_str = f"{remaining_bytes} байт (вычисляется...)"
# Добавляем запись в таблицу
self.transfers_tree.insert("", END, values=(
f"{client_addr[0]}:{client_addr[1]}",
filename,
progress,
f"{remaining} байт",
remaining_str,
f"{elapsed_time:.1f}с"
))

View File

@@ -224,6 +224,8 @@ class TFTPServer:
Передача файла клиенту по протоколу TFTP.
"""
BLOCK_SIZE = 512
MAX_RETRIES = 5
TIMEOUT = 2.0
transfer_socket = None
try:
if not os.path.exists(file_path):
@@ -249,124 +251,99 @@ class TFTPServer:
'start_time': start_time
}
self.log(f"[INFO] Начало передачи файла '{file_basename}' клиенту {client_addr}. Размер файла: {filesize} байт.")
# Создаем отдельный сокет для передачи файла
# Создаем новый сокет для передачи данных
transfer_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
transfer_socket.settimeout(5.0) # Таймаут ожидания ACK
transfer_socket.settimeout(TIMEOUT)
# Добавляем сокет в множество активных сокетов
with self.lock:
self.transfer_sockets.add(transfer_socket)
block_num = 1
bytes_sent = 0
last_progress_time = time.time()
self.log(f"[INFO] Начало передачи файла '{file_basename}' клиенту {client_addr}. Размер файла: {filesize} байт.")
try:
with open(file_path, "rb") as f:
while self.running: # Проверяем флаг running
with open(file_path, 'rb') as file:
block_number = 1
last_successful_block = 0
while True:
# Читаем блок данных
data = file.read(BLOCK_SIZE)
# Формируем и отправляем пакет данных
packet = struct.pack('!HH', 3, block_number) + data
retries = 0
while retries < MAX_RETRIES:
try:
data_block = f.read(BLOCK_SIZE)
if not data_block: # Достигнут конец файла
break
# Проверяем флаг running перед отправкой блока
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
# Формируем TFTP пакет данных
packet = struct.pack("!HH", 3, block_num) + data_block
attempts = 0
ack_received = False
# Попытка отправки текущего блока (до 3 повторных попыток)
while attempts < 3 and not ack_received and self.running:
if transfer_socket is None:
raise Exception("Сокет передачи закрыт")
transfer_socket.sendto(packet, client_addr)
# Ожидаем подтверждение
while True:
try:
transfer_socket.sendto(packet, client_addr)
# Логируем прогресс каждую секунду
current_time = time.time()
if current_time - last_progress_time >= 1.0:
elapsed_time = current_time - start_time
remaining = filesize - bytes_sent
self.log(f"[STATUS] Клиент: {client_addr} | Файл: {file_basename} | "
f"Отправлено: {bytes_sent}/{filesize} байт | "
f"Осталось: {remaining} байт | "
f"Время: {elapsed_time:.2f} сек.")
last_progress_time = current_time
# Ожидаем подтверждение
ack_data, addr = transfer_socket.recvfrom(4)
if addr == client_addr:
ack_opcode, ack_block = struct.unpack("!HH", ack_data)
if ack_opcode == 4 and ack_block == block_num:
ack_received = True
bytes_sent += len(data_block)
with self.lock:
if client_addr in self.active_transfers:
self.active_transfers[client_addr]['bytes_sent'] = bytes_sent
else:
self.log(f"[WARN] Неверный ACK от {client_addr}. "
f"Ожидался блок {block_num}, получен {ack_block}.")
ack_data, ack_addr = transfer_socket.recvfrom(4)
if ack_addr == client_addr and len(ack_data) >= 4:
opcode, ack_block = struct.unpack('!HH', ack_data)
if opcode == 4: # ACK
if ack_block == block_number:
# Успешное подтверждение
last_successful_block = block_number
bytes_sent = min((block_number * BLOCK_SIZE), filesize)
# Обновляем информацию о прогрессе
with self.lock:
if client_addr in self.active_transfers:
self.active_transfers[client_addr]['bytes_sent'] = bytes_sent
# Логируем статус каждую секунду
current_time = time.time()
if current_time - start_time >= 1.0:
bytes_remaining = filesize - bytes_sent
elapsed_time = current_time - start_time
self.log(f"[STATUS] Клиент: {client_addr} | Файл: {file_basename} | "
f"Отправлено: {bytes_sent}/{filesize} байт | "
f"Осталось: {bytes_remaining} байт | "
f"Время: {elapsed_time:.2f} сек.")
break
elif ack_block < block_number:
# Получен старый ACK, игнорируем
continue
except socket.timeout:
attempts += 1
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_num} "
f"от {client_addr}. Попытка {attempts+1}.")
except socket.error as e:
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
self.log(f"[ERROR] Ошибка сокета при отправке блока {block_num}: {str(e)}")
attempts += 1
except Exception as e:
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
self.log(f"[ERROR] Ошибка при отправке блока {block_num}: {str(e)}")
attempts += 1
if not ack_received:
raise Exception(f"Не удалось получить подтверждение для блока {block_num}")
block_num = (block_num + 1) % 65536
break
if last_successful_block == block_number:
break
else:
retries += 1
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_number} от {client_addr}. "
f"Попытка {retries + 1}.")
except Exception as e:
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
raise
retries += 1
self.log(f"[ERROR] Ошибка при передаче блока {block_number}: {str(e)}")
if retries >= MAX_RETRIES:
self.log(f"[ERROR] Превышено максимальное количество попыток для блока {block_number}")
return
block_number += 1
# Если отправили меньше BLOCK_SIZE байт, это был последний блок
if len(data) < BLOCK_SIZE:
break
if bytes_sent == filesize:
elapsed_time = time.time() - start_time
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} "
f"завершена за {elapsed_time:.2f} сек. Всего отправлено {bytes_sent} байт.")
except Exception as e:
if not self.running:
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} прервана: сервер остановлен")
raise
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} завершена успешно")
except Exception as e:
if not self.running:
return # Не логируем повторно о прерывании передачи
self.log(f"[ERROR] Ошибка при передаче файла '{os.path.basename(file_path)}' "
f"клиенту {client_addr}: {str(e)}")
try:
self.send_error(client_addr, 0, str(e))
except:
pass
self.log(f"[ERROR] Ошибка при передаче файла: {str(e)}")
finally:
# Закрываем сокет передачи
if transfer_socket:
try:
with self.lock:
self.transfer_sockets.discard(transfer_socket)
transfer_socket.close()
transfer_socket = None
except:
pass
# Удаляем информацию о передаче
# Очищаем информацию о передаче
with self.lock:
if client_addr in self.active_transfers:
del self.active_transfers[client_addr]
del self.active_transfers[client_addr]
if transfer_socket in self.transfer_sockets:
self.transfer_sockets.remove(transfer_socket)
if transfer_socket:
try:
transfer_socket.close()
except:
pass