Shared rss feeds:

Servare Mentem - blog

Мой предыдущий блог

пятница, 30 мая 2008 г.

CADBiS часть I : JRadius и с чем его едят

Итак, я обещал продолжить повествование о своём проекте на "соискание степени магистра" :)... Ээ ну, в общем, тупо дипломном проекте.
Итак, что такое JRadius и почему я пишу про него, ведь в предыдущем посте, я, кажется, писал про Proxy-сервер? Так вот, к нему я ещё вернусь. А сейчас, считаю очень важным написать именно про JRadius.
Начну издалека. Самой большой проблемой при написании программ под UNIX становится отладка, особенно если большую часть рабочего времени проводишь в среде Windows. Конечно, можно возразить, что для Linux надо писать проги, сидя под Linux. Но тут я не могу согласиться. Я давно хочу перейти на Linux полностью - и дома и на работе, но не могу этого сделать по одной простой причине: на работе мне приходится писать софт в Ms Visual Studio под .NET, а под Linux это делать не представляется сколько-нибудь возможным...
Теперь многие могут возразить, что раз уж я сижу под Windows, зачем мне разрабатывать софт под *nix? Могу сказать только, что я почти уверен, что по надёжности и производительности Windows-сервера не сравнятся с никсами. Утверждаю сейчас голословно, не хочу развязывать холивары. Если у кого-то возникли сомнения - Google вам в помощь и различные форумы типа "Windows Server vs Linux server". Для меня сейчас этот вопрос не стоит. Я знаю из личного опыта, что хорошо настроенный сервер FreeBSD надёжнее и производительнее хорошо настроенного сервера Windows Server 2003 и имеет гораздо больше удобных, полезных, а главное открытых и бесплатных серверных решений. Такой сервер способен работать буквально годами без сбоев и перезагрузок.
Я немного отвлёкся от темы. Вернёмся к защите моего бакалаврского проекта. Передо мной стояла задача аналогичная той, которая встаёт перед небольшими Интернет-провайдерами или же перед системными администраторами небольших компаний где необходим доступ в Интернет и возможность отслеживать и ограничивать использование этого доступа. А именно: нужно было поднять Интернет-шлюз с аутентификацией и организацией Интернет-доступа через VPN-канал. Каждый пользователь имеет собственный аккаунт с паролем. Каждому аккаунту сопоставлен тариф (группа), которая обеспечивает ему определённый уровень привилегий (например, разрешение/запрет на доступ в определённое время суток, ограничение максимального объёма Интернет-трафика и т.д.).
Решение, ставшее уже классическим в подобной ситуации - это: FreeBSD-сервер с установленным MPD (Multilink PPTP Daemon) и FreeRADIUS. Ну и различные прочие сервисы типа radacct, squid, freenibs, mabill etc. В общем-то и моё решение в рамках бакалаврского проекта заключалось в выборе связки FreeRADIUS+FreeNIBS. Единственное - мне пришлось немного модифицировать сам FreeNIBS, написать к нему свой интерфейс (веб-морду) и выбросить ненужную функциональность. Система отлично себя зарекомендовала и в течении 2-х лет успешно работала практически без сбоев. Попутно росла функциональность системы введением и совершенствованием прокси-сервера, который отслеживал посещаемые сайты, а заодно запрещал или разрешал к ним доступ. Но в определённый момент захотелось получить от системы большую гибкость, чем она обеспечивала. К тому же к тому времени уже был готов прототип прокси-сервера, реализованного на Java, который показал, что программирование на Java полностью избавляет от проблемы кроссплатформенности и сложности отладки приложения на другой платформе. Это даёт очень высокую степень гибкости и расширяемости системы. При этом можно писать, отлаживать и профилировать приложение сидя под любой ОС. Потом просто нужно будет скопировать собранную версию на выбранную платформу, и вуаля - она работает точно так же!
И вот тогда я занялся поисками возможности реализовать управление подключениями тоже на Java, чтобы полностью переписать функционал, который предоставляет FreeNIBS на свой лад.
После довольно продолжительных поисков решение было найдено. JRadius - это Java-framework с открытым исходным кодом, реализующий клиентский и серверный функционал протокола RADIUS. JRadius-клиент позволяет вам реализовать аутентификацию и аккаунтинг по протоколу RADIUS в собственном Java-приложении. Cервер JRadius - это движок обработки RADIUS-протокола, который работает в связке с модулем rlm_jradius для FreeRADIUS. Изначально он разработан Дэвидом Бёрдом (David Bird) во время работы над PicoPoint, B.V. В последствии разработка движка продолжилась и проект поддерживается по сей день.

JRadius - это не отдельный RADIUS-сервер, это Java-сервер, который вызывается rlm_jradius модулем, встроенным во FreeRADIUS. Модуль использует пул соединений с сервером JRadius и передаёт ему пакеты RADIUS-запроса и ответа для любого из событий которое в состоянии обработать любой из модулей FreeRADIUS. Это означает, что вы можете реализовать обработку событий аутентификации, авторизации и аккаунтинга через JRadius. Сам же сервер JRadius -это лёгкий выделенный сервер, конфигурирующийся через XML. Он позволяет использовать специфичный словарь JRadius/FreeRADIUS и любое число ваших собственных обработчиков составляющих единую цепочку (chained handlers).
Итак, я проникся идеей JRadius и загорелся желанием реализовать необходимую функциональность на его базе. Всё что мне пришлось для этого сделать - это скачать JRadius, включить его в проект, написать собственный обработчик (CADBiSHandler) и переопределить метод handle().
Работать с RADIUS-пакетами в JRadius - одно удовольствие. Например, чтобы получить какой-либо атрибут, нужно всего-лишь вызвать:
req.getAttributes().get("Client-IP-Address")
А если необходимо определить тип запроса и в зависимости от этого принять какое-то решение, то можно это реализовать так:
public boolean handle(JRadiusRequest jRequest)
{
  int type = jRequest.getType();
  switch (type)
  {
    case JRadiusServer.JRADIUS_accounting:
    {
      //...
    }
    break;
    case JRadiusServer.JRADIUS_authorize:
    {
      //...
    }
    break;
  }
}
В общем, повторить функциональность FreeNIBS труда не составило. Поэтому было решено реализовать что-нибудь поинтереснее.
Изначально, когда CADBiS только зарождалась, ситуация была следующей. Имелся некоторый ограниченный объём трафика, который было разрешено скачать в месяц(например, 20 гб). Необходимо было так поделить этот трафик между пользователями, чтобы во-первых каждая группа получила то количество, которое соответствует её статусу и числу человек, а во-вторых чтобы существовала некоторая дневная норма, превышение которой запрещено, для того чтобы как можно более рационально поделить месячный Интернет-трафик между пользователями. FreeNIBS этого сделать не позволяет, а реализовать это на нём было слишком сложно и лениво. Теперь же, с использованием JRadius эта идея должна была воплотиться в жизнь.
Идея в следующем. Задаётся месячный предел трафика, превышение которого невозможно. Каждой группе пользователей (будь то администраторы или же студенты) назначается определённое число баллов. В соответствии с этим числом пересчитывается максимальное количество трафика, которое они могут использовать в месяц. Зная текущее число месяца и количество дней в месяце, можно определить сколько дней с начала месяца прошло и сколько ещё остаётся. А значит, можно вычислить дневную норму трафика для каждой группы. Однако, разрешить пользователям определённой группы скачать фиксированное количество трафика в день недостаточно, т.к. кто-то может хотеть выкачать свою долю сегодня, а завтра не придёт вообще. Поэтому было решено разрешить превышения на несколько дней вперёд. То есть, к примеру, норма для студентов - 300 мегабайт за день. Система должна разрешить скачать им 300 мегабайт за текущий день, потом ещё 300 мегабайт за следующий день, и только потом отключить.
Подобная организация позволяет гибко управлять системой и лимитами трафика. Если какой-то группе не хватает их дневного лимита, а у другой группы лимит не расходуется и наполовину, всегда можно подменить баллы так, что каждый получит трафик в соответствии со своими потребностями, и при этом общий объём останется фиксированным.
Далее я расскажу про систему категоризации и фильтрации контента, и соовтетсвенно про прокси-сервер на Java.

Ярлыки: , , , , ,

вторник, 26 февраля 2008 г.

Hibernate + MySQL + c3p0

При тестировании приложения, использующего Hibernate и коннект к MySQL обнаружилось, что если какое-то время соединение с базой "простаивает", возникает исключение типа org.hibernate.exception.JDBCConnectionException. Проблема известная, заключается в том, что по таймауту сервер MySQL отваливает соединение, не извещая об этом клиента. С помощью idle-timeout-in-seconds и max-wait-time-in-millis это не настраивается.
Решение - использование пула коннекшенов, который умеет мониторить состояние соединения, посылая периодически запросы к серверу. Таким образом нужно обновляемое соединение. Собственный алгоритм помещения соединений в пул (connection pooling algorithm) у Hibernate довольно прост. Его назначение помочь пользователю как можно быстрее начать использовать Hibernate без дополнительных настроек и он не предназначен для использования в рабочих системах, а также для тестирования производительности. Для лучшей производительности и стабильности нужно использовать третьесторонний пул соединений, т.е. нужно заменить свойство hibernate.connection.pool_size определенными настройками пула соединений.
С3PO -- JDBC пул соединений с открытыми исходными кодами (open source connection pool), распространяемый вместе с Hibernate в каталоге lib. Hibernate будет использовать встроенный провайдер соединений C3P0ConnectionProvider для пула, если установить свойства hibernate.c3p0.*. Также существует встроенная поддержка для Apache DBCP и Proxool. Для того чтобы использовать DBCPConnectionProvider, необходимо установить свойства DBCP пула соединений hibernate.dbcp.*. Кэширование подговленных JDBC-выражений (prepared statement caching) включается если установлены следующие свойства hibernate.dbcp.ps.* (DBCP statement cache properties).
Вот пример использования C3PO (hibernate.cfg.xml):



           
        org.postgresql.Driver
        jdbc:postgresql://localhost/mydatabase
        xxx
        xxx
        true
        xxx
        net.sf.hibernate.dialect.PostgreSQLDialect
        5
        20
        1800        
        50        
        
        
    
 

Ярлыки: , , ,

среда, 20 февраля 2008 г.

Java vs C#

Начну свой новый блог, пожалуй, с очень интересной и актуальной темы: сравнение возможностей Java и C#. Нашёл очень хорошую статью, где всё разобрано подробно и разложено по полочкам. Для сравнения на некоторых сайтах попадались совершенно безграмотные обзоры. Очевидно, что люди совершенно не знают о возможностях Java и судят о них с потолка. Например, видел где-то упоминание о том, что в Java нет очень полезного и удобного оператора foreach или аналогичного ему (который, впрочем, MS спёрли у Perl), тогда как в java есть полный аналог foreach: for({type} {identifier} : {array})
Вообще, решил заняться этим вопросом, когда мне начали говорить, что C# несравнимо более богатый и продвинутый язык, нежели Java. Меня это несколько удивило - ведь я всегда считал, что C# - это просто фактическая копия явы с чуть изменённым синтаксисом. А утверждается, что в C# в отличие от Java есть: нормальные Generic, delegates, partial classes и closures.

closures & delegates

Я начал с последнего, залез в справку и увидел, что closures в Java в прямом понимании действительно нет (их введут только с JDK1.7). Но вот что интересно - так ли эти closures важны - то есть неужели их наличие играет огромную роль? Сам я, как человек, имеющий достаточно небольшой опыт работы и с Java и с C#, их никогда не использовал. Давайте посмотрим. Что такое по сути closure? Первый раз при программировании я столкнулся с этим понятием когда писал класс для рендеринга на C++ Builder и хотел спрятать таймер, по которому происходила бы отрисовка сцены внутрь него. Соответственно мне требовался callback, который задавался бы этому таймеру и я хотел задать в качестве оного - метод этого класса. Но с удивлением тогда для себя увидел, что компилятор этого мне этого сделать не позволяет. Почитав справку, я узнал что у него на это есть две причины – во-первых, чисто формальная. По правилам языка, указатель на метод класса имеет тип не указателя на функцию, а указателя на метод. И его нельзя приводить к указателю на функцию. Из-за второй причины – когда мы вызываем какой-то метод, то мы его вызываем не сам по себе, а для какого-то объекта. И указатель на этот объект передается неявно как указатель this, таким образом этот метод знает поля какого именно объекта надо модифицировать. Поэтому при использовании callback'а - метода класса, нужно как-то передавать ещё и указатель на объект. Для Borland эта проблема решилась изменением стандарта C++ и добавлением модификтора __closure в свой компилятор. Closure – это специальный вид XE(расширения стандарта) указателя на функцию, используемый в большинстве функций в библиотеках Windows. В отличие от обычных указателей на функции, эти содержат не только адрес вызываемой функции (четырехбайтный указатель), но так же и указатель на объект , для которого вызывается событие (указатель this). Использование выражения __closure некоторым образом ограничивает возможности системы, так как при нем возможно использовать лишь ограниченное число объектов одного класса. Считается, что использование указателей closure – весьма важная концепция, осуществленная в CBuilder. Хотя, лично я считаю, что любая концепция, которая там осуществлена и которая при этом отдаляет их среду от стандарта - есть зло. Для того, чтобы использовать closure и при этом не нарушать стандарт можно легко обойтись маленькой библиотчкой, которую вполне можно написать самому. Подобная же вещь реализована в библиотеке boost без всяких дополнительных модификаторов. Итак, а что понимается под closure в C# и почему же это так важно? В шарпе под closure понимается функция, которая захватывает значения своего лексического окружения, когда она создается во время исполнения. Лексическое окружение функции есть множество локальных переменных, полей и других членов классов, видимых из этой функции. Определение замыкания подразумевает понятие создания функции во время исполнения. Рассмотрим пример использования замыканий на C#:
int[] ary = { 1, 2, 3 };
int x = 2;
Array.ConvertAll(ary, delegate(int elem) { return elem * x; }); // { 2, 4, 6 }
Таким образом, C# использует анонимные методы, способные замыкаться на контекст в котором они определяются. Вещь, конечно, удобная и полезная - спору нет. Но ведь в Java тоже есть замыкания! Они реализуются с помощью анонимных классов. Анонимный класс имеет доступ к полям класса, в лексическом контексте которого он определен, а так же к переменным с модификатором final в лексическом контексте метода. Например:
public final calculateInSeparateThread(final URI uri) {
 // The expression "new Runnable() { ... }" is an anonymous class.
 Runnable runner = new Runnable() {
         void run() {
             // It can access final local variables:
             calculate(uri);
             // It can access private fields of the enclosing class:
             btnSave.setEnabled(true);
         }
     };
 new Thread(runner).start();
}
Согласен, в подобном синтаксисе есть некоторая громоздкость, но не говорите мне что в Java нет замыканий. Что же касается делегатов, то вполне можно обойтись без них. В C# delegate - это по сути указатель на метод какого-то объекта. Допустим, один объект представляет собой часть какой-то работы и не хочет завязываться на input/output, но нужно реализовать лог этой работы. Всё что нужно знать объекту - сигнатуру метода, который будет вести лог (например, выводить информацию о ходе процесса на консоль). То есть, объект ничего не будет знать о реализации данного метода, но будет его вызывать. Лично на мой взгляд подобная ситуация легко заменяется имплементацией объектом, предоставляющим вывод на консоль какого-либо интерфейса ILogger. Таким образом, классу выполняющему работу нужно будет знать только о существовании этого интерфейса. А чем это собственно, хуже? По-моему, это менее запутанная тропа чем какие-то сомнительные делегаты...

Generics

Generics в C# и Java реализованы действительно по-разному. Кто-то мне говорил что в Java генерики так сказать "формальные", то есть их там фактически нет. Я решил понять что он имеет ввиду. А выяснилось следующее. В Java они реализованы с использованием "затирания типа" (type erasure). Это значит, что представление о типа существует только на этапе компиляции, после которой компилятор заменяет тип во всех generic-объявлениях на Object. Затем компилятор добавляет приведения типов в нужные места. Причина реализации данного подхода состоит в требованиях совместимости кода, использующего Generics и старого кода, который их не поддерживает. Главная проблема с затиранием типа состоит в том, что информация о типе generic не доступна во время выполнения через Reflection или идентификацию типа во время выполнения. Другое последствие этого подхода в том, что generic типы структур данных должны всегда объявляться, используя объекты а не не примитивные типы. Таким образом нужно создавать Stack<Integer> вместо Stack<int> для работы с целыми числами.
В C# есть явная поддержка generics на уровне языка MSIL. Когда generic тип компилируется, произведенный MSIL содержит информацию об определенных типах. Во время выполнения, когда ссылка применена к generic типу (например, List<int>) система проверяет был ли осуществлён запрос типа или нет. Если тип уже запрашивался, то будет возвращен созданный предварительно определенный тип. В противном случае JIT компилятор создаст новый тип, заменив параметр generic типа в MSIL определенным типом (например заменит List<T> на List<int>). Нужно отметить, что, если требуемый тип - ссылочный, тогда параметр generic типа заменяется на Object. Однако, приведения типов во среде выполнения .NET при доступе к типам нет.
Ещё одним отличием generics в Java и в C# являются constraints(ограничения) на параметр типа. В Java всего одно ограничение - derivation constraint ограничение происхождения(можно указать что тип должен обязательно расширять какой-либо класс). В C# есть ещё 2 типа ограничений: можно указать, что generic тип должен обязательно иметь пустой конструктор(конструктор по-умолчанию), а так же наложить ограничение на то ссылочный ли это тип или простой тип.
Итак, в чём это принципиальное отличие которое делает C# языком с большой буквы в сравнении с Java? Лишь в том, что нельзя определить во время выполнения какой тип передан в generic в качестве параметра? Так ведь и с этим можно справиться более простыми методами. Например, можно опять же отнаследовать свой generic от класса, в котором определить метод:
protected Class<T> genericParameter;
//...
public Class<T> getGenericParameter() {
    return genericParameter;
}
Теперь для объекта MyGeneric<Integer> mygeneric я могу получить тип Integer вызвав: mygeneric.genericParameter(). Всё предельно просто и ясно.

partial classes

Угу. Есть такой момент - в C# можно тело класса раскидать на кучу файлов. В Java же всё строго - один класс - один файл. Да, не спорю, это немного стесняет свободу. Но что же делать - идеология Java нам советует дробить программу как только можно. Если класс разрастает до такой степени, что ему становится тесно в своём файле - значит это неправильный класс с точки зрения архитектуры, его следует разбить на подзадачи и вынести их в свои классы. А идеология Microsoft - это "давайте напишем такую штуку которая будет делать ВСЁ". И на тебе - всю функциональность давайте запихнём в один класс, вот будет прекрасно. Нет уж, ребята, по-моему, это лишнее и только затрудняет понимание архитектуры приложения, как, впрочем и все остальные вещи которые якобы выводят C# на несравнимо более высокий уровень.

conclusion

Ну что сказать. Разумеется, C# в отличие от Java предоставляет много "вкусностей". Но на мой взгляд они зачастую излишни и затрудняют понимание назначения кода, запутывают программиста. Конечно, я разобрал лишь малую толику отличий. В предлагаемой статье разобрано куда больше. Я не учёл такие моменты как: принудительный вызов GC (зачем?!); передача аргументов нессылочных типов по ссылке(излишне); различие между зубчатыми массивами и многомерными массивами(достаточно полезно, хотя и не столь принципиально); свойства(properties); препроцессорные директивы; перегрузка оператора []; недвусмысленная имплементация интерфейсов; дружественные пространства имён; и ещё многая прочая муть (подробнее - в статье).
В Java многое из этого не было реализовано именно из-за того, что эти вещи часто затрудняли читабельность кода в C++. C# больше похож на C++ чем Java - это факт. Но становится ли он от этого на ступень выше Java - вопрос очень спорный. Это тоже самое что сказать "Марс лучше Юпитера" или, скажем, "Интерференция на ступень выше дифракции" или "Берёза лучше Дуба"... Я не противник C#. Согласен, он предоставляет множество удобств, которых в Java нет. Но они не столь существенны. Оба языка дают широкие возможности для выражения собственных мыслей, и нет проблем, которые можно было бы решить на C# и нельзя на Java.

[to be continued...]

Ярлыки: , ,

-->