Как я свой мессенджер писал
Интернет сейчас переживает не лучшие времена: блокировки (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)