Блог веб разработки статьи | видеообзоры | исходный код

Блог веб разработки статьи | видеообзоры | исходный код

webfanat вконтакте webfanat youtube

Общий чат на python

Общий чат на python

Приветствую вас дорогие друзья! Сегодня мы напишем общий чат на python используя вебсокеты, а также познакомимся c WebSocket API в javascript. Погнали!

При разработке я буду использовать версию python 3.5, также понадобятся пакеты asyncio и websockets.

asyncio пакет предустановлен в python 3.5, он отвечает за асинхронное программирование.

websockets служит для работы с вебсокетами и его придется установить к примеру через утилиту pip.

Установка в linux имеет такой вид:

sudo pip3 install websockets

Далее по архитектуре будет всего два файла socket.html и my_socket.py.

Следовательно в socket.html будет клиентская часть, а в my_socket.py серверная.

В my_socket.py напишем.

import asyncio
import websockets


async def socket(websocket, path):
    print(websocket, path)

start_server = websockets.serve(socket, '127.0.0.1', 5678)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

В верхней части кода идет подключение пакетов asyncio и websockets. Далее мы создаем асинхронную функцию socket которая принимает два параметра - новое подключение(websocket) и относительный путь(path). Обратите внимание что асинхронные функции пишутся через приставку async. В теле функции просто выводятся значения параметров в консоль.

Затем создается сервер websockets:

start_server = websockets.serve(socket, '127.0.0.1', 5678)

Здесь указывается вызов нашей функции socket, адрес хоста(127.0.0.1) стандартный localhost и порт 5678.

Завершающим этапом идет асинхронный запуск сервера.

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

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

Для того чтобы создать новое подключение необходимо обратится к серверу по специальному протоколу ws - websocket. Делать мы это будем на javascript через специальный API websocket.

В файле socket.html пишем:

<!DOCTYPE html>
<html >
  <head>
    <meta charset="utf-8">
    <title>
         Работа с сокетами на python
    </title>
  </head>
  <body>
  <script>
      var socket = new WebSocket("ws://localhost:5678");
      socket.onopen = function(){
        console.log('Соединение установлено');
       };
      socket.onerror = function(){
        console.log('Ошибка при подключении');
      };
  </script>
</html>

Для работы с websocket api создаем объект socket через класс new WebSocket(). В самом классе указывается адрес соединения через протокол ws. Если сервер запущен под https то пишется так wss.

Событие open (onopen) срабатывает когда соединение открыто(установлено), error (onerror) когда происходит ошибка при подключении.

Теперь проверяем! Запускаем файл my_socket.py (наш сервер) и затем после этого открываем файл socket.html в любом браузере поддерживающим стандарты html5.

В консоли браузера должно быть сообщение о результате соединения. Если все хорошо то выведется 'Соединение установлено'. В консоли интерпретатора Python выведется что то подобное:

<websockets.server.WebSocketServerProtocol object at 0x7f2efcc18780> /

То есть это вывод нового подключения и пути.

print(websocket, path)

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

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

Файл my_socket.py.

import asyncio
import websockets

USERS = set()

async def addUser(websocket):
    USERS.add(websocket)

async def removeUser(websocket):
    USERS.remove(websocket)

async def socket(websocket, path):
    await addUser(websocket)
    print(len(USERS), USERS)

start_server = websockets.serve(socket, '127.0.0.1', 5678)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Здесь в дополнение создается множество 'USERS' и две функции:

async def addUser(websocket):
    USERS.add(websocket)

addUser() - функция добавляет подключение в множество.

async def removeUser(websocket):
    USERS.remove(websocket)

removeUser(websocket) - функция удаляет подключение из множества. Она будет вызываться когда подключение с пользователем будет не возможно(пользователь закрыл страницу, или провис интернет).

Обратите что функции являются асинхронными и будут вызываться через ключевое слово await.

addUser() вызывается при создании нового подключения. После вызова делаем вывод количества элементов в множестве и его содержимое. Процесс удаления подключения рассмотрим далее.

Теперь запустив сервер и перейдя на клиентскую страницу при каждом новом подключении мы сможем просматривать изменение содержимого множества 'USERS'. С каждым новым подключением оно будет наполняться.

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

Код сервера:

import asyncio
import websockets

USERS = set()

async def addUser(websocket):
    USERS.add(websocket)

async def removeUser(websocket):
    USERS.remove(websocket)

async def socket(websocket, path):
    await addUser(websocket)

    try:
        while True:
            message = await websocket.recv()
            
            await asyncio.wait([user.send(message) for user in USERS])
    finally:
        await removeUser(websocket)

start_server = websockets.serve(socket, '127.0.0.1', 5678)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Здесь в функции socket() в которой создается новое подключение мы в дополнении пишем исключение (try/finally). В теле исключения try запускается бесконечный цикл while(true) в котором мы отслеживаем поступление новых сообщений на канал через метод recv() подключения websocket.

message = await websocket.recv()

При поступлении сообщения на канал оно помещается в переменную 'message' и затем отправляется каждому подключению(пользователю).

await asyncio.wait([user.send(message) for user in USERS])

Через данную конструкцию происходит обратная отправка сообщения каждому из записанных в множество 'USERS' подключений. То есть происходит некая рассылка сообщения по всем активным подключениям на клиентскую часть пользователя. Если соединение с одним из подключений не активно происходит ошибка и мы попадаем в блок finally где неактивное соединение удаляется из общего множества 'USERS'.

await removeUser(websocket)

На клиентской части реализуем механизм отправки и приема сообщений:

<!DOCTYPE html>
<html >
  <head>
    <meta charset="utf-8">
    <title>
         Работа с сокетами на python
    </title>
  </head>
  <body>
    <div class="message"></div>
  <textarea placeholder="Для отправки нажмите Enter"></textarea>
  <script>
  var socket = new WebSocket("ws://localhost:5678");
  socket.onopen = function(){

    document.querySelector("textarea").addEventListener('keyup', function(e){
      if(e.keyCode === 13){
        if(this.value.trim() === ""){
          return false;
        }
        socket.send(this.value.trim());
        this.value = "";
      }

    });

  };
  socket.onerror = function(){
    console.log('Ошибка при подключении');
  };

var p = "";
  socket.onmessage = function(e){
    p = document.createElement("p");
    p.innerHTML = e.data;
    document.querySelector(".message").appendChild(p);
  };
  </script>
</html>

Добавляем теги div с классом 'message' куда будут добавляться новые сообщения и поле textarea откуда отправляться.

Вешаем обработчик 'keyup' на текстовую область. Обратите внимание что обработчик создан в событии onopen. Это важно! Так как метод send() объекта WebSocket API может быть вызван только после инициализации соединения с сервером. Отправка сообщения происходить по нажатию клавиши Enter, пустые сообщения отправлять нельзя.

socket.send(this.value.trim());

Метод send объекта websocket api отправляет данные на сервер, после чего идет очистка текстовой области.

Для приема сообщений с сервера отлавливается событие onmessage.

var p = "";
  socket.onmessage = function(e){
    p = document.createElement("p");
    p.innerHTML = e.data;
    document.querySelector(".message").appendChild(p);
  };

В событии возвращается объект с ключом 'data' где лежит наше сообщение. Оно просто помещается в сгенерированный тег 'p' который добавляется в блок div.message.

Проверяем! Запускаем сервер my_socket.py. Открываем клиентскую страницу socket.html по возможности в двух разных браузерах. И пробуем через текстовое поле добавить новых сообщений. В результате мы сможем наблюдать переписку между двумя браузерами. Если вы запустите чат на своем хостинге или сервере, то у вас появится возможность вести переписку на клиентской странице между пользователями в сети.

Вот в общем то и все. Сегодня мы реализовали простой общий чат на python и заодно поработали с websocket api. При этом написали не так уж много кода, за это спасибо питону. Надеюсь вам данная статья понравилась.

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

Всем спасибо за внимание!

Оцените статью:

Статьи

Разработки

Комментарии

Андрей

10:28 18-03-2020

Статья замечательная - а главное все работает! Автору огромная благодарность!!!

Реклама

Запись экрана

Данное расширение позволяет записывать экран и выводит видео в формате webm