Модераторы: Daevaorn
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Идеальный класс - асинхронный TCP сервер 
:(
    Опции темы
REZiaMIX
Дата 7.2.2011, 05:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 346
Регистрация: 3.11.2007

Репутация: нет
Всего: 4



Во многих моих задачах мне приходиться сталкиваться с сетевой частью, а где сетевая часть - там и сервер.
Все время в своих проектах дорабатывал , иногда переписывал и все время улучшал "велосипед".
Впринципе все отлично работает:
* в плане стабильности - все ок, ошибки уже отсеяны
* в плане производительности - все ок, потому что высокая не требуется(но ведь если можно, почему нет???)
* в тоже время чувство незаконченности - всегда есть какието архитектурные, не функциональные косяки.


И решился я написать для себя идельную либу TCP асинхронного сервера. 
На данный момент поставил для себя такие требования:
1) Кроссплатформенность
2) Используем лучшую стратегию(epoll,iocp и тп) на каждой платформе для пущей производительности
3) Законченный вид класса, чтобы не плеваться на код - самое для меня главноеsmile

Итак, за основу выбрал boost::asio - либа сразу отметает пункты 1 и 2 почти во всех ситуациях(конечено же если остальная часть выполненна правильно). К кросплатформенности вопросов нет(все что нужно есть в boost)
Остается 3.



Для меня очень удобным кажется такой вид этой "системы":
* Класс соединение(или клиент) в приватике держит все нужные внутри сервера данные - хендлы сокетов и другое.(можно наследоваться,
чтобы связать некие нужные данные с соединением)
* Класс список соединений, чтото наподобие контейнера с особой синхронизацией.
* Класс обработчик - имеет вирутальные колбеки onRead, onWrite(сервер получает ссылку на него при констракте)
* Класс сервер, для работы нужны: ссылка на обработчик и на используемый список клиентов
Имеет вирутальные onConnect,onDisconnect.

* При том, для себя очень удобным считаю, что во все вирутальные функции(такие как onRecv,onWrite,onDisconnect) приходит указатель на класс "соединение", и во все управляющие(sendBufferTo, disconnectClient).
* Можно самому инициировать подключение(connectTo)
* сервер знает всех своих клиентов, т.е. должны присутствовать методы сразу для всех соединений: disconnectAll, sendBufferAll.
* Есть огромное желание по максимуму использовать boost, НО только внутри. Снаружи должны быть чистые, легкие нешаблонные "лица".
^^^^^ хочу услышать любые мнения по этому поводу: почему коллбеки плохо? чем предложенная мной архитектура плоха, как лучше?


Что же я хочу узнать?
1) Как вы себе представляете идеальные интерфейсы: сервера, списка клиентов, клиента. 
2) Каким образом синхронизировать доступ к списку клиентов?
Я вижу такие проблемы: некий клиент (указатель на класс лежит в списке std::vector) отключился, надо заблокировать вектор. Новый подключается - вектор заблокирован, ждем разблокировки...  И т.п. комбинации подключений/отключений.  Будет много лишнего простаивания/ожидания. 
+ разные проблемы с владением: Хочу отправить пакет Васе, забираю из списка клиентов указатель на класс "соединение", в это время другой поток решает кикнуть этого клиента. В некотором месте получаем невалидный указатель на класс "соединение".

Вот наброски, чтобы примерно поняли мои задумки:
Код

class ClientBase 
{
private:
SOCKET sock;  // только тут чтото другое бустовское
SOCKET GetSocket();
};
class ClientList
{
private:
std::somecontainer<ClientBase *>
void pushClient(ClientBase *);
void eraseClient(ClientBase *);
ClientBase * capture(size_t indx);
void release(ClientBase * client);
};
class RequestHandler
{
private:
virtual void OnWrite(const ClientBase * client , size_t size);
virtual  void OnRead(const ClientBase * client , size_t size);
};
class ServerBase
{
private:
ServerBase(short int port, RequestHandler & rqhandler, ClientList & clientList);
void start(const ServerConf & conf);
void stop();
void onDisconnect(const ClientBase * client, reason_t reason);
void onConnect(const ClientBase * client, reason_t reason);
};


// потом юзаем все это:
class wwwRequestHandler : public RequestHandler
{
// переопределяем вирт.функции
...
// можно добавить чтото свое
public:
    void setWWWRoot(const std::string & dir);
    void setWWWConfiguration(const WebServerConfig & conf);
};

class WebServer : public ServerBase
{
// переопределяем вирт.функции
...
// чтото свое?
    void sendMessageToAll(const std::string & mess);
    void sendFileToAll(const std::string & filepath);
}



Что думаю по синхронизации:
1)Чтобы не блокировать весь список целиком, можем синхронизировать блоками по 4-128 соединений: но при удалении и при новом соединении(используем std::vector erase, push_back) все равно надо блокировать весь вектор! Может быть нужно выбрать альтернативный контейнер, тогда какой?
2)Проблемы с владением - слишком много мыслей, хотелось бы чтолибо изящного типа boost::shared_ptr посоветуйте ? 


Очень рад был бы увидеть наброски интерфейсов, с описанием приемуществ. 
В общем то , хочу Вашего мнения, и много критики!!! 
Когда кто-то вас в чем-нибудь упрекает, он показывает то, что могло бы стать вашим преимуществом.Бернард Вербер)))
Заранее спасибо за ответ, сразу прошу прощения за сонные ошибки. Кто дочитал нудный сей нудный пост, тому большой респект smile 



--------------------
user posted image
PM MAIL   Вверх
borisbn
Дата 7.2.2011, 10:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 4875
Регистрация: 6.2.2010
Где: Ростов-на-Дону

Репутация: 22
Всего: 135



Начал хорошо: выработка требований, разбиение на классы... а вот дальше...
1. Разработку интерфейсов стОит начинать не с private-членов, а с public-функций
2. Использовать vector в качестве контейнера, в который будут постоянно добавлять/убирать элементы, да ещё и не в конце, - не есть гуд айдеа
3. Почему у RequestHandler'а не чисто абстрактный интерфейс ? Да ещё и в private... Как ты собираешься вызывать его функции ?
4. Почему у ServerBase конструктор в private-секции ?
5. У разных классов разный code-style
Цитата(REZiaMIX @  7.2.2011,  05:51 Найти цитируемый пост)
SOCKET GetSocket();

Цитата(REZiaMIX @  7.2.2011,  05:51 Найти цитируемый пост)
void pushClient(ClientBase *);




--------------------
Женщины отличаются от программистов тем, что у них чары состоят из стрингов
PM MAIL Jabber   Вверх
REZiaMIX
Дата 7.2.2011, 11:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 346
Регистрация: 3.11.2007

Репутация: нет
Всего: 4



Вот немного подправленный по некоторым вопросам код:
Код


// класс соединения(или же клиента). Хранит в себе все связанные с данным соединением данные.
class ClientBase
{
private:
SOCKET sock;  // только тут чтото другое бустовское
SOCKET GetSocket();
bool bDeletePending;  // запросил ли кто-то удаление объекта?
};

// Массив клиентов:
class ClientList
{
private:
std::somecontainer<ClientBase *> clientsList;    //контейнер в котором лежат соединения
void pushClient(ClientBase *);                             //добавим соединение в контейнер(синхронизировано)
void eraseClient(ClientBase *);                            //удалим соединение из контейнера(синхронизировано)
ClientBase * capture(size_t indx);                       // 'захватим' клиента(предположительно будет возвращать shared_ptr, или же будет прибавлять у объекта рефы)
void release(ClientBase * client);    // 'отпустим' клиента и, если на объект больше не ссылаются + указан флажок bDeletePending - удаляем его из очереди
};

class RequestHandlerBase                 // добавил Base, чтобы видно было что он абстрактный)
{
protected:
virtual void OnWrite(const ClientBase * client , size_t size);  //потом переопределим, чтобы по другому обрабатывать исходящие данные
virtual void OnRead(const ClientBase * client , size_t size);  //потом переопределим, чтобы по другому обрабатывать входящие данные
};

class ServerBase
{
friend RequestHandlerBase;   // Добавить во френды..... плохо, но как по другому? делать public интерфейсы заместо friend?
friend ClientBase;
friend ClientList;
private:
virtual void onDisconnect(const ClientBase * client, reason_t reason);   // добавил virtual , переопределим если хотим что-то делать при дисконекте
void onConnect(const ClientBase * client, reason_t reason);  // тоже самое но при коннекте. reason_t - причина подк/откл(напр.: отключен по таймауту, отключен по внутреннему вызову, подключен при прослушивании
public: // перенес многое в паблик, ему там место!
ServerBase(const ServerConf & conf); // в ServerConf  лежат ссылки на: ClientList,RequestHandlerBase. А также номер порта для прослушки
void start();  
void stop();
};

// потом юзаем все это:
class wwwRequestHandler : public RequestHandler
{
// переопределяем вирт.функции
...
// можно добавить чтото свое
public:
    void setWWWRoot(const std::string & dir);
    void setWWWConfiguration(const WebServerConfig & conf);
};

class WebServer : public ServerBase
{
// переопределяем вирт.функции
...
// чтото свое?
    void sendMessageToAll(const std::string & mess);
    void sendFileToAll(const std::string & filepath);
};



1) Впринципе есть представления о том, что должно быть в паблике (сонный забывал писать public, в коде что выше поправил)
2) Это был один из моих вопросов: "Может быть нужно выбрать альтернативный контейнер, тогда какой?"
3) Чем же RequestHandler не абстрактный ? Добавил Base к названию чтобы было очевидней. А должны ли быть его сейчашние функции в private, или лучше protected?
4) Немного скосил, все по одной и тойже причине, извиняюсь. Перенес в паблик + start stop тудаже!
5) Так весь фокус в том, что в списке лежат именно указатели на базовые классы. Можно унаследоваться, и добавить нужную связную информацию на каждое соединение. Например для веб-сервера это могут быть хедеры запроса, наподобие "User-Agent"
А в данном случае , SOCKET (и функция getter для него) это та часть, которой пользуется сам сервер - дескриптор сокета. Соответсвенно, т.к. за основу взят буст, там будет другой класс(boost::asio::tcp::socket скорее всего)
 



--------------------
user posted image
PM MAIL   Вверх
Alca
Дата 7.2.2011, 12:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3993
Регистрация: 14.6.2006

Репутация: 7
Всего: 50



Цитата

2) Это был один из моих вопросов: "Может быть нужно выбрать альтернативный контейнер, тогда какой?"

std::list


--------------------
PM WWW ICQ Skype Jabber   Вверх
borisbn
Дата 7.2.2011, 13:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 4875
Регистрация: 6.2.2010
Где: Ростов-на-Дону

Репутация: 22
Всего: 135



Цитата(REZiaMIX @  7.2.2011,  11:14 Найти цитируемый пост)
 // добавил Base, чтобы видно было что он абстрактный)

Цитата(REZiaMIX @  7.2.2011,  11:14 Найти цитируемый пост)
Чем же RequestHandler не абстрактный ? Добавил Base к названию чтобы было очевидней.

чтобы класс стал абстрактным его функции должны быть абстрактными ( в конце объявления ставится = 0; )
Код

virtual void OnWrite(const ClientBase * client , size_t size) = 0;


Цитата(REZiaMIX @  7.2.2011,  11:14 Найти цитируемый пост)
А должны ли быть его сейчашние функции в private, или лучше protected?

В данном случае исключительно public

Цитата(REZiaMIX @  7.2.2011,  11:14 Найти цитируемый пост)
делать public интерфейсы заместо friend?

 smile и только так

Попробуй написать код, который использует твою библиотеку, как будто она уже полностью готова, и сразу увидишь все плюсы-минусы, а чтобы всё компилировалось в функциях поставь "заглушки" типа return true;



--------------------
Женщины отличаются от программистов тем, что у них чары состоят из стрингов
PM MAIL Jabber   Вверх
Alca
Дата 7.2.2011, 14:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3993
Регистрация: 14.6.2006

Репутация: 7
Всего: 50



Цитата

Попробуй написать код, который использует твою библиотеку

тесты рулят


--------------------
PM WWW ICQ Skype Jabber   Вверх
borisbn
Дата 7.2.2011, 15:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 4875
Регистрация: 6.2.2010
Где: Ростов-на-Дону

Репутация: 22
Всего: 135



REZiaMIX, когда-то делал такую же. см. аттач

Присоединённый файл ( Кол-во скачиваний: 16 )
Присоединённый файл  Bagira.zip 4,68 Kb


--------------------
Женщины отличаются от программистов тем, что у них чары состоят из стрингов
PM MAIL Jabber   Вверх
boostcoder
Дата 7.2.2011, 16:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


pattern`щик
****


Профиль
Группа: Завсегдатай
Сообщений: 5458
Регистрация: 1.4.2010

Репутация: 49
Всего: 110



а почему асинхронный сервер вообще должен быть классом? Оо
при использовании asio, запросто можно обойтись без единого класса. структура контекста соединения, и всё.
PM WWW   Вверх
REZiaMIX
Дата 7.2.2011, 19:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 346
Регистрация: 3.11.2007

Репутация: нет
Всего: 4



Цитата(boostcoder @ 7.2.2011,  16:43)
а почему асинхронный сервер вообще должен быть классом? Оо
при использовании asio, запросто можно обойтись без единого класса. структура контекста соединения, и всё.

Мое мнение, что даже для общей картины и понимания, необходимы некие сущности: 
это сервер, он принимает подключения, установим ему обработчик для данных, подключился клиент.
это не касаясь темы ООП.


Alca
Хотелось бы услышать почему именно std::list(приемущества оного, и минусы вектора)? И как лучше синхронизировать доступ к нему.
borisbn
Спасибо - то, чего действительно не хватало.

Добавлено через 4 минуты и 27 секунд
Цитата

чтобы класс стал абстрактным его функции должны быть абстрактными ( в конце объявления ставится = 0; )

Удивляюсь над собой, не вижу очевидностей  smile 

Топик буду пополнять новыми вопросами по мере продвижения разработки(только по теме). Всем респект за ответы, в репу скину позже  smile 


--------------------
user posted image
PM MAIL   Вверх
Alca
Дата 8.2.2011, 11:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3993
Регистрация: 14.6.2006

Репутация: 7
Всего: 50



Цитата

Alca
Хотелось бы услышать почему именно std::list(приемущества оного, и минусы вектора)? И как лучше синхронизировать доступ к нему.

Вот:
Цитата

2. Использовать vector в качестве контейнера, в который будут постоянно добавлять/убирать элементы, да ещё и не в конце, - не есть гуд айдеа

Вообще есть книги по STL

Добавлено @ 11:25
Ну или там очередь заюзать

Это сообщение отредактировал(а) Alca - 8.2.2011, 11:25


--------------------
PM WWW ICQ Skype Jabber   Вверх
borisbn
Дата 8.2.2011, 11:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 4875
Регистрация: 6.2.2010
Где: Ростов-на-Дону

Репутация: 22
Всего: 135



Цитата(Alca @  8.2.2011,  11:24 Найти цитируемый пост)
Вообще есть книги по STL

например: Скотт Мейерс. Эффективное использование STL
там неплохо описано в каких случаях какие контейнеры лучше использовать, а в каких - противопоказано

Это сообщение отредактировал(а) borisbn - 8.2.2011, 11:45


--------------------
Женщины отличаются от программистов тем, что у них чары состоят из стрингов
PM MAIL Jabber   Вверх
REZiaMIX
Дата 10.2.2011, 17:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 346
Регистрация: 3.11.2007

Репутация: нет
Всего: 4



Отличия то знаю, но не в данном контексте. Какие по Вашему отличия критичны для моего случая?


--------------------
user posted image
PM MAIL   Вверх
borisbn
Дата 10.2.2011, 17:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 4875
Регистрация: 6.2.2010
Где: Ростов-на-Дону

Репутация: 22
Всего: 135



Цитата(REZiaMIX @  10.2.2011,  17:19 Найти цитируемый пост)
Какие по Вашему отличия критичны для моего случая?

Как я уже говорил, ты собираешься динамически добавлять/удалять элементы из контейнера, причём не обязательно в конце. Теперь подумай, как в этом случае будет работать вектор (читай просто массив), а как список ?

Подсказка... вектор как-то так:
Код

erase( Iterator where ) {
    int idx = idxFromIterator( where );
    memcpy( &m_array[ idx ], &m_array[ idx + 1 ], m_size - idx );
}

а в листе как-то так:
Код

erase( Iterator where ) {
    where->prev->next = where->next;
}


это ОЧЕНЬ примерно, но по сути как-то вот так.


хотя... именно в твоём случае... количество подключений будет небольшое (ну не тысячи), да и частота вызова erase/push_back невысока, поэтому, как советует Мейерс, не знаешь какой контейнер использовать - используй vector

Это сообщение отредактировал(а) borisbn - 10.2.2011, 17:53


--------------------
Женщины отличаются от программистов тем, что у них чары состоят из стрингов
PM MAIL Jabber   Вверх
mabrarov
Дата 12.4.2011, 23:22 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 100
Регистрация: 12.1.2011
Где: Казань

Репутация: 8
Всего: 9



Могу предложить посмотреть на то, как реализован проект echo_server (asio samples: http://sourceforge.net/projects/asio-samples) - он создавался как раз с такой целью.

Вкратце:
1) все работает параллельно и асинхронно, с поддержкой многопоточности;
2) каждая сессия на сервере (привязанная к определенному TCP-сокету) - это активный объект, имеющий еще и внутренние циклы исполнения;
3) управление сессиями (добавление новой, удаление остановленной, останов всех сессий при останове сервера) осуществляется менеджером сессий (ma::echo::server::session_manager);
4) циклы активности (event loop) всех сессий исполняются одним пулом потоков (его размер по умолчанию связан с кол-вом логических процессоров в ОС);
5) логика менеджера сессий (как логика иного типа) исполняется другим пулом потоков (1-2 потока) - это сделано для ослабления взаимного влияния с пунктом 4 (при пиковых нагрузках);
6) вся синхронизация основана на очередях (boost::asio::io_service::strand) - это связано с необходимостью уменьшения ненужного простоя рабочих потоков и ведет (благодаря отличной реализации boost::asio::io_service::strand) к уменьшению частоты переключения потоков и увеличению вероятности использования "прогретого" кеша (кэша потока/ядра);
7) для "удержания" активных сессий в памяти менеджер сессий использует особый intrusive-container, основанный на связке boost::shared_ptr/weak_ptr - это позволяет добавлять и удалять сессии в данный контейнер со сложностью o = const и без дополнительного выделения памяти;
8) для экономии на выделении памяти остановленные сессии не освобождаются а резервируются для повторного использования;
9) предусмотрена возможность ограничения максимального количества одновременно активных сессий;
10) предусмотрен штатный останов сервера;
11) предусмотрен аварийный останов сервера и максимально быстрое прерывание работы всего сервера;
12) все параметры настраиваются а-ля CLI.

Комментариев пока ноль. Но если есть хорошие знания в Boost.Asio, то можно разобраться и без них.

Еще есть qt_echo_server - все то же самое но с GUI (и связкой с Qt).

P.S. 
Забыл "похвастать" использованием C++0x (когда компилятор его поддерживает) - а именно move semantic/rvalue reference (а это очень важно для boost::shared_ptr/weak_ptr). Плюс, некоторые плюшки-workaround-ы, сделанные специально для asio (автор asio признал их правильными и оправданными).
А комментарии будут (и мнооого) - просто хочу их сделать максимально "в стиле Boost".

Это сообщение отредактировал(а) mabrarov - 13.4.2011, 02:24
PM MAIL WWW Skype   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0879 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.