Проблема: скучные консольные сокеты
Когда нужно передать данные по сети (TCP/UDP), большинство пишет консольный скрипт. Но:
-
неудобно тестировать
-
непонятно, отправилось ли сообщение
-
нет истории переписки
А под рукой — обычный Tkinter, который выглядит как программа из 90-х.
Решение: берём CustomTkinter для дизайна и сокеты для передачи данных. Получаем стильное приложение, которое можно показывать заказчику или портфолио.
Ключевые слова статьи: python socket gui customtkinter, клиент сервер приложение на python с интерфейсом, чат на сокетах с темной темой
Что будем строить
Простой TCP-чат (или отправитель команд) с двумя режимами:
-
Сервер — принимает подключения и сообщения
-
Клиент — подключается к серверу и отправляет данные
Окно на CustomTkinter:
-
поле для IP и порта
-
лог сообщений (скроллируемый)
-
поле ввода текста
-
кнопка отправки
-
выбор темы (тёмная/светлая)
Установка библиотек
pip install customtkinter
Встроенный модуль socket дополнительно ставить не нужно.
Структура приложения
Мы не будем делать два отдельных скрипта. Сделаем одно окно, где можно запустить и сервер, и клиента (по кнопкам).
import customtkinter as ctk import socket import threading ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue")
Класс сетевого приложения
class SocketApp: def __init__(self): self.window = ctk.CTk() self.window.title("Socket Commander") self.window.geometry("600x500") # Переменные self.server_socket = None self.client_socket = None self.is_running = False self.create_widgets() def create_widgets(self): # Верхняя панель: IP + порт top_frame = ctk.CTkFrame(self.window) top_frame.pack(pady=10, padx=10, fill="x") ctk.CTkLabel(top_frame, text="IP:").grid(row=0, column=0, padx=5) self.ip_entry = ctk.CTkEntry(top_frame, placeholder="127.0.0.1") self.ip_entry.grid(row=0, column=1, padx=5) ctk.CTkLabel(top_frame, text="Port:").grid(row=0, column=2, padx=5) self.port_entry = ctk.CTkEntry(top_frame, placeholder="12345") self.port_entry.grid(row=0, column=3, padx=5) # Кнопки режимов btn_frame = ctk.CTkFrame(self.window) btn_frame.pack(pady=5, padx=10, fill="x") self.server_btn = ctk.CTkButton(btn_frame, text="Запустить сервер", command=self.start_server) self.server_btn.pack(side="left", padx=5) self.client_btn = ctk.CTkButton(btn_frame, text="Подключиться как клиент", command=self.start_client) self.client_btn.pack(side="left", padx=5) # Лог сообщений self.log_text = ctk.CTkTextbox(self.window, height=250) self.log_text.pack(pady=10, padx=10, fill="both", expand=True) # Нижняя панель: отправка bottom_frame = ctk.CTkFrame(self.window) bottom_frame.pack(pady=10, padx=10, fill="x") self.msg_entry = ctk.CTkEntry(bottom_frame, placeholder="Введите сообщение...") self.msg_entry.pack(side="left", fill="x", expand=True, padx=(0, 5)) self.send_btn = ctk.CTkButton(bottom_frame, text="Отправить", command=self.send_message) self.send_btn.pack(side="right") # Переключатель темы theme_switch = ctk.CTkSwitch( self.window, text="Тёмная тема", command=self.toggle_theme, onvalue="dark", offvalue="light" ) theme_switch.pack(pady=5) theme_switch.select() # по умолчанию тёмная def log(self, message): """Вывод в текстовое поле""" self.log_text.insert("end", f"{message}\n") self.log_text.see("end") def toggle_theme(self): current = ctk.get_appearance_mode() new = "light" if current == "Dark" else "dark" ctk.set_appearance_mode(new)
Реализация сервера (TCP)
def start_server(self): ip = self.ip_entry.get() port = int(self.port_entry.get()) def server_worker(): try: self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.bind((ip, port)) self.server_socket.listen(1) self.log(f"[Сервер] Запущен на {ip}:{port}") self.log("[Сервер] Ожидание подключения...") conn, addr = self.server_socket.accept() self.log(f"[Сервер] Подключён клиент: {addr}") self.client_socket = conn while self.is_running: data = conn.recv(1024) if not data: break self.log(f"[Клиент -> Сервер] {data.decode('utf-8')}") except Exception as e: self.log(f"[Ошибка сервера] {e}") finally: if self.server_socket: self.server_socket.close() self.is_running = True thread = threading.Thread(target=server_worker, daemon=True) thread.start()
Реализация клиента
def start_client(self): ip = self.ip_entry.get() port = int(self.port_entry.get()) def client_worker(): try: self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket.connect((ip, port)) self.log(f"[Клиент] Подключён к {ip}:{port}") while self.is_running: # В реальном чате тут нужен приём сообщений # Для простоты — клиент только отправляет pass except Exception as e: self.log(f"[Ошибка клиента] {e}") self.is_running = True thread = threading.Thread(target=client_worker, daemon=True) thread.start()
Отправка сообщений
def send_message(self): msg = self.msg_entry.get() if not msg: return if self.client_socket: try: self.client_socket.send(msg.encode('utf-8')) self.log(f"[Вы -> Сервер] {msg}") self.msg_entry.delete(0, "end") except Exception as e: self.log(f"[Ошибка отправки] {e}") else: self.log("[Ошибка] Нет активного подключения")
Запуск
def run(self): self.window.mainloop() if __name__ == "__main__": app = SocketApp() app.run()
Вы только что написали профессиональное сетевое приложение с интерфейсом, которое не стыдно показать. CustomTkinter убил двух зайцев:
-
дал современный внешний вид (скругления, hover, тёмная тема)
-
оставил родную логику Tkinter + сокетов
Автор: Евгений Морковин




0 комментариев
Оставьте комментарий
Комментарии