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
This commit is contained in:
2025-02-16 03:50:27 +03:00
parent 467d582095
commit 136c7877d3

View File

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