LINUX.ORG.RU

Советы по проектированию мессенджера

 ,


2

4

Делаю простой мессенджер на сокетах, TCP/IP. Есть клиент и сервер, дошёл до этапа обработки запросов и задумался над тем, каким образом наиболее труЪшным ооп путём организовать приём и обработку пакетов. Пакеты могут быть разные, черновой вариант такой:

enum RequestType {
    Register,
    Authenticate,
    Message, // текстовое сообщение
    Attachment, // файл
};

Пока идея такая:

  • Все пакеты первым полем должны содержать int type
  • Все пакеты кроме Attachment вторым полем будут иметь поле unsigned size, а далее JSON рамером в size с необходимыми полями
  • Attachment, в отличие от остальных, не JSON, у него три поля: кому recipient, имя файла char name[32] и его размер unsigned size, далее идут данные

Приём пакетов делаю так:

void Client::start()
{
    char *buffer;
    unsigned long bytes;

    m_socket->setBlocking(false); // неблокирующий режим

    while (true) {
        bytes = m_socket->waitForRead();

        if (bytes == 0) {
            // Disconnected
            delete m_socket;
            return;
        }

        // Вот здесь нужно организовать приём и формирование пакетов

        buffer = new char[bytes];

        m_socket->recv(&buffer[0], bytes);

        delete []buffer;
    }
}

Конечно можно решить задачу «в лоб», но мне интересно:

  • Как наиболее правильно с точки зрения ооп сделать задуманное? Что-то мне подсказывает для каждого пакета создать класс, которому передавать buffer и bytes, который будет формировать пакет?
  • Какие наиболее подходящие паттерны для подобных задач?
  • Где и как лучше всего осуществлять проверку аутентифицирован ли пользователь, или нет?

Может у кого есть хорошие примеры. Thanks in advance, так сказатб.

мимокрокодил

Все пакеты первым полем должны содержать int type

В пакетах должны быть типы с точно известной длиной. uint32_t, например. Плюс, у числовых типов должен быть известный порядок байт.

    bytes = m_socket->waitForRead();

Учитывай, что в TCP передаётся поток байт, а не пакеты. Если ты с одной стороны отправил 10 кбайт одним вызовом, то с другой стороны эти 10 кбайт могут «поделиться» на кучу независимых вызово recv().

    buffer = new char[bytes];

std::vector<uint8_t>

Deleted ()

1. Опиши типы данных (желательно в виде алгебраических типов)
2. Нарисуй диаграмму последовательности (сообщений)
3. ??????
4. Profit

А ООП для этого не нужно.

quantum-troll ★★★★★ ()

Я бы первым делом посоветовал ТСу не рожать очередной велосипед, а подумать, какой из уже существующих мессенджеров наименее ужасен и присоединиться к его развитию. Серьёзно, несовместимых протоколов уже наплодили кучу, а стаскивать людей с вайбера и шкайпика некуда.

Хотя если проект не замышляется как публичный, а делается из интереса / прокачивания скиллов, то тут я умолкаю.

P.S. Занятие на самом деле интересное, если бы у меня уже не было на руках других проектов — тоже, возможно, покодил бы свой мессенджер.

hobbit ★★★★★ ()

Получился такой набросок (не тестировал, это всё конечно же будет рефакторится). Пакет состоит из uint32_t type и uint32_t jsonSize, у Attachment после JSON идёт содержимое файла.

struct Request
{
    enum RequestType {
        First = 0, // Reserved

        Register,
        Authenticate,
        Message,
        Attachment,

        Last, // Reserved
    };

    virtual ~Request();

    uint8_t *data = nullptr;

    uint32_t *data32_t() const { return reinterpret_cast<uint32_t*>(data); }

    uint32_t type() const
    {
        return *data32_t();
    }

    // Total size of the request (size of fields + size of data)
    virtual uint32_t size() const = 0;
};

struct JsonRequest: public Request
{
    uint32_t jsonSize() const
    {
        // Skip `type` field
        return  *(data32_t() + 1);
    }

    const uint8_t *jsonData() const
    {
        // Skip `type` & `size` fields
        return reinterpret_cast<uint8_t*>(data32_t() + 2);
    }

    // TODO
    std::map<std::string, std::string> json() const { return std::map<std::string, std::string>(); }

    virtual uint32_t size() const { return sizeof(type()) + jsonSize(); }
};

struct AttachmentRequest: public JsonRequest
{
    // TODO
    uint32_t fileSize() const
    {
        return static_cast<uint32_t>(std::stoi(json()["file_size"]));
    }

    // TODO
    std::string fileName() const
    {
        return json()["file_name"];
    }

    // Here goes file contents
    uint8_t *fileContents() const
    {
        return data + JsonRequest::size();
    }

    virtual uint32_t size() const { return JsonRequest::size() + fileSize(); }
};
neversleep ★★ ()
Ответ на: комментарий от neversleep

Зачем конвертировать двоичные данные перед отсылкой в текст, а по приёму обратно в двоичные данные? Вебмакаки укусили? Достаточно чётко описать двоичный формат, который вдобавок может быть нативным для большинства реальных платформ.

anonymous ()

Я не понял зачем эта возня с низкоуровневым языком, трата времени на борьбу с ним и переизобретение каждой мелочи.

Deleted ()
Ответ на: комментарий от anonymous

Вебмакаки укусили?

Ну я бы не был таким категоричным, но в целом да. Текстовые уже есть, тот же XMPP... А было бы интересно увидеть мессенджер, который с одной стороны современный, с другой не жрёт трафик как не в себя (для мобилок кое-где по-прежнему актуально).

hobbit ★★★★★ ()

Дельные советы закончились на 2ом комментарии. Для тех, кто в танке: это не демотивации тред, и не о нужности сего поделия - перечитайте ещё раз тему, там есть конкретные вопросы. А в профессионализме и гениальности местных анонимусов я ни разу не сомневаюсь. Спасибо.

neversleep ★★ ()
Последнее исправление: neversleep (всего исправлений: 5)
Ответ на: комментарий от neversleep

а какие бы вы дали советы школьнику который только что пришел в первый класс и хочет уже знать высшую математику ?
а учить вас с нуля, это абсурд, идете на гитхаб, берете любой рабочий проект, разбираете до нуля как он работает итд
потом берете еще один
и так пока не научитесь
у вас до сериализации еще полно сетевых проблем, вы даже не понимаете как закодить обмен с сетью
и этому на форумах не учат, если этому не научили в вашем вузе, то учитесь сами либо поступаете в другое высшее учебное заведение, на крайняк на какие нибудь курсы запишитесь
вон куча всяких онлайн в ютубе
хотя бы базовые вещи будете знать

anonymous ()
Ответ на: комментарий от anonymous

у вас до сериализации еще полно сетевых проблем, вы даже не понимаете как закодить обмен с сетью

  • Клиент подключился
  • Ожидание данных, как только получен минимальный объём (заголовок) - выполняются необходимые действия, и вот здесь как раз не хотелось бы «лепить», собственно, из-за чего и создал тему. И да, я знаю, что отправленное сообщение может прийти не всё сразу, или может больше, чем ожидается. О чем говорил @mironov_ivan Советы по проектированию мессенджера (комментарий)
  • Клиент отключился (освобождение памяти, удаление объектов и тд)
neversleep ★★ ()

Делаю простой мессенджер на сокетах, TCP/IP.

Между чем и чем? Лучше возьми websocket ведь тебе, скорее всего, понадобиться веб-клиент. Веб не умеет в tcp.

Ну и у тебя недостаточно хорошо с матчастью. ws тебе так же уберёт проблему с «не дочитал данные» - там сокет с сообщениями. Заодно оно умеет в tls и тебе не придётся ещё прикручивать шифрование.

Как наиболее правильно с точки зрения ооп сделать задуманное?

Что ты имеешь ввиду под ооп. Напиши.

Где и как лучше всего осуществлять проверку аутентифицирован ли пользователь, или нет?

Что значит «где»? Это банальный флаг на соединении.

delightfish ()
Ответ на: комментарий от delightfish

Между чем и чем?

Между моим самописным сервером, и моим самописным клиентом, на сокетах. Шифрование здесь не нужно. Максимум, думаю добавить контрольные суммы для верификации целостности переданных сообщений/файлов.

Что ты имеешь ввиду под ооп. Напиши.

:

Что-то мне подсказывает для каждого пакета создать класс, которому передавать buffer и bytes, который будет формировать пакет?

Чтобы, например, вот такую простыню не писать https://github.com/fffaraz/Lan-Messenger/blob/3283dd8b82e3312786d21a2ea1e8f46... разбить всё по классам или методам для обработки каждого сообщения. Это то, что я подрузамевал под «Конечно можно решить задачу «в лоб»». А может, это самый подходящий способ и не надо мудрить?

Что значит «где»? Это банальный флаг на соединении.

Наверное да, следует просто добавить флаг bool m_isLogged классу Client, и проверять его при получении всех сообщений (кроме Register/Authenticate).

neversleep ★★ ()
Ответ на: комментарий от neversleep

Между моим самописным сервером, и моим самописным клиентом, на сокетах.

Зачем тебе тогда «аутентифицирован»? Или ты будешь свой клиент кому-то раздавать? К тому же, делай сразу по-пацанский с шифрованием.

Максимум, думаю добавить контрольные суммы для верификации целостности переданных сообщений/файлов.

У тебя tcp.

Чтобы, например, вот такую простыню не писать https://github.com/fffaraz/Lan-Messenger/blob/3283dd8b82e3312786d21a2ea1e8f46... разбить всё по классам или методам для обработки каждого сообщения. Это то, что я подрузамевал под «Конечно можно решить задачу «в лоб»». А может, это самый подходящий способ и не надо мудрить?

У тебя такая же лапша.

Наверное да, следует просто добавить флаг bool m_isLogged классу Client, и проверять его при получении всех сообщений (кроме Register/Authenticate).

Ну проверять нужно правильно, не лапшой.

Что это:

m_isLogged

За треш? Тебя маздайка покусала?

delightfish ()
Ответ на: комментарий от neversleep

В чём вообще твоя проблема. Если ты не знаешь/можешь написать нормально сетевой уровень - возьми готовое.

    m_socket->setBlocking(false); // неблокирующий режим

    while (true) {
        bytes = m_socket->waitForRead();

Это ведь полный треш. Не можешь сам - возьми у других. Посмотри как там сделано. Приучись к тому, как это выглядит, когда написано нормально.

Возьми транспорт, в котором уже есть сообщения. Я тебе такой предложил. Всё, далее занимаешься уже своим прикладным протоколом.

Какой смысл в твоих вопросах про «как мне сделать обработку сообщений», если у тебя нету сетевого уровня? Тебе нужно сделать сетевой уровень, а потом спрашивать про «как сделать следующие».

delightfish ()
Ответ на: комментарий от delightfish

Ну проверять нужно правильно, не лапшой.
За треш? Тебя маздайка покусала?
Это ведь полный треш.

Ну, круто, всё сразу стало понятно.

Не можешь сам - возьми у других. Посмотри как там сделано

На гитхаб полно говна, где найти «правильный» проект? Желательно не слишком огромный.

neversleep ★★ ()
Ответ на: комментарий от neversleep

вы собираетесь учиться на всем маленьком ?
тогда у меня для вас плохая новость, ищите другое занятие
да и что бы писать правильный ооп код, нужно уметь в голове все разложить по полочкам
пример boost asio, сможете рассказать как он устроен ?
а аналог в несколько строк на коленке?
когда сможете, тогда и беритесь писать свой мессенджер

anonymous ()
Ответ на: комментарий от neversleep

Ну, круто, всё сразу стало понятно.

Я понятия не имею что значат твои вызовы, но судя по названию/описанию - ты делаешь операции с сокетом неблокируемые, а после долбишь в цикле на блокировке(либо просто долбишь). Очевидно, что это не имеет смысла.

При использование «неблокирующих» сокетов ты не можешь узнать когда данные есть. Значит нужно что-то, что тебе будет об этом говорить. Такие механизмы в ядре есть. Он триггерят события, которые ты должен обрабатывать.

На гитхаб полно говна, где найти «правильный» проект? Желательно не слишком огромный.

Дело не в говне. У тебя проблемы с сетевым уровнем. Я тебе предложил использовать готовый транспорт в котором вся фигня уже есть.

Ты можешь 1) использовать его, 2) посмотреть как он реализован. В данный момент тебе смотреть ничего ненужно. Ты сказал, что хочешь писать прикладной уровень - пиши. Потом, когда уже будешь более-менее разбираться - пойдёшь на системный уровень.

delightfish ()
Ответ на: комментарий от neversleep

Сообщил под что пишешь. Что пишешь. Выбери транспорт, реализуй сетевой уровень. Что-бы данные у тебя передавались логическими блоками. И только после этого можно уже думать о какой-то там прикладухе.

delightfish ()