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:
parent
467d582095
commit
136c7877d3
187
TFTPServer.py
187
TFTPServer.py
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user