Improve TFTP server shutdown and error handling
- Enhance server stop mechanism with more robust socket and thread management - Add better handling of active transfers during server shutdown - Implement additional safety checks and timeout handling - Improve logging and error reporting for server stop process - Prevent potential deadlocks and resource leaks during server termination
This commit is contained in:
@@ -964,9 +964,22 @@ class SerialAppGUI(tk.Tk):
|
|||||||
"""Остановка TFTP сервера."""
|
"""Остановка TFTP сервера."""
|
||||||
if self.tftp_server:
|
if self.tftp_server:
|
||||||
try:
|
try:
|
||||||
|
# Отключаем кнопки на время остановки сервера
|
||||||
|
self.start_tftp_button.config(state="disabled")
|
||||||
|
self.stop_tftp_button.config(state="disabled")
|
||||||
|
|
||||||
|
# Останавливаем сервер
|
||||||
self.tftp_server.stop_server()
|
self.tftp_server.stop_server()
|
||||||
|
|
||||||
|
# Ждем завершения потока сервера с таймаутом
|
||||||
if self.tftp_server_thread:
|
if self.tftp_server_thread:
|
||||||
self.tftp_server_thread.join(timeout=2.0)
|
self.tftp_server_thread.join(timeout=5.0)
|
||||||
|
if self.tftp_server_thread.is_alive():
|
||||||
|
self.append_tftp_log("[WARN] Превышено время ожидания остановки сервера")
|
||||||
|
|
||||||
|
# Очищаем ссылки на сервер и поток
|
||||||
|
self.tftp_server = None
|
||||||
|
self.tftp_server_thread = None
|
||||||
|
|
||||||
# Обновляем состояние кнопок
|
# Обновляем состояние кнопок
|
||||||
self.start_tftp_button.config(state="normal")
|
self.start_tftp_button.config(state="normal")
|
||||||
@@ -984,6 +997,10 @@ class SerialAppGUI(tk.Tk):
|
|||||||
self.append_tftp_log(f"[ERROR] Ошибка остановки сервера: {str(e)}")
|
self.append_tftp_log(f"[ERROR] Ошибка остановки сервера: {str(e)}")
|
||||||
messagebox.showerror("Ошибка", f"Не удалось остановить TFTP сервер: {str(e)}")
|
messagebox.showerror("Ошибка", f"Не удалось остановить TFTP сервер: {str(e)}")
|
||||||
|
|
||||||
|
# Восстанавливаем состояние кнопок в случае ошибки
|
||||||
|
self.start_tftp_button.config(state="disabled")
|
||||||
|
self.stop_tftp_button.config(state="normal")
|
||||||
|
|
||||||
def append_tftp_log(self, message):
|
def append_tftp_log(self, message):
|
||||||
"""Добавление сообщения в лог TFTP сервера."""
|
"""Добавление сообщения в лог TFTP сервера."""
|
||||||
self.tftp_log_text.insert(END, message + "\n")
|
self.tftp_log_text.insert(END, message + "\n")
|
||||||
|
|||||||
131
TFTPServer.py
131
TFTPServer.py
@@ -30,6 +30,7 @@ class TFTPServer:
|
|||||||
self.running = False
|
self.running = False
|
||||||
self.server_socket = None
|
self.server_socket = None
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
self.transfer_sockets = set() # Множество для хранения всех активных сокетов передачи
|
||||||
# Словарь активных передач для мониторинга их статуса.
|
# Словарь активных передач для мониторинга их статуса.
|
||||||
# Ключ – адрес клиента, значение – словарь с информацией о передаче.
|
# Ключ – адрес клиента, значение – словарь с информацией о передаче.
|
||||||
self.active_transfers = {}
|
self.active_transfers = {}
|
||||||
@@ -60,33 +61,106 @@ class TFTPServer:
|
|||||||
:param ip: IP-адрес для привязки сервера.
|
:param ip: IP-адрес для привязки сервера.
|
||||||
:param port: Порт для TFTP сервера.
|
:param port: Порт для TFTP сервера.
|
||||||
"""
|
"""
|
||||||
|
if self.running:
|
||||||
|
self.log("[WARN] Сервер уже запущен")
|
||||||
|
return
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
|
try:
|
||||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
self.server_socket.bind((ip, port))
|
self.server_socket.bind((ip, port))
|
||||||
self.log(f"[INFO] TFTP сервер запущен на {ip}:{port}")
|
self.log(f"[INFO] TFTP сервер запущен на {ip}:{port}")
|
||||||
try:
|
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
# Устанавливаем таймаут для возможности периодической проверки состояния сервера.
|
|
||||||
self.server_socket.settimeout(1.0)
|
self.server_socket.settimeout(1.0)
|
||||||
data, client_addr = self.server_socket.recvfrom(2048)
|
data, client_addr = self.server_socket.recvfrom(2048)
|
||||||
if data:
|
if data and self.running:
|
||||||
# Каждая обработка запроса выполняется в отдельном потоке.
|
|
||||||
threading.Thread(target=self.handle_request, args=(data, client_addr), daemon=True).start()
|
threading.Thread(target=self.handle_request, args=(data, client_addr), daemon=True).start()
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except socket.error as e:
|
||||||
|
if self.running: # Логируем ошибку только если сервер еще запущен
|
||||||
self.log(f"[ERROR] Ошибка получения данных: {str(e)}")
|
self.log(f"[ERROR] Ошибка получения данных: {str(e)}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if self.running: # Логируем ошибку только если сервер еще запущен
|
||||||
|
self.log(f"[ERROR] Ошибка получения данных: {str(e)}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"[ERROR] Ошибка запуска сервера: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
|
self.running = False
|
||||||
|
if self.server_socket:
|
||||||
|
try:
|
||||||
self.server_socket.close()
|
self.server_socket.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.server_socket = None
|
||||||
self.log("[INFO] TFTP сервер остановлен.")
|
self.log("[INFO] TFTP сервер остановлен.")
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
"""
|
"""
|
||||||
Остановка TFTP сервера.
|
Остановка TFTP сервера.
|
||||||
"""
|
"""
|
||||||
self.running = False
|
if not self.running:
|
||||||
|
return
|
||||||
|
|
||||||
self.log("[INFO] Остановка TFTP сервера...")
|
self.log("[INFO] Остановка TFTP сервера...")
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Закрываем основной сокет сервера первым
|
||||||
|
if self.server_socket:
|
||||||
|
try:
|
||||||
|
# Создаем временный сокет и отправляем пакет самому себе,
|
||||||
|
# чтобы разблокировать recvfrom
|
||||||
|
temp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
server_address = self.server_socket.getsockname()
|
||||||
|
temp_socket.sendto(b'', server_address)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
temp_socket.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.server_socket.close()
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"[WARN] Ошибка при закрытии основного сокета: {str(e)}")
|
||||||
|
finally:
|
||||||
|
self.server_socket = None
|
||||||
|
|
||||||
|
# Закрываем все активные сокеты передачи
|
||||||
|
with self.lock:
|
||||||
|
active_sockets = list(self.transfer_sockets)
|
||||||
|
self.transfer_sockets.clear()
|
||||||
|
active_transfers = dict(self.active_transfers)
|
||||||
|
self.active_transfers.clear()
|
||||||
|
|
||||||
|
# Закрываем сокеты передачи после очистки множества
|
||||||
|
for sock in active_sockets:
|
||||||
|
try:
|
||||||
|
if sock:
|
||||||
|
sock.close()
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"[WARN] Ошибка при закрытии сокета передачи: {str(e)}")
|
||||||
|
|
||||||
|
# Отправляем сообщения об остановке для активных передач
|
||||||
|
for client_addr, transfer_info in active_transfers.items():
|
||||||
|
try:
|
||||||
|
self.send_error(client_addr, 0, "Сервер остановлен")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"[ERROR] Ошибка при остановке сервера: {str(e)}")
|
||||||
|
finally:
|
||||||
|
self.running = False # Гарантируем, что флаг running будет False
|
||||||
|
self.log("[INFO] TFTP сервер остановлен.")
|
||||||
|
|
||||||
def handle_request(self, data, client_addr):
|
def handle_request(self, data, client_addr):
|
||||||
"""
|
"""
|
||||||
@@ -193,24 +267,36 @@ class TFTPServer:
|
|||||||
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(5.0) # Таймаут ожидания ACK
|
||||||
|
|
||||||
|
# Добавляем сокет в множество активных сокетов
|
||||||
|
with self.lock:
|
||||||
|
self.transfer_sockets.add(transfer_socket)
|
||||||
|
|
||||||
block_num = 1
|
block_num = 1
|
||||||
bytes_sent = 0
|
bytes_sent = 0
|
||||||
last_progress_time = time.time()
|
last_progress_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
while True:
|
while self.running: # Проверяем флаг running
|
||||||
try:
|
try:
|
||||||
data_block = f.read(BLOCK_SIZE)
|
data_block = f.read(BLOCK_SIZE)
|
||||||
if not data_block: # Достигнут конец файла
|
if not data_block: # Достигнут конец файла
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Проверяем флаг running перед отправкой блока
|
||||||
|
if not self.running:
|
||||||
|
raise Exception("Передача прервана: сервер остановлен")
|
||||||
|
|
||||||
# Формируем TFTP пакет данных
|
# Формируем TFTP пакет данных
|
||||||
packet = struct.pack("!HH", 3, block_num) + data_block
|
packet = struct.pack("!HH", 3, block_num) + data_block
|
||||||
attempts = 0
|
attempts = 0
|
||||||
ack_received = False
|
ack_received = False
|
||||||
|
|
||||||
# Попытка отправки текущего блока (до 3 повторных попыток)
|
# Попытка отправки текущего блока (до 3 повторных попыток)
|
||||||
while attempts < 3 and not ack_received:
|
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)
|
transfer_socket.sendto(packet, client_addr)
|
||||||
|
|
||||||
@@ -226,7 +312,7 @@ class TFTPServer:
|
|||||||
last_progress_time = current_time
|
last_progress_time = current_time
|
||||||
|
|
||||||
# Ожидаем подтверждение
|
# Ожидаем подтверждение
|
||||||
ack_data, addr = transfer_socket.recvfrom(4) # Ожидаем только 4 байта для ACK
|
ack_data, addr = transfer_socket.recvfrom(4)
|
||||||
if addr == client_addr:
|
if addr == client_addr:
|
||||||
ack_opcode, ack_block = struct.unpack("!HH", ack_data)
|
ack_opcode, ack_block = struct.unpack("!HH", ack_data)
|
||||||
if ack_opcode == 4 and ack_block == block_num:
|
if ack_opcode == 4 and ack_block == block_num:
|
||||||
@@ -242,7 +328,14 @@ class TFTPServer:
|
|||||||
attempts += 1
|
attempts += 1
|
||||||
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_num} "
|
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_num} "
|
||||||
f"от {client_addr}. Попытка {attempts+1}.")
|
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:
|
except Exception as e:
|
||||||
|
if not self.running:
|
||||||
|
raise Exception("Передача прервана: сервер остановлен")
|
||||||
self.log(f"[ERROR] Ошибка при отправке блока {block_num}: {str(e)}")
|
self.log(f"[ERROR] Ошибка при отправке блока {block_num}: {str(e)}")
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
|
||||||
@@ -252,14 +345,26 @@ class TFTPServer:
|
|||||||
block_num = (block_num + 1) % 65536
|
block_num = (block_num + 1) % 65536
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log(f"[ERROR] Ошибка при обработке блока {block_num}: {str(e)}")
|
if not self.running:
|
||||||
|
raise Exception("Передача прервана: сервер остановлен")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if bytes_sent == filesize:
|
||||||
elapsed_time = time.time() - start_time
|
elapsed_time = time.time() - start_time
|
||||||
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} "
|
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} "
|
||||||
f"завершена за {elapsed_time:.2f} сек. Всего отправлено {bytes_sent} байт.")
|
f"завершена за {elapsed_time:.2f} сек. Всего отправлено {bytes_sent} байт.")
|
||||||
|
except Exception as e:
|
||||||
|
if not self.running:
|
||||||
|
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} прервана: сервер остановлен")
|
||||||
|
else:
|
||||||
|
self.log(f"[ERROR] Ошибка при передаче файла '{file_basename}' "
|
||||||
|
f"клиенту {client_addr}: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if not self.running:
|
||||||
|
self.log(f"[INFO] Передача файла прервана: сервер остановлен")
|
||||||
|
else:
|
||||||
self.log(f"[ERROR] Ошибка при передаче файла '{os.path.basename(file_path)}' "
|
self.log(f"[ERROR] Ошибка при передаче файла '{os.path.basename(file_path)}' "
|
||||||
f"клиенту {client_addr}: {str(e)}")
|
f"клиенту {client_addr}: {str(e)}")
|
||||||
try:
|
try:
|
||||||
@@ -267,11 +372,17 @@ class TFTPServer:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
|
# Закрываем сокет передачи
|
||||||
if transfer_socket:
|
if transfer_socket:
|
||||||
try:
|
try:
|
||||||
|
with self.lock:
|
||||||
|
self.transfer_sockets.discard(transfer_socket)
|
||||||
transfer_socket.close()
|
transfer_socket.close()
|
||||||
|
transfer_socket = None
|
||||||
except:
|
except:
|
||||||
pass
|
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]
|
||||||
Reference in New Issue
Block a user