Как написать свой мессенджер на python

Table of Contents

В этой статье я бы хотел показать как написать простое приложение мессенджер менее чем в 150 строк.

Серверная часть

Начнём с сервера(наше приложение будет состоять из скриптов сервера и клиента), через который можно получать входящие запросы от клиентов, желающих общаться. Традиционно указываем путь до интерпретатора и импортируем необходимые модули. Конкретно socket и threading. Первый отвечает непосредственно за “общение” процесссов между собой, второй за многопоточность. О этих модулях подробно можно почитать например здесь — socket , threading.

Использование фреймворков, таких как Twisted и SocketServer, было возможным, но мне показалось это излишним для такого простого программного обеспечения, как наше.

#!/usr/bin/env python3
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread

Давайте обозначим константы, отвечающие например за адрес порта и размер буфера.

clients = {}
addresses = {}
HOST = ''
PORT = 33000
BUFSIZ = 1024
ADDR = (HOST, PORT)
SERVER = socket(AF_INET, SOCK_STREAM)
SERVER.bind(ADDR)

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

def accept_incoming_connections():
    """Настраивает обработку для входящих клиентов."""
    while True:
        client, client_address = SERVER.accept()
        print("%s:%s присоединился к переписке" % client_address)
        client.send(bytes("Привет!"+
                          "Введи своё имя и нажми Enter", "utf8"))
        addresses[client] = client_address
        Thread(target=handle_client, args=(client,)).start()

Это просто цикл, который всегда ждет входящих соединений и, как только он его получает, регистрирует соединение (печатает некоторые сведения о соединении) и отправляет подключенному клиенту приветствие. Затем он сохраняет адрес клиента и позже запускает поток обработки для этого клиента. Конечно, мы еще не определили для этого целевую функцию handle_client (), но вот как мы это сделаем:

def handle_client(client):
     name = client.recv(BUFSIZ).decode("utf8")
    welcome = 'Добро пожаловать %s! если желаете покинуть чат то, нажмите {quit} чтобы выйти.' % name
    client.send(bytes(welcome, "utf8"))
    msg = "%s Теперь в переписке" % name
    broadcast(bytes(msg, "utf8"))
    clients[client] = name
     while True:
        msg = client.recv(BUFSIZ)
        if msg != bytes("{quit}", "utf8"):
            broadcast(msg, name+": ")
        else:
            client.send(bytes("{quit}", "utf8"))
            client.close()
            del clients[client]
            broadcast(bytes("%s покинул переписку." % name, "utf8"))
            break

Естественно, после того, как мы отправим новому клиенту приветственное сообщение, он ответит именем, которое он хочет использовать для дальнейшего общения. В функции handle_client () первая задача, которую мы делаем, — мы сохраняем это имя, а затем отправляем клиенту еще одно сообщение о дальнейших инструкциях. После этого идет основной цикл: здесь мы получаем дополнительные сообщения от клиента и, если сообщение не содержит инструкций для выхода, мы просто передаем сообщение другим подключенным клиентам (мы определим метод широковещания через минуту ). Если мы сталкиваемся с сообщением с инструкциями выхода (то есть клиент отправляет {quit}), мы возвращаем то же самое сообщение клиенту, а затем мы закрываем сокет подключения для него. Затем мы делаем очистку, удаляя запись для клиента, и, наконец, сообщаем другим связанным людям, что этот конкретный человек покинул чат.

Теперь пропишем функцию broadcast ():

def broadcast(msg, prefix=""):
     for sock in clients:
        sock.send(bytes(prefix, "utf8")+msg)

Эта функция просто отправляет сообщение всем подключенным клиентам и при необходимости добавляет дополнительный префикс. Мы передаем префикс для broadcast () в нашей функции handle_client () и делаем это так, чтобы люди могли точно знать, кто является отправителем конкретного сообщения.
Это были все необходимые функции для нашего сервера. Наконец, мы добавили код для запуска нашего сервера и прослушивания входящих соединений:

if __name__ == "__main__":
    SERVER.listen(5)
    print("Ожидание соединения")
    ACCEPT_THREAD = Thread(target=accept_incoming_connections)
    ACCEPT_THREAD.start()  # Бесконечный цикл.
    ACCEPT_THREAD.join()
    SERVER.close()

Мы присоединяемся к ACCEPT_THREAD, чтобы основной скрипт ожидал его завершения и не переходил на следующую строку, которая закрывает сервер.
Это завершает наш серверный скрипт.

В итоге получаем вот такой код для серверной части:

#!/usr/bin/env python3
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread

def accept_incoming_connections():
    while True:
        client, client_address = SERVER.accept()
        print("%s:%s соединено" % client_address)
        client.send(bytes("Добро пожаловать , введите своё имя и нажмите Enter", "utf8"))
        addresses[client] = client_address
        Thread(target=handle_client, args=(client,)).start()


def handle_client(client):  
    name = client.recv(BUFSIZ).decode("utf8")
    welcome = 'Добро пожаловать %s! Если желаете выйти,то нажмите {quit} чтобы выйти.' % name
    client.send(bytes(welcome, "utf8"))
    msg = "%s вступил в переписку" % name
    broadcast(bytes(msg, "utf8"))
    clients[client] = name

    while True:
        msg = client.recv(BUFSIZ)
        if msg != bytes("{quit}", "utf8"):
            broadcast(msg, name+": ")
        else:
            client.send(bytes("{quit}", "utf8"))
            client.close()
            del clients[client]
            broadcast(bytes("%s покинул переписку" % name, "utf8"))
            break


def broadcast(msg, prefix=""):

    for sock in clients:
        sock.send(bytes(prefix, "utf8")+msg)

        
clients = {}
addresses = {}

HOST = ''
PORT = 33000
BUFSIZ = 1024
ADDR = (HOST, PORT)

SERVER = socket(AF_INET, SOCK_STREAM)
SERVER.bind(ADDR)

if __name__ == "__main__":
    SERVER.listen(5)
    print("ожидание соединения")
    ACCEPT_THREAD = Thread(target=accept_incoming_connections)
    ACCEPT_THREAD.start()
    ACCEPT_THREAD.join()
    SERVER.close()

Клиентская часть###

Теперь приступим к наиболее интересной части нашего приложения — к клиенту. В качестве gui будем использовать tkinter, т.к в нём довольно легко построить несложное приложение. Традиционно импортируем модуль tkinter, а также модули использовавшиеся ранее при написании серверной части программы.

#!/usr/bin/env python3

from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
import tkinter

Теперь мы напишем функции для обработки отправки и получения сообщений. Начнем с получения:

def receive():
    """обработка получения сообщений"""
    while True:
        try:
            msg = client_socket.recv(BUFSIZ).decode("utf8")# декодируем,чтобы не получить кракозябры
            msg_list.insert(tkinter.END, msg)
        except OSError:
            break

Почему мы снова используем бесконечный цикл? Потому что мы будем получать сообщения совершенно независимо от того, как и когда мы отправляем сообщения. Мы не хотим, чтобы это было приложение для чата с функциональностью уровня рации. Мы хотим приложение в котором можно отправлять или получать сообщения одновременно; мы хотим получать сообщения, когда сами того пожелаем, и отправлять их, когда захотим.

Функциональность внутри цикла довольно проста; recv () является блокирующей частью. Он останавливает выполнение до тех пор, пока не получит сообщение, а когда это произойдет, мы продвигаемся вперед и добавляем сообщение в msglist. Затем мы определяем msg_list, который является функцией Tkinter для отображения списка сообщений на экране.
Далее мы определим функцию send ():

def send(event=None):
    """обработка отправленных сообщений"""
    msg = my_msg.get()
    my_msg.set("")  # очищаем поле.
    client_socket.send(bytes(msg, "utf8"))
    if msg == "{quit}":
        client_socket.close()
        top.quit()

my_msg — это поле ввода в графическом интерфейсе, и поэтому мы извлекаем сообщение для отправки с помощью msg = my_msg.get (). После этого мы очищаем поле ввода и затем отправляем сообщение на сервер, который, как мы видели ранее, передает это сообщение всем клиентам (если это не сообщение о выходе). Если это сообщение о выходе, мы закрываем сокет, а затем приложение с графическим интерфейсом (через top.close ())

Мы определяем еще одну функцию, которая будет вызываться, когда мы решим закрыть окно с GUI. Это своего рода функция очистки до закрытия, которая закрывает соединение с сокетом до закрытия графического интерфейса:

def on_closing(event=None):
    """Эта функция вызывается когда закрывается окно"""
    my_msg.set("{quit}")
    send()

Это устанавливает в поле ввода значение {quit}, а затем вызывает send (). Начнем с определения виджета верхнего уровня и установки его заголовка, как и в любой другой программе на tkinter:

top = tkinter.Tk()
top.title("TkMessenger")

Затем создаём фрейм со списком сообщений, поле для ввода сообщений и скроллбар для перемещения по истории переписки

messages_frame = tkinter.Frame(top)
my_msg = tkinter.StringVar()
my_msg.set("Введите ваше сообщение здесь.")
scrollbar = tkinter.Scrollbar(messages_frame)#скроллбар

“Упаковываем” наши элементы и размечаем их расположение в окне:

msg_list = tkinter.Listbox(messages_frame, height=15, width=50, yscrollcommand=scrollbar.set)
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
msg_list.pack()
messages_frame.pack()

После этого мы создаем поле ввода для пользователя, чтобы ввести свое сообщение, и привязать его к строковой переменной, определенной выше. Мы также привязываем его к функции send (), чтобы всякий раз, когда пользователь нажимает return, сообщение отправлялось на сервер.

Далее мы создаем кнопку отправки, если пользователь желает отправить свои сообщения, нажав на нее. Опять же, мы связываем нажатие этой кнопки с функцией send ().

И да, мы также упаковываем все то, что создали только сейчас. Кроме того, не забудьте использовать функцию очистки on_closing (), которая должна вызываться, когда пользователь хочет закрыть окно GUI:

entry_field = tkinter.Entry(top, textvariable=my_msg)
entry_field.bind("<Return>", send)
entry_field.pack()
send_button = tkinter.Button(top, text="отправить", command=send)
send_button.pack()
top.protocol("WM_DELETE_WINDOW", on_closing)

И вот мы подходим к завершению. Мы еще не написали код для подключения к серверу. Для этого мы должны запросить у пользователя адрес сервера. Я сделал это, просто используя input (), чтобы пользователь встретился с подсказкой командной строки, запрашивающей адрес хоста перед запуском окна с графическим интерфейсом. В будущем можно добавить виджет для этой цели. А пока вот так:

HOST = input('Введите хост: ')
PORT = input('Введите порт: ')
if not PORT:
    PORT = 33000  # Стандартный порт
else:
    PORT = int(PORT)
BUFSIZ = 1024
ADDR = (HOST, PORT)
client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(ADDR)

Как только мы получаем адрес и создаем сокет для подключения к нему, мы запускаем поток для получения сообщений, а затем основной цикл для нашего приложения с графическим интерфейсом:

chat_app_gif

receive_thread = Thread(target=receive)
receive_thread.start()
tkinter.mainloop()

Вот и всё! Теперь наш скрипт клиентской части выглядит вот так:

#!/usr/bin/env python3
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
import tkinter


def receive():
    while True:
        try:
            msg = client_socket.recv(BUFSIZ).decode("utf8")
            msg_list.insert(tkinter.END, msg)
        except OSError:
            break


def send(event=None):
    msg = my_msg.get()
    my_msg.set("")
    client_socket.send(bytes(msg, "utf8"))
    if msg == "{quit}":
        client_socket.close()
        top.quit()


def on_closing(event=None):
    my_msg.set("{quit}")
    send()

top = tkinter.Tk()
top.title("TkMessenger")

messages_frame = tkinter.Frame(top)
my_msg = tkinter.StringVar()
my_msg.set("Введите ваше сообщение здесь")
scrollbar = tkinter.Scrollbar(messages_frame)
msg_list = tkinter.Listbox(messages_frame, height=15, width=50, yscrollcommand=scrollbar.set)
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
msg_list.pack()
messages_frame.pack()

entry_field = tkinter.Entry(top, textvariable=my_msg)
entry_field.bind("<Return>", send)
entry_field.pack()
send_button = tkinter.Button(top, text="отправить", command=send)
send_button.pack()

top.protocol("WM_DELETE_WINDOW", on_closing)


HOST = input('Введите хост: ')
PORT = input('Введите порт: ')
if not PORT:
    PORT = 33000
else:
    PORT = int(PORT)

BUFSIZ = 1024
ADDR = (HOST, PORT)

client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(ADDR)

receive_thread = Thread(target=receive)
receive_thread.start()
tkinter.mainloop()

Да, наше приложение не может тягаться с такими гигантами как: telegram, viber, клиентами xmpp/jabber; однако нам удалось создать простой чат, который каждый может развить в что-то своё: сделать уклон в безопасность(например шифруя передаваемые пакеты) или в хороший ux/ui. Получилась своего рода база для чего-то большего и это круто. Спасибо за прочтение, буду рад любым замечаниям и пожеланиям. Традиционно исходный код программы доступен в моём репозитории на github.

Messenger

Разделы:

Главное

Обучение

Код

Главное

Что такое Messenger?

Messenger — бесплатный мессенджер с открытым исходным кодом для обмена мгновенными сообщениями

Messenger написан на языке програмирования Python

Установка

Установка Python

Установите Python

Работают ли определённые версии Python:

  • 3.10 — Все основные тесты происходят на этой версии
  • 3.9 — Версия поддерживается Messenger и тестируется в Github
  • 3.8 — Версия поддерживается Messenger и тестируется в Github
  • 3.7 — Версия не поддерживается, но пока что есть возможность запустить Messenger на ней
  • 3.6 — Версия не поддерживается, но пока что есть возможность запустить Messenger на ней
  • 2.7 — Версия не поддерживается

Остальные версии не тестировались

Скачивание репозитория

Скачивание из терминала

Если у вас установлен git, то выполните команду:
git clone https://github.com/werryxgames/Messenger.git

В таком случае Messenger скачается в текущую директорию

Скачивание из Github

Также вы можете скачать Messenger при помощи интерфейса Github

Нажмите на кнопку Code, а затем на Download ZIP

Необходимо распаковать скачанный архив в нужную директорию

Теперь вы можете удалить скачанный архив

Завершение установки

Теперь вы можете создать ярлыки со следующими командами на рабочий стол:

python "путь_к_файлу_server.py"
python "путь_к_файлу_main.py"

Обновление Messenger

Рекомендуется обновлять версию только из релизов

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

Если написано, что формат хранения данных не изменён, или ничего не написано, проделайте шаги, как в Скачивание из Github, но распакуйте только файлы server.py и main.py

Обучение

Основы

Messenger состоит из двух частей: сервера и клиента

Сервер хранит все данные и обрабатывает запросы от клиентов

Клиент показывает информацию от сервера в графическом виде

Чтобы запустить сервер, напишите

python "путь_к_файлу_server.py"

Вы должны увидеть надпись Сервер запущен, иначе создайте проблему на вкладке Issues

Перед надписью Сервер запущен может появиться дополнительная информация (True, False (несколько раз) и список с сообщениями). Это значит, что база данных сброшена до первоначального состояния. После этого рекомендуется выключить сервер и сделать то, что написано в Отключение сброса базы данных

Отключение сброса базы данных

Чтобы отключить сброс базы данных, необходимо зайти в файл server.py (пролистать в конец) и убрать все строки между этими (оставьте сами строки, удалите все строки, на месте которых «…»):

if __name__ == "__main__":
    ...
    main()

Примеры

Аккаунты

Вам понадобиться включить сервер

Чтобы зайти в Messenger в первый раз, надо создать аккаунт. Для этого введите логин и пароль в соответствующие поля. Если всё правильно, то вы должны увидеть, как появится основной интерфейс Messenger

Если интерфейс не появился, посмотрите в консоли, там должно была появиться ошибка на подобии следующей:

----------------------------
| ОШИБКА: Заголовок ошибки |
----------------------------
|     Описание ошибки      |
----------------------------

Сообщения

Чтобы отправить сообщения, выберите получателя в списке слева, напишите текст сообщения справа внизу и нажмите ‘Enter’, или кнопку ‘Отправить’

Внизу вашего сообщения написан его статус

Статусы сообщений:

  1. Отправлено — Сообщение отправлено, но ещё не получено сервером
  2. Доставлено — Сообщение доставлено на сервер, но ещё не получено получателем
  3. Получено — Сообщение получено получателем, но он ещё не ответил
  4. Прочитано — Сообщение получено получателем, который написал что-то после вашего сообщения

Код

Код проверяется при помощи flake8 и pytest (тесты в папке tests):

flake8 . --exclude venv,__pycache__,.git,.github
python -m pytest tests

Функции и классы

server.py — Модуль сервера.

Зависимости: bcrypt

def absolute(path_: str) -> str:
    """Возвращает абсолютный путь из относительного."""


def encrypt_password(user_id: int, password: str) -> str:
    """Шифрует пароль.

    Аргументы:
        user_id:    ID пользователя.
        password:   Пароль пользователя.

    Возвращаемое значение:  Зашифрованный пароль.
    """


class Database:
    """Класс базы данных."""

    def __init__(self, filepath: str) -> None:
        """Инициализация базы данных.

        Аргументы:
            filepath:   Путь к базе данных.
        """

    def sql(
        self,
        sql_text: str,
        format_=None,
        noresult: bool = False
    ):
        """Выполняет SQL код.

        Аргументы:
            sql_text:   SQL код.
            format_:    Заменители '?' в SQL коде.
            noresult:   Если включено, то результатом будет булевое значение.

        Возвращаемое значение:
            bool:   Если включено noresult, возвращает True, если результат
                        выполнения SQL кода пустой, иначе False.
            list:   Массив с результатами.
            tuple:  Единственный результат.
        """

    def reset_database(self) -> bool:
        """Сбрасывает базу данных к начальному состоянию.

        Возвращаемое значение: True, если удалось сбросить, иначе False.
        """

    def create_account(self, name: str, password: str):
        """Создаёт аккаунт.

        Аргументы:
            name:       Логин аккаунта.
            password:   Пароль от аккаунта.

        Возвращаемое значение:  Статус выполнения, id в случае успеха.
        """

    def login_account(self, name: str, password: str):
        """Входит в аккаунт.

        Аргументы:
            name:       Логин аккаунта.
            password:   Пароль от аккаунта.

        Возвращаемое значение:  Статус выполнения, id в случае успеха.
        """

    def get_account_data(self, name: str) -> (tuple, list):
        """Получает данные аккаунта.

        Аргументы:
            name:   Логин аккаунта.

        Возвращаемое значение:
            [
                Отправленные и полученные сообщения,
                ID пользователей, чей статус сообщений был изменён.
            ].
        """

    def send_message(self, login: str, receiver: int, message: str) -> bool:
        """Создаёт запись в базе данных о сообщении."""

    def find_user(self, username: str):
        """Ищет пользователя по username.

        Аргументы:
            username:   Имя пользователя.

        Возвращаемое значение:
            False:          Пользователь не найден.
            list[int, str]: ID пользователя и его имя.
        """

    def close(self) -> None:
        """Закрывает базу данных."""


class NetworkedClient:
    """Класс клиента."""

    _instances = []

    def __init__(self, sock: socket, addr) -> None:
        pass

    @staticmethod
    def encode_message(message) -> bytes:
        """Превращает объекты, преобразоваемые в JSON в байты."""

    @staticmethod
    def decode_message(message: bytes):
        """Превращает байты в объекты, преобразоваемые в JSON."""

    def send(self, message: list) -> None:
        """Отправляет сообщение клиенту.

        Аргументы:
            message:    Сообщение.
        """

    def send_account_data(self) -> None:
        """Отправляет данные об аккаунте."""

    def receive(self, jdata: bytes) -> bool:
        """Получает сообщение от клиента.

        Аргументы:
            jdata:  Данные от клиента.

        Возвращаемое значаени: Надо ли обновлять таймер сообщений?
        """

    def close(self) -> None:
        """Закрывает соединение с клиентом."""


def check_idle() -> None:
    """Отключает неактивных клиентов.

    Аргументы:
        clients:    Словарь всех клиентов.
    """


def main() -> None:
    """Основная функция."""

main.py — Мессенджер на Python (клиент).

Нет зависимостей

class Window:
    """Класс окна."""

    def __init__(self, tk_window: tk.Tk) -> None:
        """Инициализация окна.

        Аргументы:
            tk_window:  Окно Tkinter.
        """

    def place(self, id_: str, element, *args, **kwargs) -> None:
        """Размещает объект element.

        Аргументы:
            id_:        Уникальный идентификатор элемента.
            element:    Элемент, который необходимо разместить.
            *args:      Аргументы element.place().
            **kwargs:   Позиционные аргументы element.place().
        """

    def pack(self, id_: str, element, *args, **kwargs) -> None:
        """Размещает объект element.

        Аргументы:
            id_:        Уникальный идентификатор элемента.
            element:    Элемент, который необходимо разместить.
            *args:      Аргументы element.pack().
            **kwargs:   Позиционные аргументы element.pack().
        """

    def clear(self) -> None:
        """Очищает все элементы."""

    def __getattribute__(self, id_: str):
        """Получает элемент по его ID.

        Аргументы:
            id_:    Уникальный идентификатор элемента.

        Возвращаемое значение: Элемент.
        """


class MessengerClient:
    """Основной класс."""

    def __init__(self) -> None:
        """Инициализация класса."""

    @staticmethod
    def show_error(title: str, message: str) -> None:
        """Показывает ошибку.

        Аргументы:
            title:      Заголовок ошибки.
            message:    Описание ошибки.
        """

    @staticmethod
    def encode_message(message) -> bytes:
        """Превращает объекты, преобразоваемые в JSON в байты."""

    @staticmethod
    def decode_message(message: bytes):
        """Превращает байты в объекты, преобразоваемые в JSON."""

    def send(self, message) -> None:
        """Отправляет сообщение message на сервер.

        Аргументы:
            message:    Сообщение.
        """

    @staticmethod
    def create_round_rectangle(
        cnv,
        px1,
        py1,
        px2,
        py2,
        radius,
        ign1=False,
        ign2=False,
        **kwargs
    ) -> int:
        """Создаёт скруглённый прямоугольник.

        Аргументы:
            cnv:    Холст Tkinter.
            px1:    Левая точка прямоугольника.
            py1:    Верхняя точка прямоугольника.
            px2:    Правая точка прямоугольника.
            py2:    Нижняя точка прямоугольника.
            radius: Радиус скругления.
            kwargs: Дополнительные аргументы cnv.create_polygon().

        Возвращаемое значение: ID Скруглённого прямоугольника на холсте.
        """

    def user_selected(self) -> None:
        """Обработчик события выбора пользователя.

        Аргументы:
            wid:    Виджет Listbox Tkinter.
        """

    def resize(self, event) -> None:
        """Обработчик изменения размера окна."""

    def send_message(self, message: str) -> None:
        """Отправляет сообщение на сервер.

        Аргументы:
            message:    Сообщение.
        """

    def add_user(self, username: str) -> None:
        """Добавляет пользователя по имени.

        Аргументы:
            username:   Имя пользователя.
        """

    def receive(self) -> None:
        """Получает сообщения от сервера."""

    def send_idle(self) -> None:
        """Отправляет сообщение серверу о том, что клиент до сих пор открыт."""

    def login_tab(self, clear=True) -> None:
        """Перемещает на начальную вкладку."""

    def on_destroy(self):
        """Обработчик выхода из приложения."""

    def main(self):
        """Основная функция клиента."""

Комната для чата — это интерфейс, который позволяет двум или более людям общаться в чате и отправлять сообщения всем, кто находится в комнате. Сегодня мы поговорим про то, как создать простое чат-приложение на Python и разрешить нескольким клиентам подключаться к нему с помощью сокетов.

Мы используем встроенный в Python сокет-модуль. Он дает возможность осуществлять операции с сокетами. Эти операции широко используются в Интернете: они стоят за любым подключением к любой сети.

Кроме того, для изменения цвета текста, нам понадобится пакет colorama. С помощью этого пакета мы сможем присвоить цвет каждому клиенту в нашем чате. Давайте установим этот модуль:

pip3 install colorama

Поскольку мы используем сокеты, нам нужен серверный и клиентский код. Давайте начнем с серверной части.

Серверная часть

В нашей архитектуре вся работа сервера заключается в выполнении двух основных операций:

  • Прослушивание клиентских подключений. Если подключается новый клиент, мы добавляем его в нашу коллекцию клиентских сокетов
  • Запуск нового потока для каждого подключенного клиента, прием сообщений, отправленных от клиента, и трансляция их всем другим пользователям.

Приведенный ниже код создает TCP-сокет и привязывает его к адресу сервера, а затем прослушивает поступающие соединения:

Обратите внимание, что мы использовали 0.0.0.0 в качестве IP-адреса сервера. Это охватывает все адреса IPv4 на локальном компьютере. Вы можете задаться вопросом, почему мы просто не используем localhost или 127.0.0.1. У сервера может быть два IP адреса, допустим 192.168.1.2 в одной сети и 10.0.0.1 в другой. При указании адреса 0.0.0.0 сервер слушает обе сети.

python logo

Английский для программистов

Наш телеграм канал с тестами по английскому языку для программистов. Английский это часть карьеры программиста. Поэтому полезно заняться им уже сейчас

Мы еще не принимаем соединения, так как не вызывали метод accept() . Приведенный ниже код завершает наш бэкенд:

Как упоминалось ранее, мы добавляем подключенный клиентский сокет в коллекцию наших сокетов. Затем запускаем новый поток и устанавливаем его как поток демона (daemon thread), который выполняет определенную нами функцию listen_for_client() . При наличии клиентского сокета эта функция ожидает отправки сообщения с помощью метода recv() и затем отправляет это сообщение всем другим подключенным клиентам.

Наконец, давайте закроем все сокеты:

Хорошо, с серверным кодом покончено, теперь давайте углубимся в код клиентской части.

Клиентская часть

Клиент выполняет три основные операции:

  • Подключение к серверу
  • Прослушивание сообщений, поступающих с сервера и вывод их на консоль (чтобы от сервера поступило сообщение, клиент должен отправить его на сервер, а сервер — распространить)
  • Ожидание сообщений от пользователей для их дальнейшей отправки на сервер.

Ниже представлен код для первой операции:

Заодно мы устанавливаем цвет для каждого клиента (увидеть можно будет в выводе). Кроме того, давайте установим имя для каждого клиента, чтобы мы могли различать клиентов между собой:

Отлично, двигаемся дальше! Приведенный ниже код отвечает за вторую операцию — прослушивание сообщений с сервера и вывод их на консоль:

Кроме того, мы хотим, чтобы прослушивание сообщений происходило в фоне, т.е. чтобы этот поток был потоком-демоном.

Переходим к последней операции — ожиданию сообщений от пользователей с последующей отправкой их на сервер. Сделаем это следующим образом:

Мы добавляем цвет для каждого клиента, его имя, а также текущую дату и время к отправляемому сообщению. Дальше мы отправляем сообщение с помощью метода send() . Для выхода из программы нужно будет ввести «q» в качестве сообщения.

Демонстрация функционала

Хорошо, теперь, когда мы закончили и серверную, и клиентскую часть, давайте проверим, как всё работает. Во-первых, давайте запустим один экземпляр сервера:

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

Отлично! Первый клиент подключен к серверу и ему предлагается ввести имя пользователя. Теперь, чтобы убедиться, что он подключен, вернитесь к консоли сервера:

Обратите внимание, что сейчас мы используем адрес localhost (127.0.0.1), так как это одна и та же машина. Но если вы хотите подключиться с других машин в той же сети, вы также можете это сделать, просто измените SERVER_HOST в клиентской части кода с 127.0.0.1 на частный IP-адрес сервера.

Давайте запустим еще один клиент, чтобы они могли поболтать:

Удивительно! Как видите, сообщения клиентов отличаются цветом, что позволяет различать пользователей. Что ж, давайте запустим третьего клиента для развлечения:

Заключение

Отлично, теперь каждое сообщение, отправленное одним клиентом, отправляется всем остальным. Обратите внимание, что цвета меняются при каждом повторном выполнении сценария client.py.

Пожалуйста, сверьтесь с полным кодом, чтобы вы могли легко запустить чат-приложение самостоятельно!

Мы также советуем добавить больше возможностей в эту программу. Например, можно сделать оповещение для всех пользователей при подключении нового клиента!

Итак, сегодня мы поговорили о том, как создать чат-приложение на Python. Надеемся, данная статья была вам полезна!

Мессенджер на python

Появилась мысль создать простой чатмессенджер на Python(+- 100 участников), в следствии чего возник вопрос. Мне придется искать сервер и платить за него, или можно это сделать как-то без него? А если можно без него, то как это реализовать?

Попытки найти ответ на мой вопрос в интернете были, но либо я не понял, либо ответа на него не было. Если вы имеете представление как это сделать, прошу помочь, хотя бы книгой, роликом или статьей где будет про это говориться.

(я не в коем случае не прошу готовое решение, я просто решил спросить у людей, которые понимают в программировании лучше меня)

Я пару раз выкладывал концепт p2p месенжера на стековерфлоу. работать можно без сервера вообще, но нужен ещё один месенжер чтоб обменяться контактами для соединения.

Реализуется это через udp и stun. stun пробивает udp порт во внешнюю сеть, а udp позволяет посылать сообщения без установки соединения = без сервера.

Нужно только опубликовать контакт (ip, port) одного из участников и через него получить остальные. Что-то в стиле DHT. Или использовать другой месенжер (например пуши гугла, публичный сип сервер или даже смски) для обмена контактами.

Если вы новичок в сетевом программировании — то начните с месенжера с сервером. Сервер можно найти бесплатно по акции или на начальных тарифах pythonanywhere, oracle и тп. На домашнем интернете есть шанс что у вас белый адрес — можно пробросить порт.

Бот в Telegram на Питоне от А до Я

В социальных сетях и мессенджерах огромную популярность набрали так называемые боты. Они пишутся на разных языках программирования, внедряются в ПО и активно применяются на практике.

В данной статье будет рассказано о том, как написать простой бот на Python. А еще – рассмотрены особенности соответствующего ЯП, преимущества и недостатки упомянутого «виджета». Примеры будут приведены на основе Telegram. Здесь bot – это весьма распространенное явление.

Бот – это…

Это специализированный робот-помощник. Он помогает выполнять любые рутинные занятия. Боты способны реализовывать программные коды, которые будут отвечать за активацию разных команд со стороны пользователей.

Переписка с таким ПО осуществляется непосредственно через чат. Клиент дает боту команды, которые он обрабатывает и выполняет в режиме 24/7. Ключевая задача “робота» – дать ответ на вопрос клиента, опираясь на заданную программу. С помощью оных удается экономить не только время, но и остальные ресурсы.

Умения

Бот Телеграмм умеет многое. Сегодня к спектру его навыков относят следующие моменты:

  • проведение обучения;
  • развлечение публики;
  • предложение и запуск «мини-игр»;
  • работа с поисковыми системами в пределах Сети;
  • скачивание данных – фото, видео, аудио, документов;
  • выступать в качестве напоминалки;
  • участие в групповых чатах для решения заранее определенного спектра задач (пример – согласование оптимального времени встречи);
  • комментирование постов и статей;
  • использование функций управления умным домом и другими подобными устройствами.

Bots – это связующее звено между пользователями и интернетом, а также конкретной компанией. Пользоваться ими не очень трудно. Первое, что нужно сделать – это определиться со спектром задач, поручаемых боту. После – написать грамотный код и внедрить его в мессенджер. Это способен сделать даже начинающий разработчик.

Преимущества и недостатки

Как и любое другое ПО, bot – это «виджет», который имеет ряд сильный и слабых сторон. Их предстоит учитывать каждому, кто хочет подключить соответствующего «помощника» в своем чате/диалоге.

Сильные стороны

К преимуществам ботов Телеграм относят:

  • круглосуточную помощь – функционирование bots прекратят только в случае аварий на серверах, которые случаются крайне редко;
  • удобство и простоту использования – для большинства команд достаточно выбрать из предложенного списка подходящую операцию;
  • мгновенное получение ответа;
  • отсутствие требований к мощности задействованного устройства – это связано с тем, что для работы ботов используются возможности сторонних серверов;
  • высокий уровень безопасности;
  • отсутствие необходимости инициализации дополнительного ПО для запуска рассматриваемого вида «помощника».

Ботов применять удобно и интересно. Они упрощают жизнь и владельцу чата/компании, и непосредственным клиентам/участникам диалога.

Слабые стороны

Минусы у такого ПО тоже есть, но они не слишком весомые:

  • составлять bot должен программист – тот, кто далек от данной сферы деятельности, будет долго разбираться в принципах коддинга;
  • писать бота лучше на одном языке – через Питон;
  • нужно заранее хорошо продумать навигационное меню – тем, кто раньше не имел дела с подобным контентом, может потребоваться немало времени для этого.

Бот должен быть полезным, отвечать потребностям ЦА, а также целям владельца чата. Составить его удастся «с нуля» за 15-30 минут. Особенно если придерживаться определенного алгоритма действий.

Почему Питон

Python – универсальный язык программирования с возможностью использования принципов ООП. Он обладает простым и понятным синтаксисом, освоить который еще проще, зная английский.

Бот, написанный на Python, будет отличаться скоростью, безопасностью и стабильностью. Сам ЯП предусматривает следующие преимущества:

  • функциональность;
  • разделение итоговой кодификации на блоки, что позволяет значительно повысить ее читаемость;
  • поддержка длинной арифметики;
  • кроссплатформенность;
  • огромное количество библиотек, которые смогут выручить в любое время;
  • понятный синтаксис.

Это – идеальный вариант для веб-разработки, приложений для мессенджеров и мелких проектов. Крупные и масштабные игры на чистом Python составить не получится. Для этого предстоит подучить Java или C++.

Составление софта

Bot – это просто и удобно. Телеграм позволяет внедрять и искать такие «виджеты» без особого труда. Хорошего бота удастся составить менее чем за час. Главное – придерживаться определенного алгоритма действий.

Принцип

Перед непосредственной разработкой необходимо разобраться в том, как все будет работать. Bot для «Телеги» функционирует по определенным принципам. Пример будет рассмотрен на компьютере и Telegram-клиенте.

Стоит обратить внимание на следующее:

  1. На компьютере есть интерпретатор Python. Также на устройство необходимо поставить сервер Телеграмма и клиент.
  2. Внутри интерпретатора будет функционировать программа-бот. Она будет отвечать за весь софт: в оной прописана логика и шаблоны, а также возможные операции.
  3. Внутри приложения, написанного через Питон, имеется библиотека, отвечающая за связь с сервером Telegram. В нее нужно вшить секретный ключ. Это поможет указать серверу клиента, что программа связана с конкретным ботом.
  4. Когда клиент с «Телегой» осуществляет запрос гороскопа, bot осуществляет выгрузку на сервер, а сервер – выводит результат на компьютер.
  5. Запрос будет проходить обработку через утилиту на Python, дает ответ на сервер Телеграмма.
  6. Сервер передает необходимый результат непосредственному пользователю.

Bot внедряется без особого труда. Описанный принцип действий актуален не только для гороскопов. Он подойдет для bot любого вида в мессенджере.

Краткий план – пошагово

Чтобы bot Телеграм работал, можно представить процедуру его подключения так:

  1. Провести регистрацию нового бота в мессенджере.
  2. Установить Питон-библиотеку для работы с Telegram.
  3. Добавить библиотеку в программу с гороскопом.
  4. Научить bot реагировать на сообщения в пределах чата.
  5. Прописать там же кодификацию, которая отвечает за кнопки выбора знака зодиака.
  6. Сделать так, чтобы при клике по кнопке отображался гороскоп выбранного варианта.

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

Регистрация

Для того, чтобы зарегистрировать нового бота в Телеграмме, нужно:

  1. Открыть соответствующий мессенджер.
  2. При помощи командной строки найти @BotFather. Он несет ответ за регистрацию нового bot.
  3. Кликнуть по надписи Start, а также указать команду / newbot.
  4. Система задаст поочередно вопросы о названии бота и его ника. Имя должно быть уникальным. С первого раза установить его не всегда получается.

На этом первый этап подготовки завершен. Можно двигаться дальше.

Библиотека и ее инициализация

Следующий этап – это установка подходящей библиотеки Python. Работать с «Телегой» можно через telebot. Второй вариант – это инициализация Webhook. Первый вариант проще, поэтому заострим внимание на нем:

  1. Запустить командную строку от имени администратора на устройстве.
  2. Набрать команду pip install pytelegrambotapi.
  3. Подтвердить обработку операции.
  4. Чтобы приложение понимало бота, в самое начало кода требуется добавить: import telebot;
  5. Bot = telebot.TeleBot(«токен»);.
  6. Вместо слова «токен» вставить настоящий токен, выданный @BotFather.
  7. Открыть программу гороскопа и добавить ее.

Перед тем, как импортировать приложение гороскопа, необходимо его написать. Сделать его требуется на Питоне.

Гороскоп программа

Вот так будет выглядеть код приложения, который отвечает за отображение информации о гороскопах. Создается контент в программной среде Питона:

Сразу после формирования ПО можно приступить к следующему этапу настройки.

Реакции

Bot должен реагировать на слово «Привет». После него будет выдана реакция на соответствующую фразу. Чтобы все работало нормально, необходимо добавить после строчек импорта новый метод. Он отвечает за соответствующую операцию:

  • Добавить после метода строку типа: bot.polling(none_stop=True, interval=0) .
  • После ее добавления у бота будет постоянно проверяться наличие новых сообщений.
  • Прописать код, который предполагает работу с кнопками. Сначала осуществляется вывод всех знаков зодиака. При клике по конкретной – отображается гороскоп оного.
  • Добавить обработчик кнопок. Он будет реагировать на слово zodiac. При написании оного в программе отобразится случайный текст:
  • Можно убрать кодификацию, которая ранее отвечала за вывод знаков зодиака в консоли. После очистки получится приложение:

На этом рассматриваемый «помощник» окончен. Теперь все должно нормально работать. Остается запустить его в Телеграме и получить тот или иной результат.

Команды управления

«Помощник» имеет разные функции и команды. Они пишутся через знак «слеш» («/») прямо в сообщении чата. Вот основные операции:

  • /start – начать работу помощника;
  • /help – вывод помощи на экран;
  • /settings – открыть настройки.

Некоторые подобные «дополнения» способны понимать команды на русском языке. Пример – запрос у робота Антона, который «подрабатывает» в Гидрометцентре. Если при общении с ним прописать «Погода Калининград», будет выведен соответствующий результат.

Почему «молчит»

Иногда бывает так, что «помощник» не отвечает. Такое наблюдается при вводе любой команды/выбора подходящего варианта из меню. Данное явление может происходить по нескольким причинам:

  1. Проблемы и неполадки на сервере. Пример – сбой или полный отказ оного от функционирования.
  2. Ошибки при написании кодификации. Распространенное явление среди новичков.
  3. Ввод команды, которую Телеграм бот на Python не понимает. В этом случае можно воспользоваться Google для поиска подходящих операций и их форматов.

Иногда помогает полное отключение и перезапуск «помощника».

Как быстро освоить Python

Питон и его возможности можно выучить в ВУЗе, техникуме или самостоятельно поисках материалы в Сети. Вот видео по боту в «Телеге». Самообразование – один из лучших, но долгих методов обучения.

А чтобы надписи типа examples, def get, main() и другие не доставляли хлопот, стоит пройти дистанционные курсы. Их преимущества:

  1. Доступность. Обучение можно проводить в любом месте и в любое время, имя под рукой интернет.
  2. Разнообразие направлений. Есть предложения для новичков и опытных программеров.
  3. Срок обучения – до 12 месяцев. За это время пользователь сможет освоить даже несколько направлений.
  4. Хорошо продуманная программа, подпитанная практикой и кураторством опытных разработчиков.

По завершении процесса пользователь получит сертификат, подтверждающий навыки и познания в выбранной области.

Комната для чата — это интерфейс, который позволяет двум или более людям общаться в чате и отправлять сообщения всем, кто находится в комнате. Сегодня мы поговорим про то, как создать простое чат-приложение на Python и разрешить нескольким клиентам подключаться к нему с помощью сокетов.

Мы используем встроенный в Python сокет-модуль. Он дает возможность осуществлять операции с сокетами. Эти операции широко используются в Интернете: они стоят за любым подключением к любой сети.

Кроме того, для изменения цвета текста, нам понадобится пакет colorama. С помощью этого пакета мы сможем присвоить цвет каждому клиенту в нашем чате. Давайте установим этот модуль:

pip3 install colorama

Поскольку мы используем сокеты, нам нужен серверный и клиентский код. Давайте начнем с серверной части.

Серверная часть

В нашей архитектуре вся работа сервера заключается в выполнении двух основных операций:

  • Прослушивание клиентских подключений. Если подключается новый клиент, мы добавляем его в нашу коллекцию клиентских сокетов
  • Запуск нового потока для каждого подключенного клиента, прием сообщений, отправленных от клиента, и трансляция их всем другим пользователям.

Приведенный ниже код создает TCP-сокет и привязывает его к адресу сервера, а затем прослушивает поступающие соединения:

import socket
from threading import Thread

# server's IP address
SERVER_HOST = "0.0.0.0"
SERVER_PORT = 5002 # port we want to use
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize list/set of all connected client's sockets
client_sockets = set()
# create a TCP socket
s = socket.socket()
# make the port as reusable port
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind the socket to the address we specified
s.bind((SERVER_HOST, SERVER_PORT))
# listen for upcoming connections
s.listen(5)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")

Обратите внимание, что мы использовали 0.0.0.0 в качестве IP-адреса сервера. Это охватывает все адреса IPv4 на локальном компьютере. Вы можете задаться вопросом, почему мы просто не используем localhost или 127.0.0.1. У сервера может быть два IP адреса, допустим 192.168.1.2 в одной сети и 10.0.0.1 в другой. При указании адреса 0.0.0.0 сервер слушает обе сети.

[python_ad_block]

Мы еще не принимаем соединения, так как не вызывали метод accept(). Приведенный ниже код завершает наш бэкенд:

def listen_for_client(cs):
    """
    This function keep listening for a message from `cs` socket
    Whenever a message is received, broadcast it to all other connected clients
    """
    while True:
        try:
            # keep listening for a message from `cs` socket
            msg = cs.recv(1024).decode()
        except Exception as e:
            # client no longer connected
            # remove it from the set
            print(f"[!] Error: {e}")
            client_sockets.remove(cs)
        else:
            # if we received a message, replace the <SEP> 
            # token with ": " for nice printing
            msg = msg.replace(separator_token, ": ")
        # iterate over all connected sockets
        for client_socket in client_sockets:
            # and send the message
            client_socket.send(msg.encode())

while True:
    # we keep listening for new connections all the time
    client_socket, client_address = s.accept()
    print(f"[+] {client_address} connected.")
    # add the new connected client to connected sockets
    client_sockets.add(client_socket)
    # start a new thread that listens for each client's messages
    t = Thread(target=listen_for_client, args=(client_socket,))
    # make the thread daemon so it ends whenever the main thread ends
    t.daemon = True
    # start the thread
    t.start()

Как упоминалось ранее, мы добавляем подключенный клиентский сокет в коллекцию наших сокетов. Затем запускаем новый поток и устанавливаем его как поток демона (daemon thread), который выполняет определенную нами функцию listen_for_client(). При наличии клиентского сокета эта функция ожидает отправки сообщения с помощью метода recv() и затем отправляет это сообщение всем другим подключенным клиентам.

Наконец, давайте закроем все сокеты:

# close client sockets
for cs in client_sockets:
    cs.close()
# close server socket
s.close()

Хорошо, с серверным кодом покончено, теперь давайте углубимся в код клиентской части.

Клиентская часть

Клиент выполняет три основные операции:

  • Подключение к серверу
  • Прослушивание сообщений, поступающих с сервера и вывод их на консоль (чтобы от сервера поступило сообщение, клиент должен отправить его на сервер, а сервер — распространить)
  • Ожидание сообщений от пользователей для их дальнейшей отправки на сервер.

Ниже представлен код для первой операции:

import socket
import random
from threading import Thread
from datetime import datetime
from colorama import Fore, init, Back

# init colors
init()

# set the available colors
colors = [Fore.BLUE, Fore.CYAN, Fore.GREEN, Fore.LIGHTBLACK_EX, 
    Fore.LIGHTBLUE_EX, Fore.LIGHTCYAN_EX, Fore.LIGHTGREEN_EX, 
    Fore.LIGHTMAGENTA_EX, Fore.LIGHTRED_EX, Fore.LIGHTWHITE_EX, 
    Fore.LIGHTYELLOW_EX, Fore.MAGENTA, Fore.RED, Fore.WHITE, Fore.YELLOW
]

# choose a random color for the client
client_color = random.choice(colors)

# server's IP address
# if the server is not on this machine, 
# put the private (network) IP address (e.g 192.168.1.2)
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 5002 # server's port
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize TCP socket
s = socket.socket()
print(f"[*] Connecting to {SERVER_HOST}:{SERVER_PORT}...")
# connect to the server
s.connect((SERVER_HOST, SERVER_PORT))
print("[+] Connected.")

Заодно мы устанавливаем цвет для каждого клиента (увидеть можно будет в выводе). Кроме того, давайте установим имя для каждого клиента, чтобы мы могли различать клиентов между собой:

# prompt the client for a name
name = input("Enter your name: ")

Отлично, двигаемся дальше! Приведенный ниже код отвечает за вторую операцию — прослушивание сообщений с сервера и вывод их на консоль:

def listen_for_messages():
    while True:
        message = s.recv(1024).decode()
        print("n" + message)

# make a thread that listens for messages to this client & print them
t = Thread(target=listen_for_messages)
# make the thread daemon so it ends whenever the main thread ends
t.daemon = True
# start the thread
t.start()

Кроме того, мы хотим, чтобы прослушивание сообщений происходило в фоне, т.е. чтобы этот поток был потоком-демоном.

Переходим к последней операции — ожиданию сообщений от пользователей с последующей отправкой их на сервер. Сделаем это следующим образом:

while True:
    # input message we want to send to the server
    to_send =  input()
    # a way to exit the program
    if to_send.lower() == 'q':
        break
    # add the datetime, name & the color of the sender
    date_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 
    to_send = f"{client_color}[{date_now}] {name}{separator_token}{to_send}{Fore.RESET}"
    # finally, send the message
    s.send(to_send.encode())

# close the socket
s.close()

Мы добавляем цвет для каждого клиента, его имя, а также текущую дату и время к отправляемому сообщению. Дальше мы отправляем сообщение с помощью метода send(). Для выхода из программы нужно будет ввести «q» в качестве сообщения.

Демонстрация функционала

Хорошо, теперь, когда мы закончили и серверную, и клиентскую часть, давайте проверим, как всё работает. Во-первых, давайте запустим один экземпляр сервера:

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

Отлично! Первый клиент подключен к серверу и ему предлагается ввести имя пользователя. Теперь, чтобы убедиться, что он подключен, вернитесь к консоли сервера:

Обратите внимание, что сейчас мы используем адрес localhost (127.0.0.1), так как это одна и та же машина. Но если вы хотите подключиться с других машин в той же сети, вы также можете это сделать, просто измените SERVER_HOST в клиентской части кода с 127.0.0.1 на частный IP-адрес сервера.

Давайте запустим еще один клиент, чтобы они могли поболтать:

Удивительно! Как видите, сообщения клиентов отличаются цветом, что позволяет различать пользователей. Что ж, давайте запустим третьего клиента для развлечения:

Заключение

Отлично, теперь каждое сообщение, отправленное одним клиентом, отправляется всем остальным. Обратите внимание, что цвета меняются при каждом повторном выполнении сценария client.py.

Пожалуйста, сверьтесь с полным кодом, чтобы вы могли легко запустить чат-приложение самостоятельно!

Мы также советуем добавить больше возможностей в эту программу. Например, можно сделать оповещение для всех пользователей при подключении нового клиента!

Итак, сегодня мы поговорили о том, как создать чат-приложение на Python. Надеемся, данная статья была вам полезна!

Успехов в написании кода!

Перевод статьи «How to Make a Chat Application in Python».

Привет! Меня зовут Илья Осипов, я методист курса программирования на Python «Девман» и больше 5 лет пишу код на этом языке. Сегодня расскажу, как новичку сделать полезного чат-бота.

Python — классный язык программирования. Но многие новички учат его неправильно: почти все онлайн-курсы, тренажёры, книги и видео предлагают начинать учить Python с нуля с каких-то странных задач, вроде: «Давайте решим вот эту математическую задачку» или «Давайте напишем рекурсивный поиск чисел Фибоначчи». Вы уже тоже зеваете от скуки?

Нет, удовольствие от программирования совсем не в этом, а в том, чтобы решать реальные проблемы. Например, я втянулся в программирование, когда писал чат-бота для своего вуза. Расписание на сайте было очень неудобно смотреть. Я решил потратить время и написать чат-бота, который будет присылать мне расписание занятий в более удобном виде. Оказалось, что другим студентам тоже было неудобно, и спустя год в чат-боте накопилось 4 тысячи пользователей.

Как раз на стыке между освоением синтаксиса и решением реальных проблем, как мне кажется, и сдаются большинство начинающих программистов. Совершенно не понятно как перейти от «сложить два числа» к написанию чего-то стоящего, чем можно похвастаться родителям, друзьям, жене и собаке.

Если вы тоже обнаружили себя в этой «яме», то этот туториал для вас. Дайте себе ещё один шанс.

Шаг 1. Зайдите в Repl.it

Создайте песочницу для языка Python. Ничего устанавливать не нужно. Просто откройте эту ссылку в соседней вкладке.

Придётся зарегистрироваться, но, думаю, вы разберётесь. 🙂

А вы уже нажали кнопку? Нет? Идите жмите!
А вы уже нажали кнопку? Нет? Идите жмите!

Шаг 2. Пройдите шаг 1

Эй, хватит читать! Так дела не делаются! Чтобы научиться программировать на Python — нужно писать код своими руками, а не смотреть, как кто-то делает это за вас 🙂

Вы же не думаете, что можно стать альпинистом, просматривая видео, как другие люди лезут в горы? А уж инструктором вас без реального опыта и подавно никто не наймёт. С программированием то же самое.

Шаг 3. Установите библиотеку для написания ботов

Многие советуют пользоваться более сложными библиотеками для чат-ботов. Например aiogram. Она крутая и популярная, но это библиотека для асинхронного кода. Это совсем другой зверь, которого не стоит касаться новичкам. Воспринимайте асинхронные библиотеки как другой язык программирования, хотя бы на старте.

Вместо этого будем работать с куда более простой библиотекой — python-telegram-bot. Откройте меню для установки библиотек слева-снизу:
Нужна кнопка Packages
Нужна кнопка Packages.

И в появившемся окошке вбейте её название. Нажмите на «+» напротив названия и подождите загрузки. В конце появится вот такая зелёная плашка об успехе:
Название библиотеки можно скопировать прямо из статьи, не обязательно печатать :)
Название библиотеки можно скопировать прямо из статьи, не обязательно печатать. 🙂

Теперь сложноватый и неприятный момент, но такова разработка, за это нам и платят деньги. Нужно будет поменять версию библиотеки на более дружелюбную для новичков. Для этого нужно зайти в очень недружелюбный файл и подправить число на 13.15. В общем-то больше ничего делать и не нужно, думаю, вы справитесь:
Очень страшно, ничего не понятно, но надо только поменять одно число.
Очень страшно, ничего не понятно, но надо только поменять одно число.

Шаг 4. Запустите пример из документации

В документации к библиотеке есть готовые примеры кода. Давайте запустим пример эхобота.

Эхобот — это бот, который просто повторяет за вами. Отвечает вам тем же, что вы написали ему. Вот пример в документации. Я почистил его от всякого ненужного, поэтому можете взять сразу мой, он должен быть не таким страшным. Копируйте этот код в файл main.py и жмите кнопку > Run наверху. Ничего не получится, но так и нужно, об этом ниже:

from telegram import Update, ForceReply
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters

def start(update, context):
    user = update.effective_user
    update.message.reply_text('Привет!')

def echo(update, context):
    update.message.reply_text(update.message.text)

if __name__ == '__main__':
    updater = Updater("TOKEN")
    dispatcher = updater.dispatcher

    dispatcher.add_handler(CommandHandler("start", start))
    dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))

    updater.start_polling()
    updater.idle()

Шаг 5. Переживите шок от первой ошибки

Смотрите, у меня в коде ошибка. Значит, всё, не суждено стать программистом?
Смотрите, у меня в коде ошибка. Значит, всё, не суждено стать программистом?

В этом коде ошибка. Обычно новичкам от такого становится очень страшно, не хочется ни во что вникать, а поскорее закрыть консоль и больше никогда не возвращаться.

Поэтому обычно в курсах, книгах и видео ошибки от вас прячут: делают вид, что их не бывает. А вот в жизни всё совсем иначе, у программистов постоянно ошибка на ошибке, и бОльшую часть времени они тратят на то, чтобы разобраться, чего же этот Python от них хочет.

В хороших библиотеках ошибки исправлять очень легко. Они сами подсказывают вам, что делать. Давайте посмотрим, что тут написано:

Если не обращать внимание на цвет текста, то не так уж и страшно
Если не обращать внимание на цвет текста, то не так уж и страшно.

Ошибка говорит, что что-то не то с токеном от Telegram. Ну и правда, вы же никаких ботов в Telegram ещё не заводили. Для запуска кода вам понадобится токен бота. Получить его можно прямо в Telegram, у официального бота @BotFather. Только не забудьте просить его с уважением!

Не переживайте, что я показываю свой токен. После публикации статьи я его удалил :)
Не переживайте, что я показываю свой токен. После публикации статьи я его удалил. 🙂

Осталось вставить полученный токен в код и запустить его снова:

Не накосячьте с кавычками.&nbsp;

Не накосячьте с кавычками.

Заходите в Telegram, открывайте переписку с вашим ботом, и получайте удовольствие от беседы с цифровым другом:

Он всё ещё за мной повторяет :(
Он всё ещё за мной повторяет. 🙁

Шаг 6. Начните менять код

В скопированном коде много всего страшного. Давайте коротко расскажу, как это всё работает.

Начинается исполнение кода отсюда:

if __name__ == '__main__':
    updater = Updater("5646004689:AAECTkuGjWo1Imwr-_6UrN-nzbo89sd3WSM")
    dispatcher = updater.dispatcher

    dispatcher.add_handler(CommandHandler("start", start))
    dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))

    updater.start_polling()
    updater.idle()

Почти весь этот код можно воспринимать просто как волшебство. Это штуки, которые запускают бота. Я сам толком не уверен, что там происходит внутри. Их просто не нужно менять, почти никогда.

Единственное, что пригодится — это строчки, начинающиеся с dispatcher.add_handler. Это «интеллект» вашего бота. По ним он определяет, как ему реагировать на ваше сообщение. Вот как эти строчки расшифровываются:

Чуть ниже будет о том, как научиться читать такие заклинания. Это всё есть в документации библиотеки python-telegram-bot
Чуть ниже будет о том, как научиться читать такие заклинания. Это всё есть в документации библиотеки python-telegram-bot.

А вот что внутри функции echo:

def echo(update, context):
    update.message.reply_text(update.message.text)

Если просто перевести код на русский, становится понятнее:

Шаг 7. Веселитесь!

В документации python-telegram-bot есть масса примеров использования библиотеки. Там же можно подсмотреть как отправить фотку в Telegram или как создать отложенную задачу.

Остаётся только придумать, что будет делать ваш бот, разбить задачу на части и понемногу кодить его, добавляя и слегка переписывая примеры из документации.

Если вы будете достаточно упорны и потратите на это хотя бы пару месяцев, то станете настоящим крутаном. Останется дополнить свой стек другими технологиями, изучая их аналогичным образом, и всё получится. 😉


 

·

8 min read
· Updated
jul 2022

· Python Standard Library

Disclosure: This post may contain affiliate links, meaning when you click the links and make a purchase, we receive a commission.

A chat room is an interface that allows two or more people to chat and send messages to everyone in the room. In this tutorial, you will learn how to build a simple chat room server and allow multiple clients to connect to it using sockets in Python.

We are going to use socket module which comes built-in with Python and provides us with socket operations that are widely used on the Internet, as they are behind any connection to any network.

To get started, and for changing text color, we gonna need colorama package to assign a printing color to each client in the chatroom:

pip3 install colorama

Since we’re using sockets, then we need a server and client code, let’s start with the server-side.

Server Code

In our architecture, the whole job of the server is to do two essential operations:

  • Listening for upcoming client connections, if a new client is connected, we add it to our collection of client sockets.
  • Start a new thread for each client connected that keeps listening for upcoming messages sent from the client and broadcasts it to all other clients.

The below code creates a TCP socket and binds it to the server address, and then listens for upcoming connections:

import socket
from threading import Thread

# server's IP address
SERVER_HOST = "0.0.0.0"
SERVER_PORT = 5002 # port we want to use
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize list/set of all connected client's sockets
client_sockets = set()
# create a TCP socket
s = socket.socket()
# make the port as reusable port
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind the socket to the address we specified
s.bind((SERVER_HOST, SERVER_PORT))
# listen for upcoming connections
s.listen(5)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")

Notice I’ve used «0.0.0.0» as the server IP address. this means all IPv4 addresses on the local machine. You may wonder, why we don’t just use localhost or «127.0.0.1» ? Well, if the server has two IP addresses, let’s say «192.168.1.2» on a network and «10.0.0.1» on another, then the server listens on both networks.

We’re not yet accepting connections, as we didn’t call accept() method, the below code finishes the server code recipe:

def listen_for_client(cs):
    """
    This function keep listening for a message from `cs` socket
    Whenever a message is received, broadcast it to all other connected clients
    """
    while True:
        try:
            # keep listening for a message from `cs` socket
            msg = cs.recv(1024).decode()
        except Exception as e:
            # client no longer connected
            # remove it from the set
            print(f"[!] Error: {e}")
            client_sockets.remove(cs)
        else:
            # if we received a message, replace the <SEP> 
            # token with ": " for nice printing
            msg = msg.replace(separator_token, ": ")
        # iterate over all connected sockets
        for client_socket in client_sockets:
            # and send the message
            client_socket.send(msg.encode())

while True:
    # we keep listening for new connections all the time
    client_socket, client_address = s.accept()
    print(f"[+] {client_address} connected.")
    # add the new connected client to connected sockets
    client_sockets.add(client_socket)
    # start a new thread that listens for each client's messages
    t = Thread(target=listen_for_client, args=(client_socket,))
    # make the thread daemon so it ends whenever the main thread ends
    t.daemon = True
    # start the thread
    t.start()

As mentioned previously, we add the connected client socket to the collection of our sockets, and then we start a new thread and we set it as a daemon thread (check this tutorial for more information about daemon threads) that executes our defined listen_for_client() function, which given a client socket, it waits for a message to be sent using recv() method, if so, then it sends it to all other connected clients.

Finally, let’s close all sockets:

# close client sockets
for cs in client_sockets:
    cs.close()
# close server socket
s.close()

Alright, that’s it for the server code, let’s dive into the client code.

Client Code

The client does three basic operations:

  • Connects to the server.
  • Keep listening for messages coming from the server (must be a client sent a message to the server and the server broadcasted it) and print it to the console.
  • Waiting for user to input messages to send to the server.

Here’s the code for the first operation:

import socket
import random
from threading import Thread
from datetime import datetime
from colorama import Fore, init, Back

# init colors
init()

# set the available colors
colors = [Fore.BLUE, Fore.CYAN, Fore.GREEN, Fore.LIGHTBLACK_EX, 
    Fore.LIGHTBLUE_EX, Fore.LIGHTCYAN_EX, Fore.LIGHTGREEN_EX, 
    Fore.LIGHTMAGENTA_EX, Fore.LIGHTRED_EX, Fore.LIGHTWHITE_EX, 
    Fore.LIGHTYELLOW_EX, Fore.MAGENTA, Fore.RED, Fore.WHITE, Fore.YELLOW
]

# choose a random color for the client
client_color = random.choice(colors)

# server's IP address
# if the server is not on this machine, 
# put the private (network) IP address (e.g 192.168.1.2)
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 5002 # server's port
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize TCP socket
s = socket.socket()
print(f"[*] Connecting to {SERVER_HOST}:{SERVER_PORT}...")
# connect to the server
s.connect((SERVER_HOST, SERVER_PORT))
print("[+] Connected.")

As a side operation, we also set a color for each client, you’ll see it in the output. Also, let’s set a name for each client, so we can distinguish between clients:

# prompt the client for a name
name = input("Enter your name: ")

The below code is responsible for the second operation; keep listening for messages from the server and print them to the console:

def listen_for_messages():
    while True:
        message = s.recv(1024).decode()
        print("n" + message)

# make a thread that listens for messages to this client & print them
t = Thread(target=listen_for_messages)
# make the thread daemon so it ends whenever the main thread ends
t.daemon = True
# start the thread
t.start()

We also want it to be in a separate thread as a daemon thread, so we can do other things while listening for messages.

Now let’s do the final task; waiting for user input for messages, and then send them to the server:

while True:
    # input message we want to send to the server
    to_send =  input()
    # a way to exit the program
    if to_send.lower() == 'q':
        break
    # add the datetime, name & the color of the sender
    date_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 
    to_send = f"{client_color}[{date_now}] {name}{separator_token}{to_send}{Fore.RESET}"
    # finally, send the message
    s.send(to_send.encode())

# close the socket
s.close()

We add the client color, name, and the current date-time to the message to be sent, we send the message using send() method and we make a way to exit out of the problem, by just inputting 'q' character in the place of the message.

Demonstration

Alright, now that we finished both code recipes, let’s make a demonstration. First, let’s run one and only one server instance:

Server is listening for upcoming client connections

Awesome, the server is listening for upcoming client connections, let’s try to run one client instance:

First client connectedNow the client is connected to the server and prompted for a username, to make sure it’s connected, get back to the server console and you’ll see indeed it’s connected:

The Client is connected to the serverNote we’re on localhost (127.0.0.1) address for now, as it’s the same machine, but if you want to connect from other machines in the same network, you can do that as well, just make sure to change SERVER_HOST in client code from 127.0.0.1 to the server’s private IP address.

Let’s run another client so we can chat:

Second client is connected & chatting

Awesome, as you can see, each client have a color so we can distinguish between users, let’s run a third client for fun:

Three clients chatting

Conclusion

Great, now every message sent from a particular client is sent to all other clients. Note the colors are changed whenever you re-execute the client.py script.

Please check the full code so you can easily run them on your own!

I encourage you to add more features to this program. For example, you can make a notice to all users when a new client is connected!

For more Python sockets tutorials, check these:

  • How to Transfer Files in the Network using Sockets in Python.
  • How to Create a Reverse Shell in Python.

Finally, if you’re a beginner and want to learn Python, I suggest you take the Python For Everybody Coursera course, in which you’ll learn a lot about Python. You can also check our resources and courses page to see the Python resources I recommend!

Learn also: Logging in Python.

Happy Coding ♥

View Full Code

Read Also

How to Create a Reverse Shell in Python

How to Transfer Files in the Network using Sockets in Python

Logging in Python

Comment panel

На очередной практике по Java, не предвещающей ничего необычного, преподаватель ворвался в аудиторию и с порога заявил: «Сегодня мы с вами познакомимся с сокетами и напишем прототип собственного чата».

«А вечер-то перестаёт быть томным» — подумал я и не ошибся. Чёрт возьми, это какая-то магия, вертелось в моей голове по пути домой. Тут надо отметить, что я не только бедный студент, но ещё и преподаватель в кружке программирования, поэтому после столкновения с такой интересной темой во мне затаилось жгучее желание поделиться ею со своими ребятами.

Так и родилась эта статья, которая является крайне упрощенным пособием по использованию веб-сокетов для начинающих изучать программирование.

Для начала определимся, что это за зверь такой — Сокет?

Представим себе работу ресторана быстрого питания, пусть будет Burger Queen, так вот работник этого заведения и будет сокетом, то есть программой, которая отвечает за обмен данными(бургерами) между клиентом и заведением. Как сокет узнает кому отдать заветное хрючево(данные)? У него есть чек с номером заказа! Вот и у сокета есть порт к которому он привязан, то есть и в Burger Queen и в сетевых технологиях есть приложение, тот самый сокет, который отвечает за работу с определенным портом, так же как и работник, который отвечает за обработку конкретного номера заказа, ведь и к конкретному компьютеру и к конкретной забегаловке одновременно конектятся разные клиенты, и всем им нужны разные данные.

Как и от работников сети быстрого питания от сокетов большого набора скиллов не требуется, поэтому мы создадим отдельно серверный сокет, он будет принимать данные от одного клиента и передавать другому и клиентский сокет, он будет отправлять данные на сервер и получать ответ.

Пишем сервер

Фуууух, самая потная часть статьи позади, расчехляем питонов! Первым делом напишем программу серверного сокета, работника Burger Queen, который принимает заказы.

import socket # Подключаем необходимую библиотеку, она встроена


new_socket = socket.socket() #Создаём объект сокета
new_socket.bind(('127.0.0.1', 5000)) # Привязываем наш объект к ip и порту
new_socket.listen(2) # Указываем нашему сокету, что он будет слушать 2 других

print("Server is up now!")

Каюсь, в объяснении сокетов я ни слова не сказал про IP, но тут тоже всё просто, наш работник(сокет) работает в конкретном ресторане по конкретному адресу, то есть, подытоживая можно сказать, в конкретном ресторане по определенному IP адресу работает много работников, сокетов, каждый из которых обслуживает свой порт, номер заказа.

Почему я выбрал 5000-ный порт, потому что методом проб и ошибок только с ним запустилась моя программа, очевидно другие — заняты, а IP 127.0.0.1 — стандартный локальный адрес любого компьютера(совсем любого).

Ползём дальше, получаем коннекты.

conn1, add1 = new_socket.accept() 
# сохраняем объект сокета нашего клиента и его адрес
print("First client is connected!")

conn2, add2 = new_socket.accept()
#аналогично со вторым клиентом
print("Second client is connected!")

Далее создадим две функции, которые принимают данные от одного клиента и отправляют их другому.

def acceptor1():
  # Запустим бесконечный цикл, мы хотим общаться постоянно!
    while True:
#Получим 1024 байта от первого клиента
        a = conn1.recv(1024)
 #Перешлём их второму
        conn2.send(a)

def acceptor2():
# А здесь мы получим 1024 байта от второго клиента и перешлём первому.
    while True:
        b = conn2.recv(1024)
        conn1.send(b)

Вуаля, осталось запустить функции, но вот проблема, они будут выполнятся последовательно(вспомним физику 8-го класса), то есть друг за другом, а нам хотелось бы, что они выполянлись параллельно, для этого в программировании существует концепция так называемых потоков, тут я снова встал на скользкую дорожку, потому что про потоки можно говорить часами и всё равно будут оставаться вопросы(лично у меня так бывает), поэтому здесь, памятуя, что статья для новичков, скажу лишь одно, поток — волшебный класс, который позволяет выполянять указанные функции параллельно.

from threading import Thread # Подключили класс потока

#Создаём потоки, в качестве именнованного аргумента передаем нашу ф-ю
tread1 = Thread(target=acceptor1) 
tread2 = Thread(target=acceptor2)

#Запускаем потоки
tread1.start()
tread2.start()

Здесь вся магия заключается в том, что мы указываем каждому потоку, какую функцию он будет выполнять, а затем стартуем их и отныне наши функции выполянются параллельно.

Вот собственно и все, можно запустить наш сервер и попробовать подключиться к нему из командной строки, с помощью следующей команды:

curl 127.0.0.1:5000

Здесь мы просто говорим системной утилите curl законектится к указанному адресу и порту, при успешном подключении сервер сообшит нам о нём в консоли.

Итак, приведём полный код сервера.

import socket 
from threading import Thread

new_socket = socket.socket()
new_socket.bind(('127.0.0.1', 5000))

new_socket.listen(2)

print("Server is up now!")

conn1, add1 = new_socket.accept()
print("First client is connected!")

conn2, add2 = new_socket.accept()
print("Second client is connected!")

def acceptor1():
    while True:
        a = conn1.recv(1024)
        conn2.send(a)

def acceptor2():
    while True:
        b = conn2.recv(1024)
        conn1.send(b)

tread1 = Thread(target=acceptor1)
tread2 = Thread(target=acceptor2)

tread1.start()
tread2.start()

Пишем клиента

С клиентом, а точнее с клиентами всё куда проще, идеалогически мы всего лишь создаём новый объект сокета и заставляем его отправлять и принимать данные, делает он это конечно же параллельно, смотрим сразу весь код и вникаем.

#Подключаем зависимости
import socket
from threading import Thread
#Создаём новый сокет
client_socket = socket.socket()
#Заставляем его подключиться к серверному сокету
client_socket.connect(("127.0.0.1", 5000))
#Создаём ф-и отправки и получения сообщений
def sender():
    while True:
      #Читаем строку с клавиатуры
        a = input()
        #Отправляем её, предварительно закодировав
        client_socket.send(a.encode("utf-8"))
def reciver():
    while True:
      #Получаем строку от сервера
        b = client_socket.recv(1024)
       #Печатаем, предварительно раскодировав
        print(b.decode("utf-8"))
#Создаём по отдельному потоку для каждой функции
tread1 = Thread(target=sender)
tread2 = Thread(target=reciver)
#Потоки запушены, клиент готов получать и отправлять сообщения
tread1.start()
tread2.start()

Вот и всё! Создаем двух клиентов и переписываемся без мам пап, ватсапов и телеграмов, как говорится, мы и сами с усами.

P. S.

Буду рад любым комментариям, новичкам подскажу, опытных разработчиков выслушаю и приму к сведению.

Понравилась статья? Поделить с друзьями:
  • Как написать свой логин
  • Как написать свой личный дневник
  • Как написать свой линукс
  • Как написать свой лаунчер майнкрафт
  • Как написать свой лаунчер для minecraft