На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
Страницы: (5) « Первая ... 3 4 [5]  все  ( Перейти к последнему сообщению )  
> gcc - установка и настройка
    Собственно код клиента/сервера:
    Прикреплённый файлПрикреплённый файлShared.zip (5,98 Кбайт, скачиваний: 33)
    Он конечно ужастен, но времени не было причесать (даже объявления классов не успел вынести в хидеры), а классы проектировались вообще наобум :) И немного отклонился от ТЗ - вместо обмена данными двух программ сделал классический клиент/сервер, когда клиентов можно назапускать сколько захочешь.
      Цитата Eretic @
      Собственно код клиента/сервера:

      Не помню точно, но вроде "bool is_closed()" пишется не так.
      is_closed будет если есть событие на чтение, и при этом
      число байт для чтения равно 0.
      Проверь, если не лень. :)
      Сообщение отредактировано: ЫукпШ -
        Позволишь покомментировать?
        Из того, что называется "причесать".
        • Незачем везде пихать virtual. У тебя конкретные классы, позднее связывание им не упало от слова совсем.
        • Захваченные std::unique_lock<> блокировки нет нужны освобождать явно, это делает деструктор блокировки. Собственно для этого они и проектировались. Иначе хватило бы и явно лочить сам мютекс безо всякого std::unique_lock. К примеру IOBuffer::_wait() может выглядеть просто как
          ExpandedWrap disabled
            std::string _wait()
            {
              std::unique_lock<std::mutex> lk(io_mutex);
             
              io_cond.wait(lk, [this]{return !buf.empty();});
              return get_unlock();
            }
        • Проверка !buf.empty() внутри IOBuffer::get_unlock() не нужна. Ты уже захватил блокировку в IOBuffer::_wait(), в которую попадаешь, когда условная переменная тебе позволит, и никто другой это не успеет изменить. Но. У тебя есть дублирующий функционал в IOBuffer::get(), который мешает. Тебе следовало бы решить, как тебе надо, и оставить только один метод из этих двух. А чтоб IOBuffer::get_unlock() не звал кто ни попадя, существуют приватные методы.
        • На std::list<std::string> ты реализовал по сути собственный std::queue<>. :unsure:
        • Незачем создавать IOBuffer динамически. Это Плюсы, тут можно создавать экземпляры прям на стеке, прям экземпляры, прям без неочевидных ссылок. Тогда б не забыл его deleteнуть, компилер разрушить объект по его выходу из области видимости не забудет. С сокетом аналогично.
        • По этой же причине INPString::replace_even() неэффективен, т.к. принимает параметр по значению, т.е. new_symb является новым объектом, точной копией оригинала. Это может быть оправдано, если он меняется, а оригинал должен остаться неизменным, но у тебя он только форвардится в append(), так что не вижу никаких препятствий определить его как const std::string& new_symb – явная ссылка, причём константная. Ну и далее ещё несколько таких мест.
        • Есть и другая причина неэффективности. Вместо того, чтобы в цикле посимвольно добавлять то одно, то другое поочерёдно, проще было бы пробежаться сразу с шагом 2 и insert()-ить. Главное – не забыть скорректировать i на длину new_symb-1.
        • OUTString::append_string() какой-то странный. Фактически он просто присваивает.
        • Я бы везде в параметрах указатели заменил на ссылки.
        • В конструкторах и деструкторах нет нужды вызывать clear() для контейнеров-агрегатов. Они и так ...э-эм, это делают.
        • Вообще говоря, странно видеть в Плюсовой программе ограничения на объёмы данных. Все эти char buffer[1024], #define MAX_INPUT_BUFF 64 итп. Та и в C тоже, коль на то пошло, но там есть хоть какое-никакое оправдание, а тут просто лень недостаток опыта, в смысле. Все контейнеры умеют управлять своим хранилищем, только попроси.
        • Контейнеры указателей опасны. Весьма. Не стоит. У тебя std::list<>, он более-менее лоялен, но всё ж. Используй std::shared_ptr<>, если уж очень надо указатели.
        • Не понимаю, почему народ предпочитает while(true) вместо спецом для этого предназначенного for(;;) ...
        Не стоит к этому относиться слишком уж буквально. Просто советы на будущее.

        Добавлено
        Цитата ЫукпШ @
        is_closed будет если есть событие на чтение, и при этом
        число байт для чтения равно 0.
        Для UDP точно пустые пакеты допускаются. Сам юзал в качестве эдаких эвентов. Они ж connectless. Для TCP не уверен, но в любом случае всегда можно проверить состояние ошибки.
          Из разряда не "причесать", а вполне себе недостатки. Даже в учебных проектах видеть их не очень хочется, правда.
          • У всех классов всё торчит наружу. Ну нет необходимости всё публиковать. Только то, что нужно пользователям классов, остальное нужно закрывать. Типичный пример см. выше. Наружу выводим функцию с блокировками и под ними зовём приватные методы, которые блокировкой уже не озадачиваются. Тут это не особо важно, т.к. классы относительно просты, но когда у тебя методы зовут другие методы, так ты уменьшишь оверхед и существенно минимизируешь вероятность случайно напрограммить дидлоки.
          • Классы INPString и OUTString имеют базовым std::string, который не предназначен служить базой чему-либо. Опять же, в твоём случае всё относительно просто, но вот почитай тут и чуток повыше, там сам список. Возможно тебе имело смысл агрегировать в них std::string, а не наследоваться от него, но формально в этих классах вообще нет нужды, всё легко решается в обычном процедурном стиле: набор из нескольких функций над std::string.
          • Никогда не пиши функции в стиле как INPString::replace_even(). Ты сначала уничтожаешь clear() состояние экземпляра класса и только потом начинаешь создавать новое. Это можно делать только безотказным способом, но ни push_back(), ни append() не являются безотказными, они могут бросить исключение при нехватке памяти (или там повреждения хипа). Всегда — всегда! — сначала следует подготовить новое состояние и только потом уничтожать старое. Очень неприятно в каком-нибудь Блендере при копировании коллекции объектов, например кучи домов с улицы на улицу, получить "Not enough memory" и не только не получить копию, но и исходную улицу полностью потерять. Вот примерный правильный вариант (не стал реализовывать свой совет выше):
            ExpandedWrap disabled
              void replace_even(const std::string& new_symb)
              {
                std::string  tmp;
                std::string& self = *this;
               
                for (int i = 0; i < self.size(); i++)
                {
                  if (i & 0x01)
                  {
                    tmp.push_back(self[i]);
                  } else {
                    tmp.append(new_symb);
                  }
                }
                std::swap(self, tmp);
              }
            Обрати внимание, ни одного try{}, но код абсолютно отказоустойчив. Хоть в себя самого вставляй себя самого. Если случится страшное, *this не поменяет своего состояния, т.к. tmp лежит на стеке, и его деструктор его разрушит, т.к. даже при размотке стека по исключениям язык гарантирует вызов деструкторов всех объектов, чьи области определения снимаются, не попрощавшись. И только в самом конце std::swap() меняет местами новое и старое состояния, так что по выходу разрушится старое, а не неполностью подготовленное новое. И – да, std::swap() гарантирует безотказность для сущностей std (где явно не заявлено обратное). И заметь, это замечание не по языку конкретно, это по проектированию архитектуры в целом. Ну т.е. отказоустойчивость является независимым от языка аспектом.
          С сокетами отдельная тема. В смысле, сокеты – это отдельная тема. Для 80-ых прошлого тысячелетия архитектура сокетов возможно была вершиной программерского гения, но с позиции хотя бы рубежа веков это не архитектура, а один сплошной дуршлаг с кое-как залатанными кое-какими дырами. Так что не буду ничего писать за твои принципы организации работы с ними. Лишь пара замечаний.
          • У тебя нет какого-то конкретного протокола между клиентом и сервером. В принципе ну и ладно, задача несложная, но даже тут ты в итоге вынужден был написать много "на всякий случай". Представь себе задачу посложнее, и запросто утонешь в попытках разгрести этот ад. Впрочем, это всё ж из разряда "причесать".
          • Не следует ожидать, что коннект может пропасть посреди дороги, и его нужно восстанавливать. Ну, в смысле, то, что может пропасть, ожидать, конечно, нужно, но вот восстанавливать его не следует. Ну, в смысле, ...блин. :blush:
            Короче, сугубо архитектурно, снова без акцента конкретно на языке. Соединение с сервером – задача клиента. Без оного клиент бесполезен. Вывод: коннект в конструкторе и эксепшн в случае отказа. Не нужно позволять иметь экземпляры класса, которые в принципе неспособны ни на что. Ну и да, дисконнект = конец клиента. Теперь представь, что клиент у тебя ровно так и написан. Насколько упростится код его методов? Насколько повысится его надёжность? Тут-то у тебя несложный случай, но опять же представь себе сервис 24/7/365 с мульёном клиентов и распределёнными по доменам серверами. Из этого не следует делать вывод, что дисконект = конец работы, но это однозначно говорит о том, что это внешняя по отношению к классу сокетов проблема. Не классы должны её решать, это должно решаться на стороне, которая их использует. Если очень хочется реализовать автореконнект, следует 100500 раз подумать (хотя бы из соображений сетевой безопасности, ибо коннекты редко рвутся просто так, без неожидаемых действий третьих лиц ;) , в частности) и ежели уж решить, то решать это в отдельных классах, которые сами с твоими сокетами будут работать. Ведь по-любому новое подключение = заново инициализировать. Особенно, когда у тебя протокол.
          • Всё ж скажу, коли начал предыдущим пунктом. Не следует использовать SO_REUSEADDR без необходимости. (И даже при необходимости не стоит.) Это прекрасная возможность перехватить явки/пароли твой трафик, для чего даже повышения прав не надо.


          Добавлено
          Цитата Eretic @
          Проблема даже не в том, что именно ++ я не знаю (там стажировка на лето, ещё 10 раз выучу).
          В целом же, для первого проекта на незнакомом языке очень и очень неплохо. Серьёзно. У меня вот первые шаги были поскромнее. Но вот то, что отказали в итоге, не удивительно.
            Цитата Qraizer @
            Не понимаю, почему народ предпочитает while(true) вместо спецом для этого предназначенного for(;;) ...

            Ну не знаю ... :scratch: Лично я всегда, когда пользую оператор goto - всегда стараюсь использовать и инструкцию for(;;). Для надёжности! :good:

            ExpandedWrap disabled
              #include <iostream>
              using namespace std;
              int main() {
                int cnt = 10;
                for(;;) {
                  cout <<  cnt << endl;
                  if (!cnt) goto quit;
                  cnt = ~-cnt;
                }
                quit: return 0;
              }
              Цитата Qraizer @
              Цитата ЫукпШ @
              is_closed будет если есть событие на чтение, и при этом
              число байт для чтения равно 0.
              Для UDP точно пустые пакеты допускаются. Сам юзал в качестве эдаких эвентов. Они ж connectless. Для TCP не уверен, но в любом случае всегда можно проверить состояние ошибки.

              Для UDP в "is_closed" нет необходимости.
              При нормальном закрытии соединения никакой ошибки не возникает,
              вот это я точно помню.

              Добавлено
              Цитата Majestio @
              ExpandedWrap disabled
                #include <iostream>
                using namespace std;
                int main() {
                  int cnt = 10;
                  for(;;) {
                    cout <<  cnt << endl;
                    if (!cnt) goto quit;
                    cnt = ~-cnt;
                  }
                  quit: return 0;
                }

              В данном случае просто просится:
              ExpandedWrap disabled
                if (!cnt) break;


              -----
              Проверил по своим исходникам. Действительно, если было событие на чтение,
              а число принятых байт 0 - значит TCP соединение было закрыто с той стороны.

              recv
              Сообщение отредактировано: ЫукпШ -
                Цитата ЫукпШ @
                В данном случае просто просится:

                Я понимаю, но невыносимо захотелось goto :rolleyes:
                  На момент написания и и слова такого не знал - "коллекции", находил что-то подходящее в инете и пробовал на его основе что-то делать. Я тогда еще даже на Java с коллекциями был не знаком, чего уж тут про плюсы говорить? Да и сейчас в целом не знаком, пошла нормальная учёба на жабу и времени просто не стало отвлекаться на что-то другое.
                  Понятно, что сейчас меня бы и под дулом пистолета не заставили наследовать std::string.
                  Сегодня даже понял для чего нужны все эти модификаторы static, private и тд.
                  И вообще, было бы на написание хотя бы пара недель, а не три дня (первые два ушли на изучение сокетов), подозреваю что нашел бы подходящие потокозащищенные коллекции и не пришлось бы городить это безобразие.

                  Цитата ЫукпШ @
                  Не помню точно, но вроде "bool is_closed()" пишется не так.
                  is_closed будет если есть событие на чтение, и при этом
                  число байт для чтения равно 0.
                  Сейчас точно не вспомню, но от recv() пришлось отказаться в пользу данного способа. Толи она возвращала туфту по таймауту, толи висла напрочь. Не помню. Помню только что нашел этот способ проверки коннекта на сайте линуксоидов и он показал прекрасные результаты.

                  Цитата Qraizer @
                  Есть и другая причина неэффективности. Вместо того, чтобы в цикле посимвольно добавлять то одно, то другое поочерёдно, проще было бы пробежаться сразу с шагом 2 и insert()-ить. Главное – не забыть скорректировать i на длину new_symb-1.
                  Так там не просто вставка, а подмена чётных символов на заданную подстроку. Думаю просто пробег с шагом 2 ничего не даст, или же придется перед вставкой сначала удалять символ. А это не самая быстрая операция. Отсюда и последовательный цикл - так тупо быстрее.

                  Цитата Qraizer @
                  Вообще говоря, странно видеть в Плюсовой программе ограничения на объёмы данных. Все эти char buffer[1024], #define MAX_INPUT_BUFF 64
                  Велосипед не мой, это было в ТЗ указано - ограничить входящую строку до 64-х символов. Чтобы не писать магических циферь в коде использовал через #define.

                  Цитата Qraizer @
                  Не понимаю, почему народ предпочитает while(true) вместо спецом для этого предназначенного for(;;) ...

                  Потому что это одно и то же ;)

                  Цитата Qraizer @
                  Не следует ожидать, что коннект может пропасть посреди дороги, и его нужно восстанавливать. Ну, в смысле, то, что может пропасть, ожидать, конечно, нужно, но вот восстанавливать его не следует. Ну, в смысле, ...блин.
                  Если мы умеем распознавать пропажу связи, то почему бы не уметь её восстанавливать, чтобы завершить задачу?

                  Цитата Qraizer @
                  Соединение с сервером – задача клиента. Без оного клиент бесполезен.
                  В ТЗ вообще не упоминалось про сервер-клиент, это скорее я подметил похожие принципы и так реализовал. Там же было сказано, что программа №1 принимает данные от пользователя и отправляет их программе №2, если она конечно запущена. Если программы №2 не обнаружено, то выводит сообщение об ошибке и продолжает работу. То есть, клиент работает постоянно, независимо от связи с сервером.
                  Там же и "протокол" обмена был задан: программа №2 сама контролирует корректность принятых данных. То есть некий а-ля CRC, но на примитивном уровне. Про подтверждение приёма там не было ни слова.

                  Добавлено
                  Лучше подскажите, нафига мне углубляться в изучение контейнеров под Линукс? Я же вроде не на сисопа пошел учиться...
                  Или все-таки пригодится?
                  Сообщение отредактировано: Eretic -
                    Цитата Eretic @
                    Если мы умеем распознавать пропажу связи, то почему бы не уметь её восстанавливать, чтобы завершить задачу?
                    Я ж написал:
                    Цитата Qraizer @
                    Не классы должны её решать, это должно решаться на стороне, которая их использует.
                    Вообще же, грамотная декомпозиция задачи по составным деталькам – главный отличительный признак программера от кодера. Ну т.е.: "клиент не умеет работать без сервера, значит и не надо его этому учить; у нас есть пункт, по которому клиент должен ждать сервера, значит нужен отдельный класс подключения, т.к. клиент этого не умеет; у нас есть желание сделать реконнект после потери коннекта, значит нужен отдельный поток управления, эксплуатирующий класс подключения". Как-то так. Нынче у тебя класс решает сразу три задачи, почему и оказался перегружен кодом, не связанным с решением основной задачи и погряз в вызовах, когда не нужно и "на всякий случай". Поверь, крупные комплексы без грамотной декомпозиции ты не вывезешь. И никто не вывезет, у любого профи есть некий предел охвата сложности создаваемого им продукта. Снова обращаю внимание, что это не касается конкретно Плюсов, это общий, не зависимый от языка, аспект проектирования.
                    Цитата Eretic @
                    Лучше подскажите, нафига мне углубляться в изучение контейнеров под Линукс? Я же вроде не на сисопа пошел учиться...
                    Контейнеры в контексте ...ну, этого раздела нашего форума, скажем так, это не те контейнеры. Это те же коллекции, но в std. Почему контейнеры, а не коллекции? Та фик его знает, но классы коллекций Стандарт называет именно контейнерами. Это общепринятое название в среде Плюсов, так что никто не путается. Коллекциями обычно называются вообще любые классы, которые что-то хранят, а контейнерами те, которые в std.
                      Цитата Qraizer @
                      Вообще же, грамотная декомпозиция задачи по составным деталькам – главный отличительный признак программера от кодера. Ну т.е.: "клиент не умеет работать без сервера, значит и не надо его этому учить; у нас есть пункт, по которому клиент должен ждать сервера, значит нужен отдельный класс подключения, т.к. клиент этого не умеет; у нас есть желание сделать реконнект после потери коннекта, значит нужен отдельный поток управления, эксплуатирующий класс подключения". Как-то так. Нынче у тебя класс решает сразу три задачи, почему и оказался перегружен кодом, не связанным с решением основной задачи и погряз в вызовах, когда не нужно и "на всякий случай". Поверь, крупные комплексы без грамотной декомпозиции ты не вывезешь. И никто не вывезет, у любого профи есть некий предел охвата сложности создаваемого им продукта. Снова обращаю внимание, что это не касается конкретно Плюсов, это общий, не зависимый от языка, аспект проектирования.

                      Боюсь я тебя не совсем понял, или даже совсем не понял.
                      Вот все методы класса Socket:
                      ExpandedWrap disabled
                            Socket(int port)
                            ~Socket()
                         
                            virtual void close_socket()
                            bool is_connected()
                            bool is_closed()
                            virtual bool do_connect()
                            virtual bool send_string(std::string msg)
                            virtual bool send_int(int num)

                      О каких трех решаемых задачах сокетом идёт речь? Тут по моему как раз всё предельно просто - это банальная обёртка на socket, не делающая ничего лишнего, кроме как умения коннектиться и отсылать данные. И занимает данный класс чуть больше 100 строк кода, это вместе с комментариями и пустыми строками-разделителями. Это разве перегруз?

                      Решениние о повторном коннекте, если его еще нет, принимается в отдельном потоке output_thread(), что в целом и похоже на:
                      Цитата Qraizer
                      у нас есть желание сделать реконнект после потери коннекта, значит нужен отдельный поток управления, эксплуатирующий класс подключения".

                      Собственно код треда:
                      ExpandedWrap disabled
                        void output_thread(IOBuffer *io_buffer, std::mutex* is_exit, Socket *sock)
                        {
                            OUTString str;
                         
                            std::cout << "- thread (wr) start." << std::endl;
                            while (!is_exit->try_lock())
                            {
                                str.clear();
                                str.append_string(io_buffer->_wait());
                                if (!str.empty())
                                {
                                    // строка принята, подсчитываем и отправляем на сервер
                                    int sum = str.calck_string(str);
                                    std::cout << "- thread (wr) receive: " << str << " sum = " << sum << std::endl;
                                    if (sock->is_connected())
                                    {
                                        if (!sock->send_int(sum))
                                            std::cerr << "- thread (wr): can't send message to server." << std::endl;
                                    } else {
                                        std::cout << "- connect.....";
                                        std::flush(std::cout);
                                        if (sock->do_connect())
                                        {
                                            std::cout << "- ok." << std::endl;
                                            if (!sock->send_int(sum))
                                                std::cerr << "- thread (wr): can't send message to server." << std::endl;
                                        } else {
                                            std::cerr << "server not found." << std::endl;
                                        }
                                    }
                                }    
                            }
                            std::cout << "- thread (wr) exit." << std::endl;
                        }



                      А вот нашел условие ТЗ:
                      Цитата
                      Программа №1.
                      Должна состоять из двух потоков и одного общего буфера.
                      Поток 1. Принимает строку, которую введет пользователь. Должна быть проверка, что строка состоит только из цифр и не превышает 64 символа. После проверки строка должна быть отсортирована по убыванию и все элементы, значение которых чётно, заменены на латинские буквы «КВ». После данная строка помещается в общий буфер и поток должен ожидать дальнейшего ввода пользователя.
                      Поток 2. Должен обрабатывать данные, которые помещаются в общий буфер. После получения данных общий буфер затирается. Поток должен вывести полученные данные на экран, рассчитать общую сумму всех элементов, которые являются численными значениями. Полученную сумму передать в Программу №2. После этого поток ожидает следующие данные.

                      Примечание №1 по Программе №1: Взаимодействие потоков должно быть синхронизировано, поток №2 не должен постоянно опрашивать общий буфер. Механизм синхронизации не должен быть глобальной переменной.
                      Примечание №2 по Программе №1: Работа программы должна быть максимально независима от статуса запуска программы №2. Это значит, что внезапный останов программы №2 не должен приводить к немедленным проблемам ввода у пользователя.
                      При перезапуске программы №2 необходимо произвести передподключение.

                      То есть здесь прямо прописано, что клиент умеет работать без сервера. Просто должен переподключиться когда сервер объявится.

                      Ну и для кучи:
                      Цитата
                      Программа №2.
                      Ожидает данные от Программы №1. При получении данных происходит анализ из скольки символов состоит переданное значение. Если оно больше 2-ух символов и если оно кратно 32 выводит сообщение о полученных данных, иначе выводится сообщение об ошибке. Далее программа продолжает ожидать данные.
                      Примечание №1 по Программе №2: Работа программы должна быть максимально независима от статуса запуска программы №1. Внезапный останов программы №1 не должен приводить к немедленным проблемам отображения. Необходимо ожидать подключение программы №1 при потере связи между программами.

                      Примечание по заданию: Не обязательно все размещать в одном классе. Может быть разработана иерархия классов. Чем более функционален интерфейс класса, тем лучше.
                      Типичный сервер, в режиме ожидания клиента.

                      Вот server.cpp я бы сейчас реализовал совершенно по другому.
                        Цитата Qraizer @
                        Контейнеры в контексте ...ну, этого раздела нашего форума, скажем так, это не те контейнеры. Это те же коллекции, но в std. Почему контейнеры, а не коллекции? Та фик его знает, но классы коллекций Стандарт называет именно контейнерами. Это общепринятое название в среде Плюсов, так что никто не путается. Коллекциями обычно называются вообще любые классы, которые что-то хранят, а контейнерами те, которые в std.

                        Не заметил поначалу. В том то и дело, что нам другие контейнеры выдают: механизм изоляции в Линуксе. То есть то, на чем работает Docker и подобные. И ладно бы рассматривали "внутренности" (clone() и тд), но в основном то упор на использование утилит и Docker'a.
                        Вот и удивляюсь, при чем тут курсы по программированию? Нафига программисту умение работать с контейнерами?
                          Цитата Eretic @
                          Боюсь я тебя не совсем понял, или даже совсем не понял.
                          Не бойся, хоть не понял. Клиент – слово в контексте нашего разговора неоднозначное, я об этом не подумал. Под клиентом я подразумевал не приложение, а конкретно сетевого агента. Т.е. в твоём случае это класс сокета.
                          Цитата Eretic @
                          И занимает данный класс чуть больше 100 строк кода, это вместе с комментариями и пустыми строками-разделителями. Это разве перегруз?
                          А мог бы строк 20-30. Класс соединения ещё 15-20.
                          Но вот в чём ты точно неправ, так в том, что измеряешь сложность в строках. Хоть это и имеет отношение к сказанному мной, но опосредованное. Следуя твоей логике, тебе и IOBuffer не нужен, ибо можно очередь слепить прям в Socket, разве нет?
                          Посмотри, что реально требуется сокету. (Осторожно! Далее синтетика, не отлаживалась и даже не компилилась.) В конкретно твоём случае, вероятно, адрес "127.0.0.1" жёстко задан, но никто ж не запрещает класс сделать универсальным?
                          ExpandedWrap disabled
                            class Socket
                            {
                              /* ... */
                             
                            public:
                              Socket(const std::string& addr, unsigned short port);
                              ~Socket();
                             
                              bool send(void* msg, size_t size);
                              template <typename T>
                              bool send(const T& var) { return send(&var, sizeof(var)); }
                              int lastError() const;
                            };
                          Класс, решающий одну конкретную задачу – передачу массивов информации в addr по порту port. Класс в конструкторе устанавливает соединение, живёт, пока соединение существует и как следствие отказывается создаваться, если сервер не отвечает на попытку коннекта. Выкидываем все проверки, потому что они становятся не нужны.
                          И тут бац! в ТЗ заказчику захотелось, чтоб приложение отслеживало соединение и восстанавливало его. Ну что ж, решаем задачу ещё одним классом. Как-то так:
                          ExpandedWrap disabled
                            class Connect
                            {
                              /* ... */
                              int sock;
                              int reconnect() { /* ... */ return sock; }
                             
                            public:
                              Connect(const std::string& addr, unsigned short port): sock(-1)/* ... */ { reconnect(); }
                              ~Connect() { close(sock); }
                             
                              int getId() const { return reconnect(); }
                            };
                          Класс, решающий ровно одну задачу: установление соединения и поддержка его живым. Теперь он владеет сокетом, а класс сокетов его у него запрашивает. Класс сокетов приобретает вид:
                          ExpandedWrap disabled
                            class Socket
                            {
                              /* ... */
                              Connect conn;
                             
                            public:
                              Socket(const std::string& addr, unsigned short port): conn(addr, port) {}
                             
                              bool send(void* msg, size_t size) { /* ... */ return ::send(conn.getId(), /* ... */ ) != -1); }
                              /* ... */
                            };
                          Заметь, даже в таком реконнектирующимся сетевом агенте код будет проще. Введённые тобою методы для состояния соединения просто не нужны, т.к. по сути они без нужды выносят наружу внутренние кишки класса, которыми пользователю заморачиваться нет никакого смысла.
                          Что касается третьей задачи – перевода чисел в строки – это вообще странно видеть в задачах сетевого агента. Не всё ли равно ему, что нужно передать? Пусть подготовкой массива байт занимается вызывающая сторона.

                          Добавлено
                          P.S. Просто не следует думать, что чем больше понапихано в класс, тем лучше. Почти всегда нет. Жирные интерфейсы не оправдываются.
                            Спасибо за советы :)
                            Курс подходит к концу, сейчас Spring Boot учим и затем диплом. По ООП наконец более-менее понял суть. Да и gcc освоил заодно (на курсах по линуксу), оказалось в целом более-менее годный инструмент. Сейчас на нём часть дипломной работы делаю (работа с железом и легкое сопряжение с программой на Java).
                            Еще раз, всем спасибо!
                            0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                            0 пользователей:


                            Рейтинг@Mail.ru
                            [ Script execution time: 0,1364 ]   [ 20 queries used ]   [ Generated: 13.12.24, 00:10 GMT ]