Telnet chat server for Linux x86_64

Тема в разделе "WASM.BEGINNERS", создана пользователем Hacker, 16 июл 2023.

  1. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    134
    [​IMG]
    --- Сообщение объединено, 18 фев 2024 ---
    Структура создается из двух атрибутов, чтобы не проверять указатели, а сразу знать посмотрев/сравнив флаг. Создается динамический массив из таких типов данных
    Так это не проблема, обработал данные, потом вызвал освобождение памяти

    Все эти телодвижения с новым типом данных применяется там где пориджи традиционно ждут что для них изобретут сборщик мусора. Мы об одном и том же говорим?

    Не совсем понятно о чем ты. Вероятно можно добавить в структуру для этого новый атрибут
     
  2. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    449
    Дававайте продолжим тему концепт-разговоров на примере вашего кодеса

    как насчет разобрать топис СОЛИД-а

    upload_2024-2-18_12-15-14.png
     

    Вложения:

  3. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    А зачем вводить дополнительный атрибут, если значение указателя (нулевой/ненулевой) само по себе служит флагом?
    Ну вот - сразу требуется выделять память под массив, плюс одна аллокация на ровном месте.
    И придётся писать код для заполнения и очистки этого массива, для удаления из него указателей, которые мы хотим вернуть наружу.
    Да, типа того. Только сборщик мусора работает где-то в фоне, а в RAII объекты удаляются сразу при выходе из области видимости.
    А оно так и работает. В процедурном стиле это можно сделать так:
    Код (C++):
    1. struct Object
    2. {
    3.     void* buffer;
    4. };
    5.  
    6. void destroy(Object* object)
    7. {
    8.     if (object->buffer)
    9.     {
    10.         free(object->buffer);
    11.         object->buffer = nullptr;
    12.     }
    13. }
    14.  
    15. void move(Object* from, Object* to)
    16. {
    17.     if (from == to)
    18.     {
    19.         return;
    20.     }
    21.  
    22.     destroy(to);
    23.     to->buffer = from->buffer;
    24.     from->buffer = nullptr;
    25. }
    26.  
    27. Object object{};
    28. object.buffer = malloc(123);
    29.  
    30. Object newOwner{};
    31. move(&object, &newOwner);
    Здесь сразу есть потенциальные проблемы:
    1. Во внешнем коде и содержимое Object'a, и данные по указателю может бесконтрольно поменять кто угодно.
    2. Можно сделать так: Object obj2 = obj - в итоге два объекта владеют одним и тем же буфером

    И то же самое в объектном:
    Код (C++):
    1. class Object
    2. {
    3. private:
    4.     void* m_buffer;
    5.  
    6. public:
    7.     Object() noexcept
    8.         : m_buffer(nullptr)
    9.     {
    10.     }
    11.  
    12.     explicit Object(void* buffer) noexcept
    13.         : m_buffer(buffer)
    14.     {
    15.     }
    16.  
    17.     // Запретили его копировать:
    18.     Object(const Object&) = delete;
    19.     Object& operator = (const Object&) = delete;
    20.  
    21.     // Научили его создаваться, принимая владение:
    22.     Object(Object&& obj) noexcept
    23.         : m_buffer(std::exchange(obj.m_buffer, nullptr))
    24.     {
    25.     }
    26.  
    27.     // Научили уже созданный объект принимать владение:
    28.     Object& operator = (Object&& obj) noexcept
    29.     {
    30.         if (&obj != this)
    31.         {
    32.             if (m_buffer)
    33.             {
    34.                 free(m_buffer);
    35.             }
    36.  
    37.             m_buffer = std::exchange(obj.m_buffer, nullptr);
    38.         }
    39.  
    40.         return *this;
    41.     }
    42.  
    43.     // Научили удаляться:
    44.     ~Object() noexcept
    45.     {
    46.         if (m_buffer)
    47.         {
    48.             free(m_buffer);
    49.         }
    50.     }
    51.  
    52.     // Геттер для мутабельного контекста:
    53.     void* buffer() noexcept
    54.     {
    55.         return m_buffer;
    56.     }
    57.  
    58.     // Геттер для константного и мутабельного контекстов:
    59.     const void* buffer() const noexcept
    60.     {
    61.         return m_buffer;
    62.     }
    63. };
    Вроде кода получилось в два раза больше, зато в использовании такие объекты нагляднее, а код, работающий с ними, короче. И нет проблем из процедурного варианта:
    Код (C++):
    1. Object obj1(malloc(...));
    2. Object obj2(obj1); // Нельзя
    3. Object obj2(std::move(obj1)); // Можно
    4. obj2 = obj1; // Нельзя
    5. obj2 = std::move(obj1); // Можно
    6. obj.m_buffer = ...; // Нельзя
    7.  
    8. void func(const Object& obj)
    9. {
    10.     memcpy(obj.buffer(), ...); // Нельзя
    11. }
    12.  
    Кроме того, если у тебя есть объект, хранящий другие объекты, у которых определены операторы перемещения, ты автоматически можешь безопасно перемещать и объект-контейнер:
    Код (C++):
    1. struct SuperObject
    2. {
    3.     Object obj1;
    4.     Object obj2;
    5.     Object obj3;
    6.  
    7.     SuperObject(SuperObject&&) = default;
    8.     SuperObject& operator = (SuperObject&&) = default;
    9. };
    10.  
    11. SuperObject superObject = ...;
    12. SuperObject newOwner = std::move(superObject);
    В общем, чем сложнее и больше проект - тем заметнее преимущества объектной модели.
    И фактически ты делаешь то же самое, что процедурным кодом, только пишешь эту рутину с освобождениями и мувами не сам, а просишь компилятор.
    А внутри разницы не будет. Один раз пишешь обёртки, а затем юзаешь их везде, и компилятор сам сгенерирует код, который тебе пришлось бы писать вручную.

    Это всё касается стилистической стороны, чтобы упростить себе жизнь.
    А другая - это всё, что касается наследования и полиморфизма. Его тоже можно сделать процедурным стилем, вкладывая структурки в структурки и юзая всякие макросы, типа CONTAINING_RECORD, но опять же - это просто неудобно.
    --- Сообщение объединено, 18 фев 2024 ---
    Если пилить по солиду - было бы неплохо декомпозировать сервер хотя бы на функции, не говоря уже о классах: у меня же всё в main'e сплошной стеной.
    Например, ввести классы для соединения, для сервера и клиентов, интерфейсы для обработки команд и их реализацию (тот же обработчик /setname), инфу о клиентах хранить в клиентах, а не в отдельной мапе - тут можно далеко зайти.
    Для технодемок, имхо, достаточно самого простого кода в лоб - чтобы чел понял принцип, как оно вообще работает (а чел не понял...), а архитектуру уже накрутить не проблема.
    --- Сообщение объединено, 18 фев 2024 ---
    О, кстати, к вопросу о безопасности. Нашёл бажок в операторе перемещения в Auto::Fd - забыл закрыть текущий открытый дескриптор.
    Код (C++):
    1. Fd& operator = (Fd&& fd) noexcept
    2. {
    3.     if (&fd == this)
    4.     {
    5.         return *this;
    6.     }
    7.  
    8.     // Вот этот if надо добавить:
    9.     if (valid())
    10.     {
    11.         ::close(m_fd);
    12.     }
    13.  
    14.     m_fd = std::exchange(fd.m_fd, k_invalid);
    15.  
    16.     return *this;
    17. }
     
    alex_dz и MaKsIm нравится это.
  4. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    134
    Почему?
    Зачем? :)

    Особенно заметно когда чужой код читаешь, где автар объектной моделью обмазался.
    Преимущества есть если надо надолго спрятать в таком коде тайны мадридского двора.
     
    Последнее редактирование: 18 фев 2024
  5. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Потому что константность указателя на объект не означает константность данных, на которые смотрит мутабельный указатель в этом константном объекте.
    Код (C++):
    1. void func(const Object* obj)
    2. {
    3.     // Можно, т.к. ты не меняешь состояние самого obj'a, а const распространяется только на него:
    4.     memcpy(obj->buffer, ...);
    5. }
    Например, чтобы положить в коллекцию или перенести в другое место.
    Когда смотришь на структуру с указателями и интами, непонятно, безопасно ли её копировать через memcpy или нет.
    Не все указатели требуют освобождения (например, указатели на статические данные) и не все инты семантически копируемы (например, дескрипторы).
    А так как процедуры никак к объекту не привязаны, ты можешь даже не знать, что есть специальные функции для копирования/перемещения такого объекта, или просто ошибиться - ведь компилятор тебя никак не ограничит.
    --- Сообщение объединено, 18 фев 2024 ---
    Да, бывает, что перебарщивают с абстракциями. Но самый лучший пример недостатков процедурной модели - это исходники ядра Linux.
     
  6. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    134
    Это совсем наркомания, никто в здавом уме не будет такой код делать,
    ты начал уже какие-то гипотетические примеры приводить.

    Если этой логике следовать, с объектами можно такой же чуши нагенерировать

    Там слишком большой простор для полемики, я имел в виду что-то поконкретней для обсуждения,
    на примере типов данных для устранения проблем в сложных проектах
     
    Последнее редактирование: 18 фев 2024
  7. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    В здравом или не в здравом, но так сделать можно. В реальности примеры будут намного менее очевидные.
    Без инкапсуляции всегда упираешься в то, что все данные торчат наружу, а все способы их скрыть упираются в непрозрачные объекты или даже в непрозрачные массивы для хранения объектов.
    Когда у тебя в структурке не одно поле, а пятьдесят, и ты не знаешь внутреннюю кухню объекта, очень сложно понять, что из полей ты можешь использовать напрямую, для каких требуются геттеры, а какие поля вообще нужны только для внутреннего использования.
    Разница в том, что с объектами сделать это сложнее: надо специально задаться целью так написать, а в случае процедурной модели, когда всё открыто, так получается само.
     
  8. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    134
    Структура с 50 полями? Это идиотом надо быть

    Я с такими проблемами не сталкиваюсь, для меня это немного странно
    --- Сообщение объединено, 18 фев 2024 ---
    Чтобы данные торчали наружу, надо быть архитектурным астронавтом )
    --- Сообщение объединено, 18 фев 2024 ---
    --- Сообщение объединено, 18 фев 2024 ---
    Код (Text):
    1. namespace Auto
    2. {
    3. class Fd
    4. {
    5. public:
    6.     static constexpr int k_invalid = -1;
    7. private:
    8.     int m_fd;
    9. public:
    10.     Fd() noexcept
    11.         : m_fd(k_invalid)
    12.     {
    13.     }
    14.     explicit Fd(int fd) noexcept
    15.         : m_fd(fd)
    16.     {
    17.     }
    18.     Fd(const Fd&) noexcept = delete;
    19.     Fd(Fd&& fd) noexcept
    20.         : m_fd(std::exchange(fd.m_fd, k_invalid))
    21.     {
    22.     }
    23.     ~Fd() noexcept
    24.     {
    25.         if (valid())
    26.         {
    27.             ::close(m_fd);
    28.         }
    29.     }
    30.     Fd& operator = (const Fd&) noexcept = delete;
    31.     Fd& operator = (Fd&& fd) noexcept
    32.     {
    33.         if (&fd == this)
    34.         {
    35.             return *this;
    36.         }
    37.         m_fd = std::exchange(fd.m_fd, k_invalid);
    38.         return *this;
    39.     }
    40.     bool valid() const noexcept
    41.     {
    42.         return m_fd != k_invalid;
    43.     }
    44.     explicit operator bool () const noexcept
    45.     {
    46.         return valid();
    47.     }
    48.     int fd() const noexcept
    49.     {
    50.         return m_fd;
    51.     }
    52. };
    53. } // namespace Auto
    54. namespace Poll
    55. {
    56. class Entry : public pollfd
    57. {
    58. public:
    59.     Entry(int fd, short events) noexcept
    60.         : pollfd{ fd, events, 0 }
    61.     {
    62.     }
    63.     Entry(const Entry&) noexcept = delete;
    64.     Entry(Entry&& desc) noexcept
    65.         : pollfd(std::exchange(static_cast<pollfd&>(desc), pollfd{}))
    66.     {
    67.     }
    68.     Entry& operator = (const Entry&) noexcept = delete;
    69.     Entry& operator = (Entry&& desc) noexcept
    70.     {
    71.         if (&desc == this)
    72.         {
    73.             return *this;
    74.         }
    75.         static_cast<pollfd&>(*this) = std::exchange(static_cast<pollfd&>(desc), {});
    76.         return *this;
    77.     }
    78.     void reset() noexcept
    79.     {
    80.         pollfd::revents = 0;
    81.     }
    82.     bool signaled() const noexcept
    83.     {
    84.         return (pollfd::revents & pollfd::events) != 0;
    85.     }
    86. };
    87. } // namespace Poll
    ООП в этом случае идеальное решение :lol:
     
  9. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Ну вот не придумали ничего лучше: sched.h - include/linux/sched.h - Linux source code (v6.7.5) - Bootlin
    Я про это и говорю: преимущества объектной модели становятся заметны с увеличением масштабов.
    На маленьких проектах в пару функций разницы нет - ну закроешь ты вручную хэндлы, руки не отвалятся.
    В сложных проектах есть всё - и структуры на сотню полей, и функции на 500+ строк, и перекидывание объектов между коллекциями, и времена жизни со счётчиками ссылок.
    ООП здесь решает и проблему управления ресурсами (если язык без сборщика мусора), и структурирует код, позволяя скрывать для внешнего мира ненужное, и строить иерархии объектов через наследование.

    Короче говоря, ООП - просто эволюция процедурного программирования. Было многословно и неудобно - придумали объекты с ассоциированными функциями, наследованием и скрытием внутренностей - стало попроще.
    Концепция оказалась удачной и универсальной, подходящей под любой софт - вот её и используют.
    Можно и по старинке процедурами и структурками, но зачем?
    --- Сообщение объединено, 18 фев 2024 ---
    Ну почему безальтернативно, есть же ещё функциональное программирование - тоже удобная штука, когда надо обрабатывать данные по цепочке. Все эти ваши reduce, map, filter.
    Да и можно ли навязать что-то неудобное? Начинаешь писать на си - сразу не хватает деструкторов, не хватает полиморфизма, сразу много лишнего кода и ручной работы. Нафиг оно надо, когда можно проще и быстрее.
     
  10. Research

    Research Active Member

    Публикаций:
    1
    Регистрация:
    6 янв 2024
    Сообщения:
    134
    В основном так и происходит

    Назвать это процедурным программированием как-то язык не поворачивается )

    Для меня до сих пор не понятно, где эта хрень с ООП полезна.
    Про "масштабные проекты" как-то не заходит

    Все эти абстрактные ООП-концепции и шаблоны очень эволюционны

    [​IMG]
     
  11. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    449
    видно олд-скульную школу! в С++ 30 летней давности так да.. но уже давно нету надобности
    https://pvs-studio.com/en/blog/posts/cpp/1100/
    --- Сообщение объединено, 18 фев 2024 ---
    Еще я б добавил чутка защитного программирования - раз оперируете указателями, может попасться nullptr на вход у будет оЙ
     
    Последнее редактирование: 18 фев 2024
  12. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    А, да, всё время забываю, что у него внутри проверка. В delete, кстати, тоже.
     
  13. Kulesh

    Kulesh Member

    Публикаций:
    0
    Регистрация:
    16 фев 2024
    Сообщения:
    30
    Какие именно сообщения вызывает poll
    Что дискриптор сокета ему нужно передать я понял
    И по протаколу самого telnet там в коде 5 сообшений, 4 прочитать 1 записать
     
  14. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    poll не вызывает сообщения.
    Ты передаёшь ему массив из нескольких структур pollfd, хранящих дескрипторы, на которых ты хочешь подождать.
    Каждый pollfd в массиве хранит дескриптор, маску событий, которые ты ожидаешь, и выходную маску revents, в которую poll запишет события, которые произошли в дескрипторе.
    Формируешь этот массив, отправляешь в poll, и, как только хотя бы в одном дескрипторе произойдут указанные в events события, poll вернёт управление.
    После этого тебе нужно пробежаться по всему массиву дескрипторов и найти просигналившие, проверив у них поле revents.
    В poll нужно передавать серверный и все клиентские сокеты.
    На первой итерации, когда клиентов ещё нет, передаёшь только серверный.
    Если серверный просигналил - значит, пришёл новый клиент.
    Принимаешь этого клиента функцией accept и добавляешь этот клиентский сокет в массив дескрипторов для poll’a, и снова вызываешь poll.
    Теперь мы ждём уже на двух дескрипторах.
    Если просигналит клиентский - значит, клиент прислал данные, вычитываешь их через recv.
    Если просигналил серверный - значит, пришёл ещё один клиент, и его тоже надо принять через accept и добавить в массив для poll’a.

    Важно: каждый раз перед вызовом poll ты должен обнулить revents у всех элементов в массиве.
    --- Сообщение объединено, 23 фев 2024 ---
    Структура pollfd выглядит так:
    Код (C):
    1. struct pollfd {
    2.     int fd; /* file descriptor */
    3.     short events; /* requested events */
    4.     short revents; /* returned events */
    5. };
    Подробное описание ты можешь найти в манах: https://man7.org/linux/man-pages/man2/poll.2.html
     
  15. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.243
    если в среднем на одного клиента тратим 1000кб == получаем ужо 100 гиг памяти, а по лагам совсем швах == примерно 1мс на клиента :)