Программа-мечта начинающего питоновода
Для начала введем в нашу задачу насколько условностей — пишем мы чат для локальной сети с разрешенными широковещательными UDP-пакетами. Для простоты решения задачи так же решим, что в качестве GUI будем использовать библиотеку Tkinter(обычно в дистрибьютивах Linux она идет из коробки, так же и в официальной сборке Python под Windows она является стандартной).
Запитоним сеть
Работа с сокетами в питоне не зависит от платформы(по большому счету даже под PyS60 мы получим рабочий сетевой код, по этому примеру).
Для нашего чата мы решили использовать широковещательные UDP-пакеты. Одной из причин их использования была возможность отказаться от использования сервера. Необходимо принять еще одну условность в нашей программе — номер порта, и пусть он будет равен 11719, во первых, это простое число(а ведь это уже многое). А, во вторых, этот порт, по официальной информации IANA, не занят.
Создадим слушающий сокет, который будет радовать консоль сообщениями приходящими на него:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) while 1: message = s.recv(128) print (message)
У сокета мы выставили свойства SO_REUSEADDR(позволяет нескольким приложениям «слушать» сокет) и SO_BROADCAST(указывает на то, что пакеты будут широковещательные) в значние истины. На самом деле на некоторых системах возможна работа скрипта и без этих строчек, но все же лучше явно указать эти свойства.
На прослушку мы подключаемся к адресу ‘0.0.0.0’ — это означает, что прослушиваются вообще все интерфейсы. Можно указывать в поле адреса и просто пустые кавычки, но все же лучше явно указать адрес.
Так же создадим широковещательную «засиралку» сети(для проверки первой программы):
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) while 1: sock.sendto('broadcast!',('255.255.255.255',11719))
В отношении же передающей части, необходима лишь только опция SO_BROADCAST(зато теперь обязательна на всех платформах) и с помощью метода sendto() мы рассылаем по всей сети наши пакеты. Остановив с помощью CTRL+C сетевой флуд перейдем к описанию интерфейса.
Окна, окна, окна
Tkinter — наверное, одна из самых простых библиотек для организации оконного интерфейса на питоне(а еще по заявлению создателя питона она является одной из самых надежных и стабильных). Но для начинающего питоновода главный момент все же, что эта библиотека простая.
И для того, чтобы получить просто окно нужного нам размера и с указанным заголовком от нас требуется лишь несколько строчек кода:
from Tkinter import * tk=Tk() tk.title('MegaChat') tk.geometry('400x300') tk.mainloop()
Все бы ничего, но мы создаем, не галерею окон разных размеров, а чат. А значит нам нужны на окне еще и элементы — лог чата, поле, где мы укажем свой ник и текст сообщения, который мы вводим. Без кнопки отправить можно обойтись, если наше поле для сообщения будет само реагировать на нажатие клавиши Enter.
Решение проблемы проще, чем кажется. Для лога чата можно использовать виджет Text, а для двух остальных Entry. Для того, чтобы разместить элементы на форме мы используем автоматический компоновщик pack, который будет известным только ему способом расставлять элементы, придерживаясь только явноуказанных указаний. Однако, ему можно указать сторону к которой крепить виджеты, расширять ли их и в какую сторону, и еще некоторые параметры компоновки.
from Tkinter import * tk=Tk() tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk) msg = Entry(tk) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') tk.mainloop()
А что же до связи интерфейса с данными программы? Или как заставить в фоне выполняться какую-нибудь процедуру? Ну Entry можно связать с StingVar’ами(указав в конструкторе виджетов свойство textvariable). а для запуска фоновой процедуры есть у Tkinter’а метод after(,). Если в конце исполнения этой процедуры указать переинициализацию ее, то процедура будет выполняться постоянно, пока запущена программа.
Несколько нажатий клавиш и мы получаем:
from Tkinter import * tk=Tk() text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): print ('Hello '+ name.get() + '!') tk.after(1000,loopproc) tk.after(1000,loopproc) tk.mainloop()
В консоли забегало приветствие. Меняя ник мы можем убедится, что связь между полем Entry и нашей переменной работает идеально. Самое время реализовать возможность передачи пользователем своего сообщения в программу по нажатию на Enter. Реализуется это еще проще, с помощью метода bind(, ) у виджетов. Единственное, что нам нужно учесть, что функция должна принимать параметр event. За одно перенесем с консоли действие в поле лога. Получаем:
from Tkinter import * tk=Tk() text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): log.insert (END,'Hello '+ name.get() + '!\n') tk.after(1000,loopproc) def sendproc(event): log.insert (END,name.get()+':'+text.get()+'\n') text.set('') msg.bind('',sendproc) tk.after(1000,loopproc) tk.mainloop()
Почти готово.
И кажется, что осталось объединить программы из первой и второй частей статьи и мы получим готовый чат. Что ж, попробуем. Однако сразу скажу, что специально выводить сообщения, которые мы отправляем не будем — так как при прослушивании широковещательного трафика мы их и так получим.
import socket from Tkinter import * tk=Tk() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): message = s.recv(128) log.insert(END,message) tk.after(1,loopproc) def sendproc(event): sock.sendto (name.get()+':'+text.get(),('255.255.255.255',11719)) text.set('') msg.bind('',sendproc) tk.after(1,loopproc) tk.mainloop()
Однако, чуда не произошло. Программа намертво встала в ожидании входящего пакета. Фоновый поток оказался не таким уж и фоновым и для продолжения выполнения остальной программы он должен иногда завершаться. Чтобы это происходило слушающий сокет необходимо перевести в неблокирующий режим и для того, чтобы мы не получали сообщение об ошибке когда в сокете пусто оградим этот кусок кода try’ем.
import socket from Tkinter import * tk=Tk() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): s.setblocking(False) try: message = s.recv(128) log.insert(END,message+'\n') except: tk.after(1,loopproc) return tk.after(1,loopproc) return def sendproc(event): sock.sendto (name.get()+':'+text.get(),('255.255.255.255',11719)) text.set('') msg.bind('',sendproc) tk.after(1,loopproc) tk.mainloop()
«А после доработать напильником. »
Чисто теоретически полученный код работоспособен, но обладает достаточно весомыми недостатками — поле ввода сообщения сразу не выбирается по умолчанию, возможны проблемы с кирилицей и лог сам не прокручивается вниз. Решение всех этих проблем можно увидеть из следующего листинга(место, где решается вопрос кириллицы выделил, остальное надеюсь и так очевидно):
# -*- coding: utf-8 -*- import socket from Tkinter import * #Решаем вопрос с кирилицей reload(sys) sys.setdefaultencoding('utf-8') #----------------------------- tk=Tk() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): log.see(END) s.setblocking(False) try: message = s.recv(128) log.insert(END,message+'\n') except: tk.after(1,loopproc) return tk.after(1,loopproc) return def sendproc(event): sock.sendto (name.get()+':'+text.get(),('255.255.255.255',11719)) text.set('') msg.bind('',sendproc) msg.focus_set() tk.after(1,loopproc) tk.mainloop()
P.S. И не забывайте — длина питона может достигать 9-10 метров.
- Python
- программированние для чайников
- чат
- Python
- Программирование
Интернет чат на python
Чат на python
Всем привет, решил изучить работу чата. Нашел тему https://habrahabr.ru/post/151623/, там в конце.
Python чат-бот ВК
Доброго времени суток. Столкнулся с проблемой и не могу с ней разобраться — бот не реагирует на.
Чат python+tornado
Есть задание — написать небольшой чат на фреймворке tornado. Вопрос: с чего начать? По коду не.
Python чат. С чем работать?
Всем доброго времени суток! Поставили задачу написать чат сервер на питоне, с языком более-менее.
4459 / 3212 / 1125
Регистрация: 21.03.2016
Сообщений: 7,949
Сообщение от _Oleh_ 
только о локальном чате
глобальный чат ничем не отличается, зная ip сервера можно через него организовать кучу чатов.
идея простая сервер который перенаправляет содержимое чата конкретному пользователю, или чат для двоих сервер-хост на сокетах + GUI, на чем писать GUI это ваш выбор.но основа сетевого чата это известность ip сервера для хоста(хостов)
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
Python. Многоканальность чат-бота
Как организовать многоканальность в чат-боте? Т. е. что бы на каждый чат выделялся отдельный.
ЧАТ-Бот на Python. Проблема с отправкой сообщения
Всем привет! Скажите, как сделать отправку двух сообщений подряд. Есть какой-то execute, но как им.
Интернет чат
Доброго времени суток. Киньте пожалуйста исходник интернет чата. Надо только код клиента и скрипт.
Интернет чат
Всем привет Вопрос не по коду, но по delphi) Возможно ли на delphi запилить интернет чат, что бы.
Интернет чат
Доброго времени суток. Хочу создать свой сайт с чатом между пользователями, подскажите пожалуйста.
Интернет-чат
Доброго времени суток! Подскажите, что конкретно нужно изучить для реализации интернет-чата на.
Как создать интернет чат на python
Блог веб разработки статьи | видеообзоры | исходный код

- Главная /
- PYTHON /
- Общий чат на 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 пишем:
Работа с сокетами на python
Для работы с websocket api создаем объект socket через класс new WebSocket(). В самом классе указывается адрес соединения через протокол ws. Если сервер запущен под https то пишется так wss.
Событие open (onopen) срабатывает когда соединение открыто(установлено), error (onerror) когда происходит ошибка при подключении.
Теперь проверяем! Запускаем файл my_socket.py (наш сервер) и затем после этого открываем файл socket.html в любом браузере поддерживающим стандарты html5.
В консоли браузера должно быть сообщение о результате соединения. Если все хорошо то выведется ‘Соединение установлено’. В консоли интерпретатора Python выведется что то подобное:
То есть это вывод нового подключения и пути.
При открытии socket.html в новой вкладке, окне или даже просто перезагрузке страницы у нас всегда будет создаваться новое подключение. Проверить это вы опять же сможете через консоль Python.
Далее нужно будет записывать подключения всех пользователей для возможности рассылки сообщения по всем подключениям. Расширяем код!
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)
На клиентской части реализуем механизм отправки и приема сообщений:
Работа с сокетами на python
Добавляем теги 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 где вы сможете найти дополнительный материал.
Всем спасибо за внимание!
Оцените статью:
Статьи
- курсор CSS
- регулярные выражения javaScript
- Коммиты GIT
- Javascript json
- border image css
- javascript время
- mail php
- Как установить go
Как создать онлайн чат на Python?
Я нашёл множество гайдов, но во всех них, рассказывают про локальную сеть. Я хочу создать хост на пк либо на сервере в интернете. В чате можно будет общаться с людьми из другого города или района. Кто может подсказть хороший гайд? (Либо сами его составить)
. забыл упомянуть чат будет без интерфейса и пользоваться им нужно через CMD
- Вопрос задан более двух лет назад
- 340 просмотров
3 комментария
Средний 3 комментария
