Как я свой мессенджер писал

f69b24a109c4c901fe162a5b4452a3be

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

© Habrahabr.ru