Чат на языке Python: от простого к сложному

Раздел: Сетевое программирование -> Чаты

Основы создания чата на Python

Наиболее эффективное решение для многопользовательского текстового чата на Python заключается в использовании модуля socket в сочетании с потоками threading. Это позволяет обрабатывать одновременные подключения без блокировки основного цикла. Ниже приведён базовый пример сервера и клиента.

import socket
import threading

# Сервер
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 5555))
server.listen(5)

clients = []
nicknames = []

def broadcast(message):
    for client in clients:
        client.send(message)

def handle(client):
    while True:
        try:
            message = client.recv(1024)
            broadcast(message)
        except:
            index = clients.index(client)
            nickname = nicknames[index]
            broadcast(f'{nickname} покинул чат'.encode('utf-8'))
            clients.remove(client)
            nicknames.remove(nickname)
            client.close()
            break

def receive():
    while True:
        client, address = server.accept()
        client.send('NICK'.encode('utf-8'))
        nickname = client.recv(1024).decode('utf-8')
        nicknames.append(nickname)
        clients.append(client)
        broadcast(f'{nickname} присоединился'.encode('utf-8'))
        thread = threading.Thread(target=handle, args=(client,))
        thread.start()

print('Сервер запущен...')
receive()

чат на python код (чат на python (код))

Сервер запущен...

Клиент подключается, отправляет никнейм и затем может отправлять сообщения.

import socket
import threading

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 5555))

def receive():
    while True:
        try:
            message = client.recv(1024).decode('utf-8')
            if message == 'NICK':
                client.send(input('Введите ваш ник: ').encode('utf-8'))
            else:
                print(message)
        except:
            print('Ошибка соединения')
            break

def write():
    while True:
        message = input('')
        client.send(message.encode('utf-8'))

thread_recv = threading.Thread(target=receive)
thread_recv.start()
thread_write = threading.Thread(target=write)
thread_write.start()
Введите ваш ник: Пользователь1
Пользователь2 присоединился
Привет от Пользователь1

Типичные проблемы при использовании потоков: блокировка ввода-вывода, утечка потоков при разрыве соединения, состояние гонки при изменении списков clients и nicknames. Решение: использовать блокировки threading.Lock при изменении общих данных и обработку исключений для корректного закрытия потоков.

Как сделать чат на Python без потоков, используя select?

Модуль select позволяет отслеживать несколько сокетов на готовность к чтению/записи в одном потоке. Пример сервера на select:

import socket
import select

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 5555))
server.listen(5)
server.setblocking(False)

inputs = [server]
outputs = []
message_queues = {}

while inputs:
    readable, writable, exceptional = select.select(inputs, outputs, inputs)
    for s in readable:
        if s is server:
            client, addr = s.accept()
            client.setblocking(False)
            inputs.append(client)
            message_queues[client] = []
        else:
            data = s.recv(1024)
            if data:
                message_queues[s].append(data)
                if s not in outputs:
                    outputs.append(s)
            else:
                inputs.remove(s)
                s.close()
                del message_queues[s]
    for s in writable:
        if message_queues[s]:
            data = message_queues[s].pop(0)
            for other in inputs:
                if other not in (server, s):
                    other.send(data)
        else:
            outputs.remove(s)

Проблема: сложность отладки, ограниченная масштабируемость (до нескольких сотен клиентов). Решение: для высоконагруженных систем переходить на asyncio или epoll.

Как реализовать асинхронный чат при помощи asyncio?

Библиотека asyncio предоставляет корутины и событийный цикл. Пример эхо-сервера и клиента:

import asyncio

clients = {}

async def handle_client(reader, writer):
    addr = writer.get_extra_info('peername')
    clients[addr] = writer
    try:
        while True:
            data = await reader.read(1024)
            if not data:
                break
            for addr_other, w in clients.items():
                if w != writer:
                    w.write(data)
                    await w.drain()
    finally:
        del clients[addr]
        writer.close()

async def main():
    server = await asyncio.start_server(handle_client, 'localhost', 5555)
    async with server:
        await server.serve_forever()

asyncio.run(main())
(клиент подключается и отправляет сообщения)

Проблемы: сложность отладки асинхронного кода, необходимость следить за блокирующими вызовами. Решение: использовать asyncio.gather для параллельных задач, не забывать await.

Как создать веб-чат на Python с помощью WebSocket?

Библиотека Flask-SocketIO упрощает создание WebSocket-сервера. Пример минимального сервера:

from flask import Flask, render_template
from flask_socketio import SocketIO, emit

app = Flask(__name__)
socketio = SocketIO(app)

@socketio.on('connect')
def handle_connect():
    emit('response', {'data': 'Подключение установлено'})

@socketio.on('message')
def handle_message(data):
    emit('response', {'data': data}, broadcast=True)

if __name__ == '__main__':
    socketio.run(app)
(на клиенте JavaScript WebSocket)

Проблемы: зависимость от Flask, сложность настройки production-сервера. Решение: использовать Eventlet для асинхронной обработки.

Как сделать UDP-чат для широковещательных сообщений?

UDP не требует установки соединения, подходит для локальных групповых чатов. Пример сервера (приемника):

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('localhost', 5555))

while True:
    data, addr = sock.recvfrom(1024)
    print(f'{addr}: {data.decode()}')
    sock.sendto(data, addr)  # эхо
('127.0.0.1', 12345): Привет

Проблемы: потеря пакетов, отсутствие гарантии доставки, широковещательный шторм. Решение: использовать TCP для критичных данных.

Расширенные примеры реализации чата

Чат с графическим интерфейсом на Tkinter

Данный пример объединяет клиент сокетов и интерфейс Tkinter. Каждое сообщение отображается в текстовом виджете. Для асинхронного приёма данных используется метод after.

Пример
import socket
import threading
import tkinter as tk

class ChatClient:
    def __init__(self, master):
        self.master = master
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(('localhost', 5555))
        self.text_area = tk.Text(master)
        self.text_area.pack()
        self.entry = tk.Entry(master)
        self.entry.pack()
        self.entry.bind('', self.send_message)
        threading.Thread(target=self.receive, daemon=True).start()

    def receive(self):
        while True:
            data = self.socket.recv(1024).decode()
            self.text_area.insert(tk.END, data + '\n')

    def send_message(self, event):
        msg = self.entry.get()
        self.socket.send(msg.encode())
        self.entry.delete(0, tk.END)

root = tk.Tk()
app = ChatClient(root)
root.mainloop()
(окно Tkinter с полем ввода и лентой сообщений)

Чат с шифрованием сообщений (простой шифр XOR)

Для защиты данных при передаче можно применить шифрование. Пример функции шифрования/дешифрования с использованием XOR с фиксированным ключом. Ключ должен быть одинаковым на сервере и клиенте.

Пример
KEY = 0xAB

def encrypt_decrypt(data):
    return bytes([b ^ KEY for b in data])

# На сервере при получении и отправке:
# decrypted = encrypt_decrypt(data)
# original = encrypt_decrypt(decrypted)  # обратимо
(трафик становится нечитаемым без ключа)

Проблемы: слабая стойкость, ключ в коде. Решение: использовать ssl.wrap_socket для полноценного TLS.

Чат с командами администратора (kick, ban, whisper)

Реализуется парсинг сообщений на сервере. Например, сообщение /kick nickname удаляет клиента. Пример фрагмента:

Пример
def handle(client):
    while True:
        msg = client.recv(1024).decode()
        if msg.startswith('/kick '):
            target = msg.split()[1]
            for c, n in zip(clients, nicknames):
                if n == target:
                    c.send('Вы были исключены'.encode())
                    clients.remove(c)
                    nicknames.remove(n)
                    c.close()
        else:
            broadcast(msg.encode())
(клиент с ником target отключается)

Сохранение истории чата в SQLite

Каждое сообщение записывается в базу данных SQLite. Пример записи:

Пример
import sqlite3
conn = sqlite3.connect('chat.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS messages
             (time TEXT, nickname TEXT, message TEXT)''')

def save_message(nick, msg):
    import datetime
    c.execute('INSERT INTO messages VALUES (?, ?, ?)',
              (datetime.datetime.now(), nick, msg))
    conn.commit()
(файл chat.db содержит историю)

Использование протокола TCP с keep-alive для обнаружения отключений

Настройка опции SO_KEEPALIVE и установка таймаутов позволяет быстрее узнавать о разрыве соединения. Пример для сервера:

Пример
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
server.settimeout(30)  # таймаут в секундах
(при обрыве соединения срабатывает исключение timeout)

Чат на Python (код) - comments

En
чат на python код (python)