Пишем свой мессенджер на python

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

Client-server messenger app

License

dmitry-vs/python-messenger

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Sign In Required

Please sign in to use Codespaces.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching GitHub Desktop

If nothing happens, download GitHub Desktop and try again.

Launching Xcode

If nothing happens, download Xcode and try again.

Launching Visual Studio Code

Your codespace will open once ready.

There was a problem preparing your codespace, please try again.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

Проект представяет собой учебный чат-мессенджер на Python. Состоит из клиентской и серверной частей. Сетевое взаимодействие осуществляется с использованием сокетов. Сервер использует библиотеку select для работы с несколькими клиентами сразу. Для обмена сообщениями используется протокол JIM. Клиент и сервер имеют как консольную, так и графическую версии. Последняя предпочтительнее по удобству и полноте поддерживаемого функционала. Графический интерфейс пользователя реализован с использованием PyQT5. В качестве базы данных используется sqlite, при этом ORM не применяется. Реализован механизм авторизации пользователей с использованием модулей hmac и hashlib.

Рассмотрим графическую версию сервера.

В данном блоке можно задать хост и порт для сервера, а также запустить или остановить его. Если сервер запущен, в поле Status появится значение Started .

Таблица содержит историю активности клиентов: логин, дата и время последнего посещения, IP-адрес, с которого в последний раз заходил пользователь. Данные отсортированы по времени (более новые вверху).

Чтобы клиент мог пользоваться чатом, его нужно сначала зарегистрировать на сервере. Для этого в данном блоке нужно ввести его логин и пароль, затем нажать кнопку Add client .

В данное поле выводится вся информация о работе сервера: служебные данные, сообщения об ошибках, а также все сообщения, которые принимает и отправляет сервер, плюс информация о подключениях и отключениях клиентов.

Рассмотрим графическую версию клиента.

Для начала работы нужно ввести данные для соединения с сервером:

  • User name — логин
  • Password — пароль
  • Server IP — адрес или имя хоста сервера
  • Server port — номер порта, на котором работает сервер

Кнопка Connect позволяет выполнить подключение к серверу, кнопка Disconnect — отключение.

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

В блоке Contacts отображается список контактов пользователя. Чтобы добавить новый контакт, нужно ввести логин в поле Add contact и нажать кнопку Add . После этого можно писать данному пользователю сообщения. Если клиенту приходит сообщение от контакта, которого ещё нет в списке, то он автоматически там появляется. Для удаления контакта нужно выбрать его из списка и нажать кнопку Delete selected contact .

Для отправки сообщения контакту нужно выбрать его в списке щелчком левой кнопки мыши. После этого в поле Messages появится история переписки с данным пользователем (если она была ранее). Чтобы послать ему новое сообщение, требуется ввести текст в поле Input и нажать кнопку Send . Для успешной доставки сообщения необходимо, чтобы получатель также был подключен к серверу. В противном случае сервер ответит ошибкой с текстом «client not online». Входящие сообщения будут отображаться в Messages по их получения клиентом.

В данном блоке выводится информация о работе клиента, это служебные данные и сообщения об ошибках.

Источник

Пишем свой мессенджер P2P

На фоне обсуждения будущего интернет мессенджеров и прочтения статьи «Почему ваш любимый мессенджер должен умереть», решил поделиться своим опытом создания P2P приложения для общения независимо от сторонних серверов. Точнее — это просто заготовка, передающая одно сообщение от клиента серверу, дальнейшее расширение функционала зависит только от Вашей фантазии.

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

Нам понадобится:
— один сервер с белым статическим IP адресом;
— 2 компьютера за NAT с типом соединения Full Cone NAT (либо 1 компьютер с 2-мя виртуальными машинами);
— STUN-сервер.

Full Cone NAT — это такой тип преобразования сетевых адресов, при котором существует однозначная трансляция между парами «внутренний адрес: внутренний порт» и «публичный адрес: публичный порт».

Вот, что мы можем прочесть о STUN-сервере на Wiki:

«Существуют протоколы, использующие пакеты UDP для передачи голоса, изображения или текста по IP-сетям. К сожалению, если обе общающиеся стороны находятся за NAT’ом, соединение не может быть установлено обычным способом. Именно здесь STUN и оказывается полезным. Он позволяет клиенту, находящемуся за сервером трансляции адресов (или за несколькими такими серверами), определить свой внешний IP-адрес, способ трансляции адреса и порта во внешней сети, связанный с определённым внутренним номером порта.»

При решении задачи использовались следующие питоновские модули: socket, twisted, stun, sqlite3, os, sys.

Для обмена данными, как между Сервером и Клиентом, так и между Сервером, Клиентом и Сигнальным Сервером — используется UDP протокол.

В общих чертах механизм функционирования выглядит так:

Сервер STUN сервер
Клиент STUN сервер

Сервер Сигнальный Сервер
Клиент Сигнальный Сервер

1. Клиент, находясь за NAT с типом соединения Full Cone NAT, отправляет сообщение на STUN сервер, получает ответ в виде своего внешнего IP и открытого PORT;

2. Сервер, находясь за NAT с типом соединения Full Cone NAT, отправляет сообщение на STUN сервер, получает ответ в виде своего внешнего IP и открытого PORT;

При этом, Клиенту и Серверу известен внешний (белый) IP и PORT Сигнального Сервера;

3. Сервер отправляет на Сигнальный Сервер данные о своих внешних IP и PORT, Сигнальный Сервер их сохраняет;

4. Клиент отправляет на Сигнальный Сервер данные о своих внешних IP и PORT и id_destination искомого Сервера, для которого ожидает его внешний IP, PORT.

Сигнальный Сервер их сохраняет, осуществляет поиск по базе, используя id_destination и, в ответ, отдает найденную информацию в виде строки: ‘id_host, name_host, ip_host, port_host’;

5. Клиент принимает найденную информацию, разбивает по разделителю и, используя (ip_host, port_host), отправляет сообщение Серверу.

Приложения написаны на Python версии 2.7, протестированы под Debian 7.7.

Создадим файл server.py с содержимым:

# -*- coding: utf-8 -*- #SERVER from socket import * import sys import stun def sigserver_exch(): # СЕРВЕР СИГНАЛЬНЫЙ СЕРВЕР # СЕРВЕР '' udp_socket.close() sigserver_exch() 

Заполним соответствующие поля разделов: «Внешний IP и PORT СИГНАЛЬНОГО СЕРВЕРА» и «IP и PORT этого КЛИЕНТА».

Создадим файл client.py с содержимым:

# -*- coding: utf-8 -*- # CLIENT from socket import * import sys import stun def sigserver_exch(): # КЛИЕНТ СИГНАЛЬНЫЙ СЕРВЕР # КЛИЕНТ -> СЕРВЕР # КЛИЕНТ - отправляет запрос на СИГНАЛЬНЫЙ СЕРВЕР с белым IP # для получения текущих значений IP и PORT СЕРВЕРА за NAT для подключения к нему. #Внешний IP и PORT СИГНАЛЬНОГО СЕРВЕРА: v_sig_host = 'XX.XX.XX.XX' v_sig_port = XXXX #id этого КЛИЕНТА, имя этого КЛИЕНТА, id искомого СЕРВЕРА v_id_client = 'id_client_1001' v_name_client = 'name_client_1' v_id_server = 'id_server_1002' #IP и PORT этого КЛИЕНТА v_ip_localhost = 'XX.XX.XX.XX' v_port_localhost = XXXX udp_socket = '' try: #Получаем текущий внешний IP и PORT при помощи утилиты STUN nat_type, external_ip, external_port = stun.get_ip_info() #Присваиваем переменным белый IP и PORT сигнального сервера для отправки запроса host_sigserver = v_sig_host port_sigserver = v_sig_port addr_sigserv = (host_sigserver,port_sigserver) #Заполняем словарь данными для отправки на СИГНАЛЬНЫЙ СЕРВЕР: #текущий id + имя + текущий внешний IP и PORT, #и id_dest - id известного сервера с которым хотим связаться. #В качестве id можно использовать хеш случайного числа + соль data_out = v_id_client + ',' + v_name_client + ',' + external_ip + ',' + str(external_port) + ',' + v_id_server #Создадим сокет с атрибутами: #использовать пространство интернет адресов (AF_INET), #передавать данные в виде отдельных сообщений udp_socket = socket(AF_INET, SOCK_DGRAM) #Присвоим переменным свой локальный IP и свободный PORT для получения информации host = v_ip_localhost port = v_port_localhost addr = (host,port) #Свяжем сокет с локальными IP и PORT udp_socket.bind(addr) #Отправим сообщение на СИГНАЛЬНЫЙ СЕРВЕР udp_socket.sendto(data_out, addr_sigserv) while True: #Если первый элемент списка - 'sigserv' (сообщение от СИГНАЛЬНОГО СЕРВЕРА), #печатаем сообщение с полученными данными и отправляем сообщение #'Hello, SERVER!' на сервер по указанному в сообщении адресу. data_in = udp_socket.recvfrom(1024) data_0 = data_in[0] data_p = data_0.split(",") if data_p[0] == 'sigserv': print('signal server data: ', data_p) udp_socket.sendto('Hello, SERVER!',(data_p[3],int(data_p[4]))) else: print("No, it is not Rio de Janeiro!") udp_socket.close() except: print ('Exit!') sys.exit(1) finally: if udp_socket <> '' udp_socket.close() sigserver_exch() 

Заполним соответствующие поля разделов: «Внешний IP и PORT СИГНАЛЬНОГО СЕРВЕРА» и «IP и PORT этого КЛИЕНТА».

Создадим файл signal_server.py с содержимым:

# -*- coding: utf-8 -*- # SIGNAL SERVER #Twisted - управляемая событиями(event) структура #Событиями управляют функции – event handler #Цикл обработки событий отслеживает события и запускает соответствующие event handler #Работа цикла лежит на объекте reactor из модуля twisted.internet from twisted.internet import reactor from twisted.internet.protocol import DatagramProtocol import sys, os import sqlite3 class Query_processing_server(DatagramProtocol): # СИГНАЛЬНЫЙ СЕРВЕР КЛИЕНТ # КЛИЕНТ -> СЕРВЕР # либо # СИГНАЛЬНЫЙ СЕРВЕР СЕРВЕР # СИГНАЛЬНЫЙ СЕРВЕР - принимает запросы от КЛИЕНТА и СЕРВЕРА # сохраняет их текущие значения IP и PORT # (если отсутствуют - создает новые + имя и идентификатор) # и выдает IP и PORT СЕРВЕРА запрошенного КЛИЕНТОМ. def datagramReceived(self, data, addr_out): conn = '' try: #Разбиваем полученные данные по разделителю (,) [id_host,name_host,external_ip,external_port,id_dest] #id_dest - искомый id сервера data = data.split(",") #Запрос на указание пути к файлу БД sqlite3, при отсутствии будет создана новая БД по указанному пути: path_to_db = raw_input('Enter name db. For example: "/home/user/new_db.db": ') path_to_db = os.path.join(path_to_db) #Создать соединение с БД conn = sqlite3.connect(path_to_db) #Преобразовывать байтстроку в юникод conn.text_factory = str #Создаем объект курсора c = conn.cursor() #Создаем таблицу соответствия для хостов c.execute('''CREATE TABLE IF NOT EXISTS compliance_table ("id_host" text UNIQUE, "name_host" text, "ip_host" text, \ "port_host" text)''') #Добавляем новый хост, если еще не создан #Обновляем данные ip, port для существующего хоста c.execute('INSERT OR IGNORE INTO compliance_table VALUES (?, ?, ?, ?);', data[0:len(data)-1]) #Сохраняем изменения conn.commit() c.execute('SELECT * FROM compliance_table') #Поиск данных о сервере по его id c.execute('''SELECT id_host, name_host, ip_host, port_host from compliance_table WHERE id_host=?''', (data[len(data)-1],)) cf = c.fetchone() if cf == None: print ('Server_id not found!') else: #transport.write - отправка сообщения с данными: id_host, name_host, ip_host, port_host и меткой sigserver lst = 'sigserv' + ',' + cf[0] + ',' + cf[1] + ',' + cf[2] + ',' + cf[3] self.transport.write(str(lst), addr_out) #Закрываем соединение conn.close() except: print ('Exit!') sys.exit(1) finally: if conn <> '' conn.close() reactor.listenUDP(9102, Query_processing_server()) print('reactor run!') reactor.run() 

Порядок запуска приложения следующий:
— signal_server.py
— server.py
— client.py

Источник

Читайте также:  Функции над массивами python
Оцените статью