Enhance TFTP server implementation with advanced monitoring and UI improvements

- Completely refactor TFTP server implementation with more robust file transfer handling
- Add detailed transfer tracking with active transfers table
- Implement periodic transfer status updates
- Improve log and UI layout for TFTP server tab
- Add more granular error handling and logging
- Enhance threading and socket management for file transfers
This commit is contained in:
2025-02-16 03:19:44 +03:00
parent c95915483f
commit f1ca31c198
2 changed files with 417 additions and 202 deletions

View File

@@ -829,86 +829,202 @@ class SerialAppGUI(tk.Tk):
about_window.grab_set() about_window.grab_set()
def create_tftp_tab(self, frame): def create_tftp_tab(self, frame):
"""Создание вкладки TFTP сервера."""
# Создаем фрейм для управления TFTP сервером # Создаем фрейм для управления TFTP сервером
control_frame = ttk.Frame(frame) tftp_frame = ttk.Frame(frame)
control_frame.pack(fill=X, pady=5) tftp_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
# Создаем и размещаем элементы управления
controls_frame = ttk.LabelFrame(tftp_frame, text="Управление TFTP сервером")
controls_frame.pack(fill=X, padx=5, pady=5)
# IP адрес # IP адрес
ip_frame = ttk.Frame(control_frame) ip_frame = ttk.Frame(controls_frame)
ip_frame.pack(fill=X, pady=5) ip_frame.pack(fill=X, padx=5, pady=2)
ttk.Label(ip_frame, text="IP адрес:").pack(side=LEFT, padx=5) ttk.Label(ip_frame, text="IP адрес:").pack(side=LEFT, padx=5)
self.tftp_ip_var = StringVar(value="0.0.0.0") self.tftp_ip_var = StringVar(value="0.0.0.0")
ttk.Entry(ip_frame, textvariable=self.tftp_ip_var, width=15).pack(side=LEFT, padx=5) self.tftp_ip_entry = ttk.Entry(ip_frame, textvariable=self.tftp_ip_var)
self.tftp_ip_entry.pack(fill=X, expand=True, padx=5)
# Порт # Порт
port_frame = ttk.Frame(control_frame) port_frame = ttk.Frame(controls_frame)
port_frame.pack(fill=X, pady=5) port_frame.pack(fill=X, padx=5, pady=2)
ttk.Label(port_frame, text="Порт:").pack(side=LEFT, padx=5) ttk.Label(port_frame, text="Порт:").pack(side=LEFT, padx=5)
self.tftp_port_var = StringVar(value="69") self.tftp_port_var = StringVar(value="69")
ttk.Entry(port_frame, textvariable=self.tftp_port_var, width=6).pack(side=LEFT, padx=5) self.tftp_port_entry = ttk.Entry(port_frame, textvariable=self.tftp_port_var)
self.tftp_port_entry.pack(fill=X, expand=True, padx=5)
# Кнопки управления # Кнопки управления
button_frame = ttk.Frame(control_frame) buttons_frame = ttk.Frame(controls_frame)
button_frame.pack(fill=X, pady=5) buttons_frame.pack(fill=X, padx=5, pady=5)
self.start_tftp_button = ttk.Button(button_frame, text="Запустить сервер", command=self.start_tftp_server)
self.start_tftp_button = ttk.Button(
buttons_frame,
text="Запустить сервер",
command=self.start_tftp_server
)
self.start_tftp_button.pack(side=LEFT, padx=5) self.start_tftp_button.pack(side=LEFT, padx=5)
self.stop_tftp_button = ttk.Button(button_frame, text="Остановить сервер", command=self.stop_tftp_server, state="disabled")
self.stop_tftp_button = ttk.Button(
buttons_frame,
text="Остановить сервер",
command=self.stop_tftp_server,
state="disabled"
)
self.stop_tftp_button.pack(side=LEFT, padx=5) self.stop_tftp_button.pack(side=LEFT, padx=5)
# Лог TFTP сервера # Лог сервера
log_frame = ttk.Frame(frame) log_frame = ttk.LabelFrame(tftp_frame, text="Лог сервера")
log_frame.pack(fill=BOTH, expand=True, pady=5) log_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
ttk.Label(log_frame, text="Лог сервера:").pack(anchor=W, padx=5)
self.tftp_log_text = tk.Text(log_frame, wrap="word", height=15) self.tftp_log_text = tk.Text(log_frame, wrap=tk.WORD, height=10)
self.tftp_log_text.pack(fill=BOTH, expand=True, padx=5, pady=5) self.tftp_log_text.pack(fill=BOTH, expand=True, padx=5, pady=5)
# Добавляем скроллбар для лога
scrollbar = ttk.Scrollbar(self.tftp_log_text, command=self.tftp_log_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tftp_log_text.config(yscrollcommand=scrollbar.set)
# Статус передач
transfers_frame = ttk.LabelFrame(tftp_frame, text="Активные передачи")
transfers_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
# Создаем таблицу для отображения активных передач
columns = ("client", "filename", "progress", "remaining", "time")
self.transfers_tree = ttk.Treeview(transfers_frame, columns=columns, show="headings")
# Настраиваем заголовки колонок
self.transfers_tree.heading("client", text="Клиент")
self.transfers_tree.heading("filename", text="Файл")
self.transfers_tree.heading("progress", text="Прогресс")
self.transfers_tree.heading("remaining", text="Осталось")
self.transfers_tree.heading("time", text="Время")
# Настраиваем ширину колонок
self.transfers_tree.column("client", width=120)
self.transfers_tree.column("filename", width=150)
self.transfers_tree.column("progress", width=100)
self.transfers_tree.column("remaining", width=100)
self.transfers_tree.column("time", width=80)
self.transfers_tree.pack(fill=BOTH, expand=True, padx=5, pady=5)
# Инициализация TFTP сервера
self.tftp_server = None
self.tftp_server_thread = None
def start_tftp_server(self): def start_tftp_server(self):
"""Запуск TFTP сервера."""
try: try:
ip = self.tftp_ip_var.get() ip = self.tftp_ip_var.get()
port = int(self.tftp_port_var.get()) port = int(self.tftp_port_var.get())
if not self.tftp_server: # Создаем экземпляр TFTP сервера
self.tftp_server = TFTPServer("Firmware") self.tftp_server = TFTPServer("Firmware")
# Устанавливаем функцию обратного вызова для логирования # Устанавливаем callback для логирования
def log_callback(message): def log_callback(message):
self.tftp_log_text.insert(END, f"{message}\n") self.append_tftp_log(message)
self.tftp_log_text.see(END) # Обновляем информацию о передачах
self.update_transfers_info()
self.tftp_server.set_log_callback(log_callback) self.tftp_server.set_log_callback(log_callback)
threading.Thread( # Запускаем сервер в отдельном потоке
self.tftp_server_thread = threading.Thread(
target=self.run_tftp_server, target=self.run_tftp_server,
args=(ip, port), args=(ip, port),
daemon=True daemon=True
).start() )
self.tftp_server_thread.start()
# Обновляем состояние кнопок
self.start_tftp_button.config(state="disabled") self.start_tftp_button.config(state="disabled")
self.stop_tftp_button.config(state="normal") self.stop_tftp_button.config(state="normal")
self.tftp_ip_entry.config(state="disabled")
self.tftp_port_entry.config(state="disabled")
self.append_tftp_log(f"[INFO] TFTP сервер запущен на {ip}:{port}")
# Запускаем периодическое обновление информации о передачах
self.update_transfers_periodically()
except ValueError:
messagebox.showerror("Ошибка", "Некорректный порт")
except Exception as e: except Exception as e:
self.append_tftp_log(f"[ERROR] Ошибка запуска сервера: {str(e)}")
messagebox.showerror("Ошибка", f"Не удалось запустить TFTP сервер: {str(e)}") messagebox.showerror("Ошибка", f"Не удалось запустить TFTP сервер: {str(e)}")
def run_tftp_server(self, ip, port): def run_tftp_server(self, ip, port):
"""Запуск TFTP сервера в отдельном потоке."""
try: try:
self.tftp_server.start_server(ip, port) self.tftp_server.start_server(ip, port)
except Exception as e: except Exception as e:
self.tftp_log_text.insert(END, f"[ERROR] Ошибка TFTP сервера: {str(e)}\n") self.append_tftp_log(f"[ERROR] Ошибка работы сервера: {str(e)}")
self.stop_tftp_server()
def stop_tftp_server(self): def stop_tftp_server(self):
"""Остановка TFTP сервера."""
if self.tftp_server: if self.tftp_server:
try: try:
self.tftp_server.stop_server() self.tftp_server.stop_server()
self.tftp_server = None if self.tftp_server_thread:
self.tftp_log_text.insert(END, "[INFO] TFTP сервер остановлен\n") self.tftp_server_thread.join(timeout=2.0)
except Exception as e:
self.tftp_log_text.insert(END, f"[ERROR] Ошибка при остановке TFTP сервера: {str(e)}\n") # Обновляем состояние кнопок
finally:
self.start_tftp_button.config(state="normal") self.start_tftp_button.config(state="normal")
self.stop_tftp_button.config(state="disabled") self.stop_tftp_button.config(state="disabled")
self.tftp_ip_entry.config(state="normal")
self.tftp_port_entry.config(state="normal")
self.append_tftp_log("[INFO] TFTP сервер остановлен")
# Очищаем таблицу передач
for item in self.transfers_tree.get_children():
self.transfers_tree.delete(item)
except Exception as e:
self.append_tftp_log(f"[ERROR] Ошибка остановки сервера: {str(e)}")
messagebox.showerror("Ошибка", f"Не удалось остановить TFTP сервер: {str(e)}")
def append_tftp_log(self, message):
"""Добавление сообщения в лог TFTP сервера."""
self.tftp_log_text.insert(END, message + "\n")
self.tftp_log_text.see(END)
def update_transfers_info(self):
"""Обновление информации об активных передачах."""
if not self.tftp_server:
return
# Очищаем текущие записи
for item in self.transfers_tree.get_children():
self.transfers_tree.delete(item)
# Добавляем информацию о текущих передачах
for client_addr, transfer_info in self.tftp_server.active_transfers.items():
filename = transfer_info['filename']
bytes_sent = transfer_info['bytes_sent']
filesize = transfer_info['filesize']
start_time = transfer_info['start_time']
# Вычисляем прогресс
progress = f"{bytes_sent}/{filesize} байт"
remaining = filesize - bytes_sent
elapsed_time = time.time() - start_time
# Добавляем запись в таблицу
self.transfers_tree.insert("", END, values=(
f"{client_addr[0]}:{client_addr[1]}",
filename,
progress,
f"{remaining} байт",
f"{elapsed_time:.1f}с"
))
def update_transfers_periodically(self):
"""Периодическое обновление информации о передачах."""
if self.tftp_server and self.tftp_server.running:
self.update_transfers_info()
# Планируем следующее обновление через 1 секунду
self.after(1000, self.update_transfers_periodically)
# ========================== # ==========================
# Парсер аргументов (не используется) # Парсер аргументов (не используется)

View File

@@ -1,178 +1,277 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
TFTP сервер для передачи прошивки с компьютера на коммутатор.
- Создает сервер по заданному IP и порту.
- Расшаривает папку Firmware.
- Показывает текущее состояние сервера и статус передачи файла:
- кому (IP устройства),
- сколько осталось байт,
- сколько передано байт,
- время передачи.
"""
import os import os
import tftpy import socket
import logging import struct
from typing import Optional, Callable, Dict import threading
from dataclasses import dataclass import time
from datetime import datetime
@dataclass
class FileTransfer:
filename: str
client_address: tuple
start_time: datetime
total_blocks: int = 0
current_block: int = 0
status: str = "в процессе"
class TFTPServer: class TFTPServer:
def __init__(self, root_path: str): def __init__(self, share_folder):
""" """
Инициализация TFTP сервера Инициализация TFTP сервера.
Args: :param share_folder: Путь к папке, содержащей файлы (например, папка 'Firmware')
root_path (str): Путь к корневой директории для файлов
""" """
self.root_path = root_path self.share_folder = share_folder
self.server: Optional[tftpy.TftpServer] = None self.log_callback = None
self.logger = logging.getLogger(__name__) self.running = False
self.log_callback: Optional[Callable[[str], None]] = None self.server_socket = None
self.progress_callback: Optional[Callable[[str, tuple, int, int, str], None]] = None self.lock = threading.Lock()
self.active_transfers: Dict[str, FileTransfer] = {} # Словарь активных передач для мониторинга их статуса.
# Ключ адрес клиента, значение словарь с информацией о передаче.
self.active_transfers = {}
def set_log_callback(self, callback: Callable[[str], None]): def set_log_callback(self, callback):
"""Установка функции обратного вызова для логирования""" """
Установка функции обратного вызова для логирования сообщений.
:param callback: Функция, принимающая строку сообщения.
"""
self.log_callback = callback self.log_callback = callback
def set_progress_callback(self, callback: Callable[[str, tuple, int, int, str], None]): def log(self, message):
"""Установка функции обратного вызова для отображения прогресса""" """
self.progress_callback = callback Функция логирования: вызывает callback (если задан) или выводит сообщение в консоль.
def log_message(self, message: str, level: str = "INFO"): :param message: Строка с сообщением для логирования.
"""Логирование сообщения""" """
if self.log_callback: if self.log_callback:
self.log_callback(f"[{level}] {message}") self.log_callback(message)
if level == "INFO": else:
self.logger.info(message) print(message)
elif level == "ERROR":
self.logger.error(message)
elif level == "WARNING":
self.logger.warning(message)
def update_progress(self, transfer: FileTransfer): def start_server(self, ip, port):
"""Обновление прогресса передачи файла"""
if self.progress_callback:
self.progress_callback(
transfer.filename,
transfer.client_address,
transfer.current_block,
transfer.total_blocks,
transfer.status
)
def start_server(self, ip: str = "0.0.0.0", port: int = 69):
""" """
Запуск TFTP сервера Запуск TFTP сервера на указанном IP и порту.
Args: :param ip: IP-адрес для привязки сервера.
ip (str): IP адрес для прослушивания (по умолчанию "0.0.0.0") :param port: Порт для TFTP сервера.
port (int): Порт для прослушивания (по умолчанию 69)
""" """
self.running = True
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server_socket.bind((ip, port))
self.log(f"[INFO] TFTP сервер запущен на {ip}:{port}")
try: try:
if not os.path.exists(self.root_path): while self.running:
os.makedirs(self.root_path) try:
# Устанавливаем таймаут для возможности периодической проверки состояния сервера.
# Создаем серверный класс с обработчиками событий self.server_socket.settimeout(1.0)
server = tftpy.TftpServer(self.root_path) data, client_addr = self.server_socket.recvfrom(2048)
if data:
# Добавляем обработчики событий # Каждая обработка запроса выполняется в отдельном потоке.
def on_read_request(filename: str, client_address: tuple): threading.Thread(target=self.handle_request, args=(data, client_addr), daemon=True).start()
self.log_message(f"Получен запрос на скачивание файла '{filename}' от {client_address[0]}:{client_address[1]}") except socket.timeout:
file_path = os.path.join(self.root_path, filename) continue
if not os.path.exists(file_path): except Exception as e:
self.log_message(f"Файл '{filename}' не найден", "ERROR") self.log(f"[ERROR] Ошибка получения данных: {str(e)}")
return False finally:
self.server_socket.close()
# Создаем запись о передаче файла self.log("[INFO] TFTP сервер остановлен.")
file_size = os.path.getsize(file_path)
total_blocks = (file_size + 511) // 512 # Размер блока TFTP = 512 байт
transfer = FileTransfer(
filename=filename,
client_address=client_address,
start_time=datetime.now(),
total_blocks=total_blocks
)
self.active_transfers[f"{filename}_{client_address}"] = transfer
self.update_progress(transfer)
return True
def on_write_request(filename: str, client_address: tuple):
self.log_message(f"Получен запрос на загрузку файла '{filename}' от {client_address[0]}:{client_address[1]}")
transfer = FileTransfer(
filename=filename,
client_address=client_address,
start_time=datetime.now()
)
self.active_transfers[f"{filename}_{client_address}"] = transfer
self.update_progress(transfer)
return True
def on_read_block_sent(filename: str, block_number: int, client_address: tuple):
key = f"{filename}_{client_address}"
if key in self.active_transfers:
transfer = self.active_transfers[key]
transfer.current_block = block_number
if transfer.current_block >= transfer.total_blocks:
transfer.status = "завершено"
del self.active_transfers[key]
self.update_progress(transfer)
self.log_message(f"Отправлен блок {block_number} файла '{filename}' клиенту {client_address[0]}:{client_address[1]}")
def on_write_block_received(filename: str, block_number: int, client_address: tuple):
key = f"{filename}_{client_address}"
if key in self.active_transfers:
transfer = self.active_transfers[key]
transfer.current_block = block_number
if block_number == 1: # Первый блок
file_path = os.path.join(self.root_path, filename)
if os.path.exists(file_path):
transfer.total_blocks = (os.path.getsize(file_path) + 511) // 512
if transfer.current_block >= transfer.total_blocks:
transfer.status = "завершено"
del self.active_transfers[key]
self.update_progress(transfer)
self.log_message(f"Получен блок {block_number} файла '{filename}' от клиента {client_address[0]}:{client_address[1]}")
def on_error(error: Exception, client_address: tuple):
# Помечаем все активные передачи для этого клиента как ошибочные
for key, transfer in list(self.active_transfers.items()):
if transfer.client_address == client_address:
transfer.status = f"ошибка: {str(error)}"
self.update_progress(transfer)
del self.active_transfers[key]
self.log_message(f"Ошибка при обработке запроса от {client_address[0]}:{client_address[1]}: {str(error)}", "ERROR")
# Устанавливаем обработчики
server.on_read_request = on_read_request
server.on_write_request = on_write_request
server.on_read_block_sent = on_read_block_sent
server.on_write_block_received = on_write_block_received
server.on_error = on_error
self.server = server
self.log_message(f"Запуск TFTP сервера на {ip}:{port}")
self.server.listen(ip, port)
except Exception as e:
error_msg = f"Ошибка при запуске TFTP сервера: {str(e)}"
self.log_message(error_msg, "ERROR")
raise
def stop_server(self): def stop_server(self):
"""Остановка TFTP сервера""" """
if self.server: Остановка TFTP сервера.
try: """
# Помечаем все активные передачи как прерванные self.running = False
for transfer in self.active_transfers.values(): self.log("[INFO] Остановка TFTP сервера...")
transfer.status = "прервано"
self.update_progress(transfer)
self.active_transfers.clear()
self.server.stop() def handle_request(self, data, client_addr):
self.log_message("TFTP сервер остановлен") """
except Exception as e: Обработка входящего запроса от клиента.
error_msg = f"Ошибка при остановке TFTP сервера: {str(e)}"
self.log_message(error_msg, "ERROR") :param data: Полученные данные (UDP-пакет).
raise :param client_addr: Адрес клиента, отправившего пакет.
"""
if len(data) < 2:
self.log(f"[WARN] Получен некорректный пакет от {client_addr}")
return
opcode = struct.unpack("!H", data[:2])[0]
if opcode == 1: # RRQ (Read Request) запрос на чтение файла
self.handle_rrq(data, client_addr)
else:
self.log(f"[WARN] Неподдерживаемый запрос (опкод {opcode}) от {client_addr}")
def handle_rrq(self, data, client_addr):
"""
Обработка запроса на чтение файла (RRQ).
:param data: Данные запроса.
:param client_addr: Адрес клиента.
"""
try:
# RRQ формата: 2 байта опкода, затем строка имени файла, за которой следует 0,
# затем строка режима (например, "octet"), и завершается 0.
parts = data[2:].split(b'\0')
if len(parts) < 2:
self.log(f"[WARN] Некорректный RRQ пакет от {client_addr}")
return
filename = parts[0].decode('utf-8')
mode = parts[1].decode('utf-8').lower()
self.log(f"[INFO] Получен RRQ от {client_addr}: файл '{filename}', режим '{mode}'")
if mode != "octet":
self.send_error(client_addr, 0, "Поддерживается только octet режим")
return
file_path = os.path.join(self.share_folder, filename)
if not os.path.isfile(file_path):
self.send_error(client_addr, 1, "Файл не найден")
return
# Запускаем передачу файла в новом потоке.
threading.Thread(target=self.send_file, args=(file_path, client_addr), daemon=True).start()
except Exception as e:
self.log(f"[ERROR] Ошибка обработки RRQ: {str(e)}")
def send_error(self, client_addr, error_code, error_message):
"""
Отправка сообщения об ошибке клиенту.
:param client_addr: Адрес клиента.
:param error_code: Код ошибки.
:param error_message: Текст ошибки.
"""
# Формируем TFTP пакет ошибки: 2 байта опкода (5), 2 байта кода ошибки, сообщение об ошибке и завершающий 0.
packet = struct.pack("!HH", 5, error_code) + error_message.encode('utf-8') + b'\0'
self.server_socket.sendto(packet, client_addr)
self.log(f"[INFO] Отправлено сообщение об ошибке '{error_message}' клиенту {client_addr}")
def send_file(self, file_path, client_addr):
"""
Передача файла клиенту по протоколу TFTP.
В процессе передачи обновляется состояние активной передачи, которое логируется с информацией:
- адрес клиента,
- имя файла,
- размер файла,
- количество переданных байт,
- оставшиеся байты,
- время передачи.
:param file_path: Полный путь к файлу для передачи.
:param client_addr: Адрес клиента.
"""
BLOCK_SIZE = 512
transfer_socket = None
try:
if not os.path.exists(file_path):
self.log(f"[ERROR] Файл '{file_path}' не существует")
self.send_error(client_addr, 1, "Файл не найден")
return
filesize = os.path.getsize(file_path)
if filesize == 0:
self.log(f"[ERROR] Файл '{file_path}' пуст")
self.send_error(client_addr, 0, "Файл пуст")
return
start_time = time.time()
file_basename = os.path.basename(file_path)
# Регистрируем активную передачу
with self.lock:
self.active_transfers[client_addr] = {
'filename': file_basename,
'filesize': filesize,
'bytes_sent': 0,
'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
block_num = 1
bytes_sent = 0
last_progress_time = time.time()
with open(file_path, "rb") as f:
while True:
try:
data_block = f.read(BLOCK_SIZE)
if not data_block: # Достигнут конец файла
break
# Формируем TFTP пакет данных
packet = struct.pack("!HH", 3, block_num) + data_block
attempts = 0
ack_received = False
# Попытка отправки текущего блока (до 3 повторных попыток)
while attempts < 3 and not ack_received:
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) # Ожидаем только 4 байта для ACK
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}.")
except socket.timeout:
attempts += 1
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_num} "
f"от {client_addr}. Попытка {attempts+1}.")
except Exception as e:
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:
self.log(f"[ERROR] Ошибка при обработке блока {block_num}: {str(e)}")
raise
elapsed_time = time.time() - start_time
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} "
f"завершена за {elapsed_time:.2f} сек. Всего отправлено {bytes_sent} байт.")
except Exception as e:
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:
if transfer_socket:
try:
transfer_socket.close()
except:
pass
with self.lock:
if client_addr in self.active_transfers:
del self.active_transfers[client_addr]