Интернет сейчас переживает не лучшие времена: блокировки (facebook.com*, twitter.com) и сбои в работе сайтов (vk.com, google.com), накручивание донатов (Telegram), поэтому я задался вопросом о создании мессенджера без всех этих проблем. Да это много кто-пытался делать (не только я), но без особого успеха. Но я преследую прежде всего другую цель в попытках писать велосипеды: узнать точно (не считая схем) как работает та или иная программа. Сначала я писал свою программу на Python, используя самописанный эхо-сервер. Даже тестировал её со своим другом. Но у программы был серьёзный баг - при выходе пользователя сервер и клиент крашился, наверное из-за того что я не реализовал закрытие сокета и удаления пользователя из списка. Я хотел исправить этот баг (кто знает в чём ошибка тому кидаю свой код), но со временем код испортился из-за того что я преждевременно хотел добавить новые фичи: параллельный веб-сервер например. Поэтому я решил полностью переписать свой мессенджер сделав его лучше. Для этого я выбрал протокол UDP, С# и технологию P2P вместо федерации серверов.
Устройство мессенджера
Принцип работы я взял у Torrent'а: имеется центральный сервер которому хосты сливают свои IP-адреса и информацию (имена/никнеймы) потом сами хосты получают эту информацию обратно и таким образом узнают друг друга. Таким образом легче модерировать список хостов.
Код координирующего сервера:
import socket
sock = socket.socket()
sock.bind(('', 9090))
sock.listen(5)
addrs = []
#addrs.append('blank')
print("Coordinating server running")
while True:
conn, addr = sock.accept()
#conn.send('\n'.join(addrs).encode())
data = conn.recv(27+10)
message = data.decode()
if message == 'get_users':
conn.send('\n'.join(addrs).encode())
else:
addrs.append(message)
print(message)
conn.close()
Получив IP-адрес другого хоста вызывающий хост отправляет ему вместе с сообщением своё имя (по которому в дальнейшим получается IP-адрес) благодаря чему устанавливается связь между хостами.
private void sendButton_Click(object sender, EventArgs e)
{
client = new UdpClient(/*Int32.Parse(remotePort.Text)*/);
// запускаем задачу на прием сообщений
//if (username.Text != "")
//{
// nicknames[Array.IndexOf(nicknames, username.Text)] = "";
// source.Clear();
// source.AddRange(nicknames);
// textBox1.AutoCompleteCustomSource = source;
//}
if (username.Text == "")
MessageBox.Show("Введите имя!");
else if (!nicknames.Contains(username.Text))
{
MessageBox.Show("Нет зарегистрированного пользователя с таким именем!");
}
else
{
// отправляем первое сообщение о входе нового пользователя
string message = String.Format("{0}: {1}", username.Text, field.Text);
//string message = field.Text; //userName + $" ({address}) вошел в чат";
byte[] data = Encoding.Unicode.GetBytes(message);
try
{
client.Send(data, data.Length, ports[0], Int32.Parse(ports[1]));
field.Clear();
string time = DateTime.Now.ToShortTimeString();
WriteMessage($"[{time}] {message}", true, state);//\r\n");
}
catch (Exception ex)
{
MessageBox.Show("Получатель не известен!\n" + ex.Message);
}
}
//WriteMessage(message + "\r\n");
//WriteMessage(ports[1]);
//Task.Run(ReceiveMessages);
}
async void ReceiveMessages()
{
//reciever = new UdpClient(Int32.Parse(localPort.Text));
alive = true;
try
{
while (alive)
{
IPEndPoint remoteIp = new IPEndPoint(IPAddress.Any, 0); //null;
byte[] data = client.Receive(ref remoteIp);
string message = Encoding.Unicode.GetString(data);
//WriteMessage(message);
string name = message.Split(':')[0];
sender_name = name;
if (!nodes.Contains(SetAddress(name)))
nodes.Add(SetAddress(name));
//addr = addrs[Array.IndexOf(nicknames, name)].Split(':')[0];
//Match match = Regex.Match(message, @"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b");
//if (match.Success)
// addr = IPAddress.Parse(message);
/*if(((*/
//IPAddress.TryParse(message, out addr);//){
//MessageBox.Show(addr.ToString());
//}
// добавляем полученное сообщение в текстовое поле
this.Invoke(new MethodInvoker(() =>
{
string time = DateTime.Now.ToShortTimeString();
WriteMessage($"[{time}] {message}", true, state);//\r\n");
}));
}
}
catch (ObjectDisposedException)
{
if (!alive)
return;
throw;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
TODO
Можно, конечно, реализовать аккаунты в P2P-системе хранив данные на других активных машинах, но я пока не заморачивался этим вопросом так как я хотел реализовать простой мессенджер (не для домохозяек).
Также мессенджер пока может только общается только один-на-один без бесед. Но я думаю это исправляется рассылкой пришедшим пользователю писем остальным пользователям.
Ссылки
Ссылка на репозиторий с кодом - arutimasu/echo: P2P Messenger (github.com)
Ссылка на первую версию программы - echo/old at main · arutimasu/echo (github.com)
*Запрещённая в России соцсеть
Комментарии (33)
eri
13.12.2023 17:34На удп нет надежной доставки. Нужно поверх него поднимать транспортный протокол. Не изобретая велосипед можно взять rtp. Я писал такой, но использовал stun вместо сервера.
Если мессенджер анонимный ( запрещено хостить в России), то можно не хранить пользователей вообще, а использовать какой-то идентификатор - например отпечаток ключа pgp.
Возвращаясь к торрентами - можно выложить открытый ключ pgp на торрент трекер и использовать этот трекер как сервер поиска пиров.
qw1
13.12.2023 17:34А идея любопытная: использовать существующую большую DHT-сеть от торрентов для своих целей. Ну и, чтобы уж совсем не наглеть, полностью реализовать протокол DHT, помогая координировать раздачи (самим конечно же не раздавая, мессенджер не для этого предназначен).
AndreyDmitriev
13.12.2023 17:34а если клиенты за NAT сидят?
eri
13.12.2023 17:34Если сервер белый, то UDP пробивает дырку. Через неё уже делается п2п
Vamp
13.12.2023 17:34А если UDP закрыт? В моей корпоративной сети он полностью запрещён. Наверняка найдутся ещё какие-нибудь примеры сетей, где UDP ограничен (публичный wifi, например).
eri
13.12.2023 17:34Тогда у вас не будет работать и видеочат в браузере и много чего ещё.
shushu
13.12.2023 17:34Я не уверен, но мне кажется что WebRTC в браузере не работает через UDP. Там WebSocket
eri
13.12.2023 17:34DTLS транспорт там. Это UDP
RTP может ходить по вэбсокету в теории, но на практике там соединение устанавливается через SDP. Через интернет это обычно приводит к UDP, в редких случаях tcp через turn.
lair
13.12.2023 17:34Я правильно понимаю, что вашему мессенджеру нужно, чтобы у каждого клиента был доступный публичный адрес, открытый для входящих соединений?
lair
13.12.2023 17:34Ссылка на репозиторий с кодом
У вас в репозитории полный бардак. Не надо коммитить туда собранные бинарники.
aamonster
13.12.2023 17:34Я правильно понимаю, что вы пишете проект только для локальной сети, чисто чтобы поиграться с p2p? (иначе чуть ли не первое, что придётся делать – это писать код для проброса соединений через NAT, причём он не всегда будет работать, и когда прямое соединение не установить – вам придётся-таки отправлять сообщения через ваш сервер – с чего, собственно, и стоило начинать, добавив p2p гораздо позже).
no404error
13.12.2023 17:34Принцип работы я взял у Torrent'а: имеется центральный сервер которому хосты сливают свои IP-адреса и информацию (имена/никнеймы) потом сами хосты получают эту информацию обратно и таким образом узнают друг друга. Таким образом легче модерировать список хостов.
Слишком сложно для децентрализованного. Проще использовать сервисы текста с таймлайном вроде pastebin/justpaste/sharetxt... тонны их.
А самое обалденное было в эпоху мессенджера whotel, когда сообщения со скользящим шифром хранились прямо в DNS записях какого-то числа доменов tel.
Maxim_Q
13.12.2023 17:34Может проще присоединится к уже имеющемуся проекту и немного под себя допилить? Вот есть разные мессенджеры, вы их хоть посмотрите может и не нужно велосипед изобретать:
https://github.com/Nutomic/ensichat - Заброшен проект, но можно взять за основу или сделать клон.
https://code.briarproject.org/briar/briar - Даже TOR может поддерживать
https://github.com/mastodon/mastodon-android - Mastodon - это крупнейшая распределённая социальная сеть в интернете
https://github.com/krille-chan/fluffychat - клиент matrix с открытым исходным кодом
saboteur_kiev
13.12.2023 17:34сами хосты получают эту информацию обратно и таким образом узнают друг друга. Таким образом легче модерировать список хостов.
То есть каждый отправитель и получатель знают айпи друг друга и можно вычислить реальный адрес, особенно если один из них - товарищ майор?
Zara6502
13.12.2023 17:34а вам есть что прятать от товарища майора? на планете есть только одно место, где вы можете с относительной успешностью что-то хранить тайно - это ваша голова и то при определенной сноровке всё это оттуда извлекается.
saboteur_kiev
13.12.2023 17:34считаю, что чем меньше данных доступны кому-либо без предписания суда, тем лучше.
и IP адрес - именно такие данные.
aax
13.12.2023 17:34Обхожусь без велосипедов. Сейчас пользуюсь GNU/GPL месседжером со сквозным шифрованием Jami(https://jami.net/), есть сборки для Linux, Windows, macOS, Android, iOS. Одноранговая("бессерверная") сеть использует распределенную хеш-таблицу для аудентефикации в аккаунте.
Документация Ubuntu, исходя из достаточно широкого фунционала Jami рекомендует его и для корпоративного использования(есть поддержка видеоконференций, расшаривание рабочего стола и т.п., Jami так-же поддерживает функционал SIP/IAX софтофона).
Zara6502
мой минимум функций (можно и максимум) у мессенджера:
отправка сообщений, картинок, ссылок, голоса
пересылка сообщений
пересылка контакта, добавление контакта
создание групп
lolikandr
Накидываем:
не есть батарею мобилки (минимум процессорного времени)
не торомозить в фоновом режиме (например, принимать сообщение сразу, а не через 122 минуты)
быть устойчивым к бану серверов
шифрование сессионным ключом
сохранять все данные локально и удалять их только с подтверждением от пользователя ( со стороны сервера/другого пользователя можно максимум разрешить скрыть дааные для удобства)
лёгкое создание веток разговоров и перемещение диалогов по этим веткам (как можно смотреть в эту одну длинную ленту?) в рамках одной группы
Aelliari
Для любого p2p будет болью. Либо это, либо условно-мгновенные пуши
AgentFire
p2p в целом плохая затея. сами вспомните, каким овном воспринимался whatsapp в сравнении с телегой.
eri
Первые два пункта взаимоисключающие, 3 и 5 тоже
Zara6502
это не функции мессенджера, кроме последнего, хотя последнее я не понял совсем зачем нужно, наверное для больших групп?