Вы находитесь на странице: 1из 98

№1 октябрь 2002

журнал для профессиональных системных администраторов,


вебмастеров и программистов

Сканер портов: пример реализации


Программирование сокетов

Настройка почтового сервера: postfix+imap+mysql


Что такое SAMBA?
Python+администрирование

Философия Perl
«Стеммер» — программа морфологического анализа
Оптимизация работы с памятью в Perl
Java – магия отражения
БЕСПЛАТНАЯ
РЕКЛАМА
БЕСПЛАТНОГО
ПРОДУКТА
WWW.KERNEL.ORG
Давайте знакомиться — «Системный администратор». В октябре вышел
в свет первый номер нового журнала, который, как я надеюсь, облегчит жизнь
не только системным администраторам, но и программистам сетевых и сервер-
ных приложений, вебмастерам, любознательным студентам и всем тем, кто не
мыслит своей жизни без Интернета.
В этом номере Вы найдете полезные советы, касающиеся программирова-
ния на языках Perl и Java. Ведущий разработчик системы «Рамблер» Андрей
Коваленко делится своим опытом в области морфологического анализа. Дани-
ил Алиевский откроет секреты магии Java в статье «Java - магия отражений». С
помощью материала Вячеслава Калошина Вы сможете своими руками устано-
вить и настроить удобный почтовый сервер. Проектировщикам и студентам стар-
ших курсов будет интересна рубрика «Образование» — в ней представлен об-
ширный теоретический материал-исследование, посвященный проектированию
реляционных баз данных. Роман Сузи решает некоторые задачи администри-
рования с помощью языка Python.
Современные дамы настроены не менее решительно: удастся ли им отвое-
вать место в нише компьютерных «гуру», давно и весьма основательно занятой
мужчинами? Обо всем об этом — на страницах первого номера. Впрочем, все
что Вы будете читать - это результат творческой деятельности авторов. А воп-
рос, почему они используют тот или иной продукт, Вы можете задать им лично
на форуме нашего сайта www.samag.ru . Не сомневаюсь, что и Вам есть чем
поделиться, поэтому ждем Ваших статей и интересных материалов.

Успехов!
Искренне Ваш,
Александр Михалев
СОДЕРЖАНИЕ СОДЕРЖАНИЕ

АДМИНИСТРИРОВАНИЕ ПРОГРАММИРОВАНИЕ
Хеви Хардвэр Работа с текстом, или философия Perl
Установка и настройка коммутаторов Коновалов Евгений
CISCO CATALYST серий 2900XL и 3500 34-41
Всеволод Стахов
8-13 Эффективное использование памяти
в Perl при работе с большими строками
Удобная почтовая система Даниил Алиевский
Вячеслав Калошин 42-45
В статье «Удобная почтовая система»
подробно описывается установка и настройка «Стеммер»
почтового сервера на базе связки postfix+imap Морфологический анализ для
через СУБД mySQL. небольших поисковых систем
14-19 Андрей Коваленко
46-49
Миграция с Windows на Linux
Дмитрий Галышев Java - магия отражений
Автор материала «Миграция c Windows на Linux» Даниил Алиевский
рассматривает последовательность действий, не- 50-57
обходимо для переноса файлового сервера из
Windows NT на Linux и требования к установке ColdFusion или, возможно, лучшее
SAMBA-сервера. решение для создания динамических
20-23 сайтов
Александр Меженков
Что такое SAMBA? 58-61
Сергей Еремчук
24-27 Программирование сервисов в Windows
2000
Python в администрировании сервера: Всеволод Стахов
почему бы и нет? 62-66
Роман Сузи
28-32

2
СОДЕРЖАНИЕ СОДЕРЖАНИЕ

СЕТИ ОБРАЗОВАНИЕ
Анализатор сетевого трафика Взаимные функциональные
Владимир Мешков зависимости
68-71 Андрей Филиппович
Сканер портов: пример реализации «Взаимные функциональные зависимости» —
Владимир Мешков исследование проведено по наиболее распростра-
«Сканер портов» - Целью данной статьи является ненным учебникам, посвященным проектированию
описание принципов функционирования и внутрен- реляционных баз данных. Рассматриваются ошиб-
него устройства простого сканера портов TCP про- ки и нетривиальные моменты, освещаемые
токола. в подобной литературе.
72-76
84-89
Программирование сокетов
Всеволод Стахов CommerceML - стандарт обмена
78-82 коммерческой информацией
в формате XML
Елена Ртищева
90-93
Женщина и компьютер
ТЕНДЕНЦИИ Уступите место женщине
4-5 Евгения Саблина
94
Почему мало женщин в
компьютерных компаниях
FAQ PERL Вячеслав Михалев
95
6, 77
Анонс
96
№1, октябрь 2002 3
тенденции

Дырка в PGP 7.1 Обновлен стандарт Украина переходит на


В PGP обнаружилась очень не- безопасности свободные программы
приятная дырочка: при обработке Впервые за последние 10 лет был 15 августа на рассмотрение
длинных имен файлов в шифрован- подвергнут серьезному пересмотру Верховной Рады Украины подан
ном архиве происходит переполне- стандарт безопасности, разработан- проект Закона «Об использова-
ние буфера, со всеми вытекающи- ный Организацией по Экономичес- нии Открытого (Свободного) про-
ми последствиями. Патчик к 7.1.0 кому Сотрудничеству и Развитию граммного обеспечения в госу-
и 7.1.1 выпустила Network (Organisation for Economic дарственных учреждениях и в го-
Associates. На сайте PGP.COM, ко- Cooperation and Development, OECD). сударственном секторе хозяй-
торой NA недавно продала всю ли- В новом варианте 7799 много вни- ствования». Все госучреждения и
нейку продуктов PGP, и которая мания уделено вопросам обучения и учреждения и предприятия госу-
уже анонсировала новую версию изначальной интеграции процедур дарственного сектора народного
(см. http://www.bugtraq.ru/rsn/ безопасности в информационные си- хозяйства должны использовать
archive/2002/08/18.html) 8.0 пока стемы, а также оценке рисков. только «открытое» ПО, под кото-
тихо. Jeremy Ward, представитель рым имеются в виду программы c
Источник: CNet [http:// Confederation of British Industry (CBI) открытым исходным кодом, рас-
news.com.com/2100-1001- назвал новый документ «зеленым пространяемые по лицензии GPL,
956815.html?tag=cd_mh ] светом на информационной супер- а также по лицензиям типа
магистрали». Что же, надо почитать, Apache или BSD. Использование
чего там такого революционного. коммерческого программного
Источник: vnunet [ http:// обеспечения допускается только
Не доверяй никому www.vnunet.com/News/1134825 ] в исключительных случаях и в по-
Обнаруженная недавно пробле- рядке, изложенном в ст. 6 зако-
ма с ошибочной реализацией SSL нопроекта. При этом, одним из
в браузерах Internet Explorer и обязательных требований являет-
Konqueror (последний, впрочем, овая программа: ся способнос ть данного про-
уже исправился) в равной мере от- mod_samoylyk - модуль граммного обеспечения сохра-
носится и к другому продукту, ис- для динамического нять информацию в свободных
пользующему тот же engine - MS конфигурирования (открытых) форматах.
Outlook. виртуальных хостов Забавно, но при этом сам
Отсутствие каких-либо предуп- Модуль для переадресации вир- текст законопроек та [ht tp://
реждений о несоответствии серти- туальных хостов с возможностью oracle2.rada.gov.ua/pls/zweb/
фиката (который, в принципе, ва- установки uid и gid пользователя для webproc34?id=&pf3511=
лидный, но принадлежит другому), suexec (примерно cgiwrap + suexec 12883&pf35401=23268 ] набран в
позволяет относительно легко под- = mod_samoylyk). Описание пользо- MS Word.
делывать цифровые подписи под вательских хостов находятся в не- Источник: Верховная Рада Ук-
сообщениями. Если Microsoft в бли- зависимой базе, при добавлении раины [http://oracle2.rada.gov.ua/
жайшее время серьезно не займет- или изменении виртуального хоста pls/zweb/webproc34?id=&pf3511=
ся этой проблемой, следует ожи- перезапуск apache не требуется, 12883&pf35401=23268 ]
дать взрыва злоупотреблений на каждый виртуальный хост исполь-
этой почве, ведь с использовани- зует примерно 0,3 кб памяти
ем этой уязвимости уже написана (VirtualHost в apache сьедает 10,5 кб)
специальная программа - sslsniff, Качать отсюда http:// Новая программа: ogle -
которая живет по адресу http:// www.samoylyk.com/modules/ DVD проигрыватель
www.thoughtcrime.org/ie.html. mod_samoylyk.tar.gz (GPL) для Solaris, Linux и
http://www.theregister.co.uk/ BSD
content/55/26924.html ] Проигрыватель DVD видеодис-
ков с поддержкой DVD-меню, по-
Microsoft латает дырку в зволяет проигрывать криптованые
SSL DVD диски, возможность произво-
Linux на SmatrPhone Вышел бюллетень по недавно об- дить скриншоты, реализована воз-
Linux портирован на SmatrPhone наруженной уязвимости в реализа- можность поиска. Поддерживае-
китайской компанией CMS. Причем ции SSL и патч, исправляющий ее. мые форматы: AC-3, MPEG[2],
это вполне законченный коммер- Источник: Microsoft [http:// LPCM.
ческий продукт. www.microsoft.com/technet/treeview/
http://linuxworld.com.au/ default.asp?url=/technet/security/ http://www.dtek.chalmers.se/
news.php3?nid=1797&tid=1nY bulletin/MS02-050.asp ] groups/dvd/

4
тенденции

Network Associates Microsoft IIS6: веб- Опасные игры


купила разработчика сервер строгого режима Закон, принятый правитель-
«трояна» DragNet Проектируя IIS6, разработчики ством Греции, запрещаетэлектрон-
После улаживания финансово- из Microsoft действовали самоот- ные игры — даже на мобильном те-
го вопроса с акционерами верженно. лефоне — под страхом внушитель-
McAfee.com по возвращению конт- Философией софтверного ги- ного штрафа или длительного тю-
роля, калифорнийская компания ганта всегда было создание про- ремного заключения.
сетевой безопасности Network дуктов, максимально доступных, Можно попасть в тюрьму за
Associates заявила о приобретении максимально «скриптабельных» и убийство... монстра в видеоигре.
Traxess Inc. из штата Юта. максимально мощных. Теперь все Источник: Zdnet.ru [ http://
Основной продукт Traxess — не так. После двух лет нападок со zdnet.ru/?ID=287110 ]
«DragNet» - программа, которая от- стороны секьюрити- консультантов
сылает и воспроизводит нажатия и интернет-вандалов разработчики
клавиш с одного компьютера на Microsoft решили, что, когда дело
другой. Идея программы-шпиона касается интернета, лучшая часть Wine Tools -
состоит в контроле действий со- отваги — это осторожность. Теперь Wine Tools 1.13
трудников со стороны службы бе- они думают лишь о том, как поме- Вышла новая версия Wine
зопасности компании. Программа шать пользователям добраться до Tools — Wine Tools 1.13. Данная
предназначена для работы в гига- разных функций. программа (или скорее набор про-
битных сетях и использует запатен- грамм) умеет:
тованные технологии, позволяю- — Инсталлировать приложения.
щие «шпиону» загружать вторую — Деинсталлировать их.
копию файлов вплоть до больших Новая программа: — Создавать виртуальный
MP3-файлов или потокового видео, ScanDoc - построение Windows диск.
чтобы можно было увидеть, как документации через — Редактировать конфигурацион-
именно неправильно сотрудник ис- сканирование C++ ный файл Wine.
пользует рабочий компьютер. исходников http://www.franksworld.net/wine/
Председатель Traxess говорит, что Программа позволяет проскани- winetools/s
он рад передать продукт, создава- ровать исходники на C++ и постро-
шийся годами, в такие надежные и, ить основываясь на комментариях
главное, популярные руки, как внутри исходников проиндексиро-
Network Associates. ванную документацию для функций Linux постепенно
Network Associates тоже преус- и блоков сканируемой программы. отвоевывает позиции ...
пела в создании аналогичных про- http://scandoc.sourceforge.net/ Если верить статье, то все боль-
грамм, среди которых - платформа ше IT специалистов подумывают о
Sniffer («Нюхач» — термин, обозна- переводе серверов на Linux. 31 про-
чающий программу перехвата тра- цент перевел сервера с Windows,
фика), а точнее — Архитектура Уп- Регулярные выражения 24 — c других Unix и 14 — с других
равления Корпоративным Сниф- в Perl 6 операционных систем.
фером — для работы с любыми ви- Продолжение серии статей, посвя- Правда, 46 процентов пока не
дами трафика в высокоскоростных щенных технологиям Perl 6, цель дан- владеют серверами на основе Linux
сетях. ной статьи - знакомство с новыми воз- или не собираются переводить —
можностями построения регулярных так что есть куда расти.
выражений (regex). http://news.com.com/2100-1001-
http://www.perl.com/pub/a/2002/08/ 956496.html?tag=fd_topM
Установка и настройка 22/exegesis5.html
Linux на ноутбуке
В руководстве по установке Red
Hat 7.3 на ноутбуке Compaq MPlayer 0.90-pre7 Ряд статей для людей,
Presario 711CL затрагиваются та- Удален старый код вроде использующих ОС AIX
кие моменты как настройка ACPI libvo2, подчищен libmpcodecs, до- Журнал SysAdmin Magazine от-
подсистемы управления питанием, бавлена поддержка аудио-кодека крыл online доступ к некоторым ин-
мониторинг уровня заряда батарей, sipr и поддержка realvideo, сдела- тересным статьям по OC AIX опуб-
настройка звука и подключение ны новые фильтры, переписан ви- ликованным в 2001 году.
USB устройств. деозахват и многое другое.
ht tp://www.linuxjournal.com/ ht tp://www.mplayerhq.hu/ http://www.samag.com/articles/
article.php?sid=6291&mode=thread&order=0Ao homepage 2001/0106/supplement/

№1, октябрь 2002 5


FAQ PERL

Чем отличается Как сделать так, чтобы Как сделать так, чтобы
синтаксис скриптов на скрипт работал в программа
UNIX и WIN платформах? фоновом режиме, как гарантированно
демон? работала только в одном
Юниксовые скрипты плохо вос- экземпляре?
принимает досовский перевод стро- Варианта два. Первый - восполь-
ки — CR LF. Если открыть такой зоваться модулем Proc::Daemon, Способ первый, принятый в
файл в vi в конце строк будут ^M. второй — сделать все самому, при- мире Unix:
Удалить их можно, например таким мерно так:
скриптом: $pidfile = “/var/run/mydaemon.pid”;
use strict;
#!/bin/bash require “sys/syscall.ph”; if (-e $pidfile) {
install -d -m 0775 orig # aha, pid file is here, but process
cp $1 orig/$1.orig.“date +%m-%d-%H.%M“ # Óñòàíàâëèâàåì ïóòü ïî óìîë÷àíèþ could be dead
sed -e «s/^M//g» $1 >oooo $ENV{PATH} = “/bin:/usr/bin”; by now
mv -f oooo $1 my $myfile=file_name($0);
# ×èñòî äëÿ ïðèêîëà unless (open(PIDFILE,$pidfile)) {
$0=’mydaemon’; # too dangerous to start because
I can’t read old
# Îòäåëÿåìñÿ îò ðîäèòåëÿ PID
fork() && exit; exit 1;
Как мне получить }
# Îòêëþ÷àåìñÿ îò òåðìèíàëà my $oldpid=<PIDFILE>;
зашифрованный close STDOUT; close STDERR; close close PIDFILE;
пароль? # see if there is a process with
STDIN; such pid
# Äåëàåì êîðåíü òåêóæèì êàòàëîãîì if ($oldpid > 1 && kill(0,$oldpid))
Стандартной функцией crypt(), {
chdir “/”; # another proccess is running
например вот так: already
# Ñîçäàåì íîâóþ ñåññèþ è ñòàíîâèìñÿ exit 1;
ëèäåðîì } else {
# Calculate salt value for crypt # ãðóïïû ïðîöåññîâ, ÷òîá íàñ ñëó÷àéíî
function íå ïðèáèëè # that process is long dead
}
@saltair= split // syscall(&SYS_setsid); }
,»ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcedfghijklmnopqrstuvwxyz0123456789.»; # Ïåðåõâàòûâàåì ñèãíàëû, äëÿ êîððåê- # write pid file
òíîãî âûõîäà open (PID, «>$pidfile») or die;
sub get_salt { $SIG{“INT”} = $SIG{“QUIT”} =
srand(); $SIG{“TERM”} = “quit”; print PID $$;
close(PID);
$iOff = int(rand($#saltair)); $SIG{“HUP”} = “ignore”;
$iOff2 = int(rand($#saltair));
return join(«»,$saltair[$iOff], # Äåëàåì íàøè òåìíûå äåëà # do some work
...
$saltair[$iOff2]); ...
}
# Âûõîäèì # remove pid file
unlink $pidfile;
$crypted_passwd = quit(); exit(0);
crypt($plain_passwd,get_salt());
sub quit {
# Ïîìåùàåì ñþäà êîä äëÿ êîððåêòíîãî Способ второй, основанный на
# ïðåêðàùåíèÿ ðàáîòû блокировании файлов:
...
exit(0);
} # make a lock
$lockfilename=»/tmp/mydaemon.lock»;
unless (open
А как мне проверить Если Вы хотите написать демо- (LOCKFILE,»>$lockfilename»)) {
die «cannot open lock file\n»;
соответствие на, реализующего работу через }
введенного пароля сеть, рекомендуем ознакомиться с unless (flock
(LOCKFILE,LOCK_EX|LOCK_NB)){
зашифрованному? модулем Net::Daemon. print «my copy is already
running\n»;
exit(0);
Первых два символа пароля со- }
джержат шифровальный ключ.
# do some work
Если, взяв их, зашифровать прове- ...
ряемый пароль, то зашифрованные Как защитить мою # unlock lock file
строки должны совпасть. Пример: программу, чтобы никто close(LOCKFILE);
не смог её прочитать? unlink($lockfilename);
if (crypt($entered_passwd,
subst($crypted_passwd,0,2))
eq $crypted_passwd) { Perl-сценарий представляет со-
# Ïàðîëü âåðåí бой открыто распространяемый
} else {
# Ïàðîëü íå âåðåí код. по материалам www.xpoint.ru
} составил Дмитрий Горяинов

6
АДМИНИСТРИРОВАНИЕ
администрирование

ХЕВИ ХАРДВЭР Интеллектуальные свитчи (по-


русски коммутаторы) Cisco
Catalyst серий 2900XL и 3500
предназначены для крупных
корпоративных сетей. Они
представляют собой коммутаторы
высокого класса с
микропроцессорным
управлением, флэш-памятью,
объёмом 4 Мб и DRAM-памятью
объёмом 8мб. На данных
устройствах обычно установлена
специализированная
операционная система Cisco IOS.

УСТАНОВКА И НАСТРОЙКА
КОММУТАТОРОВ CISCO CATALYST СЕРИЙ
2900XL И 3500 ВСЕВОЛОД СТАХОВ
В данной статье я буду преимуще- сов (ARP cache) — 2048 mac адре- (>1500$), они предлагают широкий
ственно говорить о версии 12.0.x. сов для Catalyst 2900XL и 8192 для выбор сервисных функций и обеспе-
(отличия версий, в основном, в веб- Catalyst 3500, поддерживают клас- чивают хорошую пропускную спо-
интерфейсе и поддержке тех или теризацию и виртуальные сети собность. Наиболее привлекатель-
иных технологий). На каждый из (VLAN), предоставляют аппаратную ными возможностями данных свит-
коммутаторов может быть установ- безопасность портов (к порту может чей являются: организация вирту-
лено программное обеспечение быть подключено только устройство альных сетей (в дальнейшем VLAN),
стандартного (Standard Edition) и с определённым mac адресом), под- полностью изолированных друг от
расширенного типа (Enterprise держивают протокол SNMP для уп- друга, но синхронизированных меж-
Edition). равления, используют удалённое уп- ду свитчами в сети, и возможность
В enterprise edition входят: под- равление через веб-интерфейс и кластеризации для единого входа в
держка магистралей 802.1Q, прото- через командную строку (т.е. через систему управления свитчами и на-
кол TACACS+ для единой авториза- telnet или модемный порт). Кроме глядного изображения топологии
ции на свитчах, модифицированная этого, имеется возможность монито- сети (для веб-интерфейса). Перс-
технология ускоренного выбора ринга портов, т.е. трафик с одного пективным является использование
Spanning Tree (Cisco Uplink Fast) и порта (или портов) отслеживается многопортового свитча в качестве
др. Здесь я преимущественно буду на другом. Многим покажется полез- центрального элемента сети (в звез-
описывать настройку свитчей с по- ной возможность ограничивать ши- дообразной архитектуре). Хотя свит-
мощью интерфейса командной стро- роковещательный трафик на портах, чи поставляются с подробной доку-
ки (CLI). Данные свитчи предостав- предотвращая тем самым чрезмер- ментацией, но она вся на английс-
ляют множество сервисных возмож- ную загрузку сети подобными паке- ком языке и нередко не сообщает
ностей. Кроме этого, они идеально тами. Исходя из всего этого, можно некоторых вещей, а иногда, напро-
подходят для крупных сетей, так как утверж дать, что выбор свитчей тив, бывает слишком избыточной.
имеют высокую пропускную способ- Cisco Catalyst является идеальным Для начала хотел бы рассказать о
ность — до 3-х миллионов пакетов для крупных и средних сетей, так как первоначальной настройке свитча.
в секунду, большие таблицы адре- несмотря на высокую стоимость Итак, Quick Start.

8
администрирование
Присоединение Шаг 2. Введите IP адрес: рировать его через веб-интерфейс с
консольного кабеля помощью Cisco Visual Switch или че-
Enter IP address: рез консоль (Telnet или модемный
n Подключите поставляемый плос- порт). Выбор средства настройки —
кий провод в разъём на задней па- Шаг 3. Введите маску подсети и ваш выбор, но учтите, что веб-интер-
нели коммутатора с маркой нажмите Enter: фейс обладает сильной «тормознуто-
console. стью», так как основан целиком на
n Подключите другой конец кабеля Enter IP netmask: апплетах Java (не забудьте включить
к com-порту компьютера через со- поддержку Java в браузере). Кроме
ответствующий переходник и запу- Шаг 4. Введите, есть ли у вас этого Cisco Visual Switch работает
стите программу-эмулятор терми- шлюз по умолчанию N/Y, если есть, только в браузерах Microsoft IE и
нала (например, HyperTerminal или то наберите его IP адрес после нажа- Netscape (хотя у меня в Netscape 6.0
ZOC). тия Y: ничего не работало). К достоинствам
Порт консоли имеет следующие этого типа настройки можно отнести
характеристики: Would you like to enter a default наглядность, простоту и возможность
n а) 9600 бод; gateway address? [yes]: y получить помощь по всем пунктам.
n b) Нет чётности; Интерфейс командной строки являет-
n c) 8 бит данных; Шаг 5. Введите IP адрес шлюза: ся немного сложным для тех, кто ред-
n d) 1 бит остановки. ко работает с консолью, но настрой-
Важное замечание для кластера IP address of the default gateway: ка через командную строку является
(объединения нескольких коммутато- очень быстрой и предоставляет до-
ров): если вы хотите использовать Шаг 6. Введите имя хоста комму- полнительные возможности. Альтер-
коммутатор в качестве члена класте- татора: нативным способом настройки явля-
ра, то можно не присваивать ему IP ется веб-консоль. В ней показывают-
адрес и не запускать построитель кла- Enter a host name: ся в виде гиперссылок допустимые
стера. В случае командного свитча, команды CLI, и вы можете собрать
вам необходимо выполнить следую- Шаг 7. Введите пароль. Кроме это- нужную последовательность команд
щий пункт. го, затем на вопрос о пароле для как бы из кирпичиков.
n Присвоение IP коммутатору Telnet ответьте Y и введите пароль для Далее, можно настроить VLAN,
В первый раз, когда вы запускае- доступа через Telnet, так как иначе вообще термин VLAN — виртуальная
те свитч, то он запрашивает IP адрес. возможны странности работы с telnet. локальная сеть. Такая сеть отличает-
Если вы назначаете ему оный, что У меня, к примеру, подключение ся от физической LAN лишь тем, что
весьма желательно, то он может кон- Telnet к свитчу обрывалось по причи- организуется разделение пакетов в
фигурироваться через Telnet. не: пароль нужен, но не определён: единой локальной сети так, как если
бы это были разные подсети. Таким
Необходимые Enter enable secret: образом, с помощью VLAN можно
требования к IP организовать деление локальной
Создался следующий файл конфи- сети на отдельные участки. При этом
Перед установкой необходимо гурации: существует возможность регулиро-
знать следующую информацию о вать взаимодействие VLAN весьма
сети: Initial configuration: широко.
n IP адрес свитча. interface VLAN1
n Маска подсети. ip address 172.16.01.24 255.255.0.0
ip default-gateway 172.16.01.01
Типы VLAN
n Шлюз по умолчанию (его может и enable secret 5 $1$M3pS$cXtAlkyR3/
не быть). 6Cn8/ Нет нужды говорить, что суще-
snmp community private rw
n Ну и пароль для свитча (хотя, ско- snmp community public ro ствует несколько типов организации
рее всего лучше это придумать са- end VLAN в сети. Самый простой из них -
мому). Use this configuration? [yes/no]: статический. Вы назначаете каждому
порту какой-либо номер VLAN, и тра-
Первый запуск Шаг 8. Если всё нормально - жми- фик будет передаваться только на те
те Y; нет - N (только учтите, что па- порты, что принадлежат тому же
Выполняйте следующие действия роль хранится в зашифрованном
для присвоения коммутатору IP адре- виде).
са:
Шаг 1. Нажмите Y при первой под- Открытие Cisco Visual
сказке системы: Switch Manager Software
Continue with configuration dialog? После того, как вы присвоили IP
[yes/no]: y
коммутатору, то вы можете конфигу-

№1, октябрь 2002 9


администрирование
DEV_PLUS_VID_NO_PAD(ïî óìîë÷àíèþ)
èìÿ óñòðîéñòâà áóäåò âûãëÿäåòü òàê:
eth0.5

Включаем интерфейс физической


сетевой карты, но без IP адреса (в до-
кументации сказано, что если у реаль-
ной сетевой карты есть IP адрес, то vlan
работать не будут, но у меня это про-
шло без особых проблем, главное, что-
бы все сетевые устройсва были в РАЗ-
НЫХ ПОДСЕТЯХ, причём это касается
и виртуальных LAN, иначе вообще ни-
чего работать не будет — проверял):
# ifconfig eth0 0.0.0.0 up

Каждый VLAN добавим на нужный


интерфейс (не добавляйте VLAN по
умолчанию, так как пакеты этого VLAN
идут без инкапсуляции):

# vconfig add eth0 2


# vconfig add eth0 3

Всё, мы прописали VLAN’ы, кото-


рые будут использоваться на интер-
фейсе, нужно присваивать им Ip ад-
реса (тип имени см. выше) в разных
VLAN (при этом необязательно, что- vlan-utils присутствует во многих со- подсетях:
бы они были на том же свитче). При временных дистрибутивах Linux
этом абсолютно исключается возмож- (Mandrake и ALT Linux). С ядра 2.4.9 # ifconfig -a
# ifconfig -i vlan0002 192.168.2.1
ность взаимодействия с «чужим» пор- опция компиляции CONFIGURE_802Q broadcast 192.168.2.255 netmask
том. А сами коммутаторы соединяют- присутствует на странице NET как эк- 255.255.255.0 up
# ifconfig -i vlan0003 192.168.3.1
ся между собой посредством особых спериментальная, поэтому вы долж- broadcast 192.168.3.255 netmask
каналов связи - trunk магистралей. По ны поставить соответствующую оп- 255.255.255.0 up
таким магистралям проходят данные цию CONFIGURE_EXPERIMENTAL(в
всех VLAN. Но, к сожалению, trunk ядрах 2.5 эта опция уже не экспери-
порт должен быть point-to-point (о двух ментальная), затем make dep —> Технические параметры
концах) и может подключаться толь- make bzImage. Настройка VLAN со Производительность
ко к свитчам и роутерам, поддержи- стороны Linux тоже не должна вызы-
вающим VLAN, так как к пакету до- вать трудностей. Для настройки слу- Êîììóòàöèîííàÿ øèíà 8.8 Ãáèò/ñåê
Ñêîðîñòü êîììóòàöèè 64-áàéòîâûõ ïà-
бавляется 4-х байтный тег, содержа- жит утилита vconfig из пакета vlan- êåòîâ
щий информацию о VLAN и её при- utils. Для получения этой утилиты, а Catalyst 2950-12: 1.8 Ìïàêåò/ñåê
Catalyst 2950-24: 3.6 Ìïàêåò/ñåê
оритете. Таким образом, организация также патчей для ядра откройте http:/ Catalyst 2950T-24: 6.6 Ìïàêåò/ñåê
сетевого доменного сервера стано- /scry.wanfear.com/~greear/vlan.html. Catalyst 2950C-24: 3.9 Ìïàêåò/ñåê
Ìàêñèìàëüíàÿ ñêîðîñòü êîììóòàöèè 4.4
вится возможной только при исполь- Патч для ядра описан в FAQ на дан- Ãáèò/ñåê
зовании роутера. Но сейчас есть вы- ной странице, я же ограничусь приме- 8 Máàéòíàÿ ðàñïðåäåëåííàÿ àðõèòåêòó-
ðà ïàìÿòè äëÿ âñåõ
ход в использовании ОС Linux, кото- ром настройки 2 VLAN на Linux маши- ïîðòîâ
рая поддерживает данный протокол не: 16 Máàéò DRAM è 8 MÁàéò ôëýø ïàìÿòü
Ïîääåðæêà 8,192 MAC àäðåñîâ
на уровне драйвера ядра, VLAN фи-
гурируют в качестве виртуальных се- Ñòàíäàðòû
vconfig set_name_type [name-type] - IEEE 802.1x support (planned future
тевых устройств. Здесь нужно только òèï íàèìåíîâàíèÿ âèðòóàëüíûõ óñòðîéñòâ,
ìîæåò ïðèíèìàòü ñëåäóþùèå çíà÷åíèÿ: software support)
указать правильный тип инкапсуляции IEEE 802.3x full duplex on 10BaseT,
VLAN_PLUS_VID 100BaseTX, and
пакетов - IEEE 802.1q (по умолчанию 1000BaseT ports
èìÿ óñòðîéñòâà áóäåò âûãëÿäåòü òàê: IEEE 802.1D Spanning-Tree Protocol
используется тип ISL). При этом, если vlan0005
VLAN_PLUS_VID_NO_PAD IEEE 802.1p CoS
все VLAN находятся в одной подсети, IEEE 802.1Q VLAN
èìÿ óñòðîéñòâà áóäåò âûãëÿäåòü òàê: IEEE 802.3ab 1000BaseT specification
то маршрутизация будет проходить vlan5
DEV_PLUS_VID IEEE 802.3u 100BaseTX specification
только на уровне статических путей. IEEE 802.3 10BaseT specification
èìÿ óñòðîéñòâà áóäåò âûãëÿäåòü òàê:
Данной теме посвящено достаточное eth0.0005 Äàííûå âçÿòû ñ ñàéòà amt.ru
количество документации, и пакет

10
администрирование
Если вы планируете использовать
маршрутизацию в сети, то добавьте
следующее:

# echo 1 > /proc/sys/net/ipv4/


ip_forward

Для удаления VLAN используйте


синтаксис:

# vconfig rem vlan0002

Подобные скрипты удобно пропи-


сать в файле /etc/init.d/network.
Кстати, учтите, что MTU в trunk
магистралях не 1500, а 1504 из-за Итак, вы подсоединили нуль-мо- разом, после сделанных изменений
того самого тега. демный кабель или запустили сеанс неплохо было бы смотреть, что имен-
Trunk магистрали поддерживаются telnet. Во-первых, на приглашение но произошло.
всеми типами свитчей, которые умеют HOST_NAME> надо ответить enable Для постоянного сохранения пара-
делать VLAN. Другое преимущество - и ввести пароль к свитчу для полу- метров настройки наберите write
использование особого протокола чения доступа к конфигурации. Для memory. Для перезагрузки свитча ис-
CISCO, обеспечивающего централизо- просмотра сведений о свитче набе- пользуйте команду reload. И заканчи-
ванное управление всей системой рите show running-config. Подсказка вая эту тему, подскажу, как сбросить
VLAN - VTP (vlan trunk protokol). Напри- по интерфейсу CLI. Здесь есть такие настройки свитча после неудачных
мер, вы можете на сервере VTP отклю- удобства как автодополнение кноп- опытов:
чить или включить определённую VLAN. кой TAB — наберите начало коман-
Широковещательный трафик по умол- ды, например show ru<TAB>, и оно >enable
#rename flash:config.text
чанию не распространяется между расширится в show running-config. flash:ÄÐÓÃÎÅ_ÈÌß.text
VLAN. MultiVLAN очень интересный тип Можно в любой момент получить #reload
организации VLAN. Он состоит в опре- справку по любому вопросу: просто
делении для порта нескольких допусти- нажмите ? и вам будут предложены Если вы забыли пароль, то дело
мых VLAN (например, для экономистов возможные параметры команды, на- ещё хуже. Для смены пароля нужно:
это могут быть VLAN Economics и Server пример, show ?. Для повторения пре- n Выключить питание свитча.
для доступа к общим серверам и.т.д.). дыдущих или следующих команд n Подсоединить нуль-модемный ка-
Такой тип подходит для организации можно использовать курсоры вверх бель.
сети с помощью одного центрального или вниз. Для пролистывания текста n При включении питания удержи-
Catalyst свитча и нескольких других при запросе —more— нажимайте вать кнопку mode.
свитчей или хабов, к нему подключён- пробел для опускания текста вниз на n При появившейся подсказке на-
ных. Общий сервер также должен под- строк у. Итак, вы набрали show брать flash_init (инициализация
ключаться к центральному Catalyst running-config, после этого отобразит- файловой системы).
свитчу к multiVLAN порту для общения ся информация о текущих настрой- n rename flash:config.text flash:
со всеми рабочими станциями. Широ- ках. Вначале общая информация о ДРУГОЕ_ИМЯ.text
ковещательные же пакеты будут рас- свитче (адрес, имя, адреса шлюз n Перезагрузите свитч.
пространяться по всей сети (разумно и.т.д.), а затем информация о портах.
включить фильтры шторма). Но при Здесь особое внимание я бы хотел Кстати, по опыту знаю, что всегда
выборе типа VLAN, учтите, что на од- обратить на информацию о режиме надо писать flash:, хотя где-то имена
ном свитче не может быть разных ти- VLAN порта: switchport mode %%%%. файлов принято искать вначале на
пов организации VLAN, из-за особен- flash:, но это не везде срабатывает.
ности протоколов (невозможна синхро- Access - ðåæèì ñòàòè÷åñêîé VLAN; Однажды я попытался обновить опе-
Multi - ìóëüòèVLAN ïîðò;
низация внутреннего формата VLAN Trunk - ðåæèì trunk ìàãèñòðàëè; рационную систему, сгрузил файл
пакетов и trunk магистралей, т.к. в пос- Dynamic - VPMS ðåæèì. ядра и написал boot имя_файла (за-
ледних можно указывать лишь один был flash:) - последствия были печаль-
номер VLAN). Ни в коем случае не со- Следующий параметр switchport по- ными: свитч отказывался найти ядро
единяйте свитчи Catalyst multiVLAN пор- казывает особые параметры для дан- и не загружался, а так как он был в
тами, т.к. это приведёт к тому, что дру- ного типа порта. Например для access труднодоступном месте, то всё было
гой свитч, получивший пакет с это единственный идентификатор ещё печальнее.
multiVLAN порта, не сможет определить VLAN, для multi - список допустимых Вы сразу же приступите к началь-
к какому VLAN он относится и присво- VLAN, разделённых «,» или «—» для ному конфигурационному диалогу.
ит ему VLAN по умолчанию (1 VLAN). указания промежутка VLAN. Таким об- Для выхода из вложенных режимов

№1, октябрь 2002 11


администрирование
конфигурации нажимайте exit. Для
полного выхода наберите два раза
exit. Итак, приступим к настройке
VLAN. Предполагаю, что вы уже на-
ходитесь в режиме конфигурации:

(config)# interface FastEthernet x/x


(íóæíûé âàì ïîðò), çàòåì âû â ðåæèìå
êîíôèãóðàöèè ïîðòà - (config-if)#

Теперь вам доступны любые дос-


тупные изменения конфигурации пор-
та. Наименование порта происходит
по следующей схеме:
n Тип порта (FastEthernet (100 Мб),
Ethernet (10 Мб), Gigabit (1 Гб)).
n Номер модуля (0 для встроенных Для осмысленной настройки VLAN vtp password password-value
портов и далее 1, 2, 3 для допол- можно использовать базу данных VLAN:
нительных модулей). n Установите нужный режим VTP
n Номер порта в модуле. #vlan database
(vlan)#
для данного свитча (клиент/сер-
Для получения списка допустимых (vlan)#show - äëÿ ïîêàçà ñîñòîÿíèÿ вер).
команд, как обычно, можно нажать ?. vlan íà äàííîì ñâèò÷å:
Подробнее остановлюсь на команде vtp server(client)
switchport mode, определяющей ре- Далее можно поменять настройки
жим работы порта для VLAN. Допус- конкретной VLAN: n Возвращаемся в основной режим.
тимые значения access (статический
доступ), multi (мультидоступ) и trunk exit
(vlan)#vlan ID ? - ñïèñîê âîçìîæ-
(режим туннельной магистрали). Для íûõ íàñòðîåê:
конфигурации конкретного режима n Проверяем настройки VTP.
нужно применять: Например, name - настройка имени
для данной vlan. Довольно удобно да- show vtp status
switchport access vlan ID - åäèíñòâåí- вать VLAN осмысленные имена, но нуж-
íàÿ vlan
switchport multi vlan ID, ID, ID èëè но иметь в виду, что если на разных Пример настройки VTP сервера:
switchport multi vlan ID-ID, ID-ID, ID свитчах одни и те же vlan будут иметь
- ñïèñîê äîïóñòèìûõ vlan # vlan database
switchport trunk allowed vlan LISTID разные имена, то это может вызвать
(vlan)# vtp domain Avitek
- äîïóñòèìûå äëÿ ìàãèñòðàëè vlan(ïî óìîë- путаницу в дальнейшем обслуживании. Óñòàíîâêà èìåíè äîìåíà Avitek
÷àíèþ 1-1005) (vlan)# vtp domain Avitek password LAVA
prunning vlan LISTID - ôèëüòðàöèÿ VTP — протокол trunk магистра-
Óñòàíîâêà ïàðîëÿ äëÿ äàííîãî äîìåíà.
øèðîêîâåùàòåëüíîãî òðàôèêà ìåæäó vlan лей — позволяет централизовать уп- (vlan)# vtp server
encapsulation - òèï ïàêåòà(disl èëè Âêëþ÷åíèå ðåæèìà VTP ñåðâåðà.
dot1q - IEEE 802.1q) равление vlan с сервера VTP. Инфор-
(vlan)# exit
native vlan ID - äëÿ trunk ìàãèñòðà- мация от сервера распространяется Íàñòðîéêè ïðèìåíåíû.
ëè òèïà IEEE 802.1q íîìåð vlan, äëÿ êî- Âûõîäèì....
òîðîé íå èçìåíÿåòñÿ ôîðìàò ïàêåòà (ïî к клиентам через trunk магистрали.
óìîë÷àíèþ 1 vlan) В ней содержится полная информа- # show vtp status
ция о VLAN, сконфигурированных на VTP Version : 2
Configuration Revision : 0
Для отмены каких-либо значений сервере. Клиент, в соответствии с Maximum VLANs supported locally : 68
воспользуйтесь командой no этой конфигурацией, выполняет син- Number of existing VLANs : 6
switchport ... и применяйте те же ко- хронизацию своей конфигурации.
манды, что и для включения опций, но Для настройки VTP в режим сер-
применяйте их в обратном порядке, то вера выполните следующее: Семейства
есть: n В основном режиме войдите в раз- коммутаторов Cisco
дел конфигурации VLAN. Catalyst 2900XL/3500XL
no switchport multi vlan ...
no switchport mode multi vlan database Семейство коммутаторов 2-го
уровня Catalyst 2900XL представле-
Не забудьте посмотреть результа- n Введите имя домена VTP(1 — 32 но пятью различными моделями:
ты вашей работы: символов). 1) WS-C2912-XL — содержит 12
универсальных портов 10/100
(config-if)#exit vtp domain domain-name Mbps Ethernet с автоматическим
(config)#exit определением скорости и режи-
#show running-config
#write memory - åñëè íàäî çàïèñàòü n (Необязательно) Установите па- ма передачи;
íàñòðîéêè роль для домена(1-64 символа). 2) WS-C2924-XL — содержит 24

12
администрирование
универсальных порта 10/100 Коммутаторы семейств 2900XL, ко и эффективно конфигурировать
Mbps Ethernet с автоматическим 3500XL, а также Catalyst 1900/2820 коммутаторы для подключения к
определением скорости и режи- могут объединяться в стеки (до 16 каналам требуемого типа.
ма передачи; устройств) при помощи соединений На каждый из коммутаторов мо-
3) WS-C2924M-XL — содержит 24 Fast Ethernet, Fast EtherChannel (аг- жет быть установлено программное
универсальных порта 10/100 грегирование Fast Ethernet по 2 или обеспечение стандартного (Standard
Mbps Ethernet с автоматическим 4 канала), а также Gigabit Ethernet и Edition) и расширенного типа
определением скорости и режи- Gigabit EtherChannel. Максимальное (Enterprise Edition). Расширенная ре-
ма передачи, а также два слота количество портов, которое может дакция дополнительно поддержива-
для установки дополнительных быть установлено в одном стеке рав- ет транкинг (ISL/802.1Q), протокол
интерфейсных карт; но 380. Такой стек является единым TACACS+ для регламентации досту-
4) WS-C2924С-XL — содержит 22 объектом сетевого управления, ко- па к коммутаторам, модифициро-
универсальных порта 10/100 торое может выполняться как при ванную технологию ускоренного вы-
Mbps Ethernet с автоматическим помощи командного языка CLI с кон- бора Spanning Tree (Cisco Uplink
определением скорости и режи- соли или при помощи протокола Fast) и др.
ма передачи, а также два опти- telnet, так и при помощи специали-
ческих порта 100 Mbps Fast зированных систем управления типа Технические
Ethernet; CWSI (Cisco Works for Switched спецификации
5) WS-C2912MF-XL — содержит 12 Internetworks), так и при помощи
оптических портов 100 Mbps WEB-технологии c любой рабочей Производительность:
Ethernet, а также два слота для станции, оснащенной программами 1) 3.2 Gbps — коммутирующих мо-
установки дополнительных ин- просмотра Netscape или Internet дуль (для моделей Catalyst
терфейсных карт. Explorer. 2900XL);
Как и все коммутаторы, входя- 2) 10.0 Gbps — коммутирующих мо-
В состав семейства коммутато- щие в семейство Catalyst устройства дуль (для моделей Catalyst
ров Catalyst 3500XL входит три мо- 2900/3500 обеспечивают построе- 3500XL);
дели: ние виртуальных сетей (в варианте 3) 3.0 million-pps пропускная спо-
1) WS-C3512-XL — содержит 12 программного обеспечения собность (64-х байтовые пакеты,
универсальных портов 10/100 Enterprise), режим безопасности, Catalyst 2900XL);
Mbps Ethernet с автоматическим при котором к коммутатору могут 4) 7.5 million-pps пропускная спо-
определением скорости и режи- быть подключены только станции с собность (64-х байтовые пакеты,
ма передачи, а также два порта указанными MAC-адресами, 4 груп- Catalyst 3500XL);
Gigabit Ethernet; пы RMON, специальный порт для 5) 4-MB разделяемая память;
2) WS-C3524-XL — содержит 24 контроля трафика, проходящего че- 6) 8-MB DRAM and 4 MB Флэш-па-
универсальных порта 10/100 рез группу портов или в заданном мять;
Mbps Ethernet с автоматическим VLANе и др. 7) 2048 MAC-адресов (Catalyst
определением скорости и режи- Модульные модели коммутато- 2900XL);
ма передачи, а также два порта ров (WS-C2924M-XL, WS-C2912FM- 8) 8192 MAC-адресов (Catalyst
Gigabit Ethernet; XL) позволяют устанавливать допол- 3500XL).
3) WS-C3508G-XL — содержит 8 нительные 4-х портовые модули 10/
портов Gigabit Ethernet. 100 Mbps Ethernet (витая пара), 2-х Управление:
и 4-х портовые 100 Mbps Ethernet SNMP Management Information
Комму таторы этих семейс тв (оптика мультимод), однопортовые Base (MIB) II, SNMP MIB extensions,
предназначены для работы в каче- модули Gigabit Ethernet (мультимод, Bridging MIB (RFC 1493).
стве сетевого оборудования рабо- мономод), а также однопортовые
чих групп среднего и малого разме- модули ATM 155 Mbps (витая пара, Поддерживаемые стандарты:
ра и имеют для своего класса очень мультимод, мономод). Мономодовые 1) IEEE 802.3x full duplex;
высокую производительность — до модули для ATM и Gigabit Ethernet 2) IEEE 802.1D Spanning-Tree
3-х миллионов пакетов в секунду, ко- могут быть выполнены в различных Protocol;
торая обеспечивается мощным мо- вариантах дальности, при этом пре- 3) IEEE 802.1Q VLAN;
дулем коммутации архитектурой дельная длина оптических каналов 4) IEEE 802.3z, IEEE 802.3x;
коммутатора, использующей разде- может достигать 70 км. Трансиверы 5) IEEE 802.3u 100BaseTX and
ляемую память, беспрецендентные Gigabit Ethernet (GBIC — Gigabit 100BaseFX specification;
механизмы управления и контроля Interface Converter), устанавливае- 6) IEEE 802.3 10BaseT specification;
за работой устройства и т.п. Высо- мые в комму таторах семейства 7) IEEE 802.3z, IEEE 802.3x
кая производительнос ть серии Catalyst 3500XL, а также в соответ- 1000BaseX specification;
2900XL подтверждена в серии испы- ствующих модулях для коммутато- 8) 1000BaseX (GBIC) —
таний таких тестовых лабораторий ров семейства Catalyst 2900XL явля- 1000BaseSX, 1000BaseLX/LH,
как Mier, ZDnet и др. ются сменными, что позволяет гиб- 1000BaseZX.

№1, октябрь 2002 13


администрирование

УДОБНАЯ
ПОЧТОВАЯ СИСТЕМА ВЯЧЕСЛАВ КАЛОШИН
Итак, задача - завести почтовую систему, в которой вся информация о
пользователях, доменах и прочем лежала бы в базе данных. Зачем это нужно?
Лично для меня это стало актуально после того, как в поддерживаемых мной
системах пользователи начали плодиться как кролики. Заводить на каждого свой
аккаунт, смотреть, чтобы они не пересекались и не пользовались чужими
доменами и так далее. Наконец, мне все надоело и я решил сделать ЭТО.
До сих пор возникает множество воп- на других системах. Все необходимые вирус. Нет такой толпы глюков, на ко-
росов по установке и настройке почто- программы и пакеты вы сможете найти торые бы я периодически не напары-
вой системы на основе Postfix, Cyrus- в окрестностях freshmeat.net. вался, глядя на AVP. Тем более, что он
SASL, MySQL, Courier-IMAP, Dr.Web, Если вы желаете задать мне воп- даже с тривиальной лицензией спокой-
SquirrelMail. рос, задавайте по мылу: но выполняет работу почтового фильт-
Если использовать приведенный multik@multik.ru, но прежде прочтите ра. Правда, с купленной лицензией бу-
ниже опус в качестве банальной инструк- хотя бы PostfixFAQ на www.postfix.org — дет гораздо спокойнее — вирусы даже
ции, у вас должна получиться легко мас- ответы на 90% вопросов, на которые я в архивах не пройдут. Но — если нет
штабируемая и управляемая система, ко- не отвечаю, есть там. Еще одно усло- лицензии, то — нет.
торая без проблем — как со стороны ад- вие — сначала обдумайте и прочтите — SquirrelMail — это просто един-
мина, так и со стороны железа — спо- все сообщения системы в /var/log/ ственная система Web-based почты, ко-
койно будет тянуть по 5-10 тысяч почто- messages и /var/log/maillog — обычно торая не вызывает брезгливой реакции
вых пользователей. При этом нет раз- там есть исчерпывающая информация, при первом, главное, при последующих
ницы: сколько почтовых доменов заве- почему не работает что-либо. взглядах. Плюс, она приемлемо рабо-
дено в системе, как называются пользо- Для нормальных: Просто мне тает с русским языком.
ватели и так далее. Ибо с системными ОЧЕНЬ надоели письма вида: «А чего Теперь устанавливаем SASL. К со-
они никак не коррелируют. оно не работает, когда я сделал все так, жалению, установить его из RPM не по-
Пользователи же получат стандар- как ты написал?». лучится. Все, что я видел до этого, со-
тный набор сервисов: SMTP с аутенти- И последнее — специально для тех, брано либо не так, либо не туда. В об-
фикацией, pop и imap сервисы, доступ кто задается вопросом: «Почему я выб- щем, неработоспособное. Но вы може-
к почте через браузер, плюс, чистую от рал postfix? Ведь есть стандарт-де-фак- те попытать удачу.
вирусов почту. то sendmail. Есть еще exim,qmail и куча
Теперь оговорки. Данный текст на- других почтовых серверов». Отвечаю: # tar zxvf cyrus-sasl-1.5.27.tar.gz
писан в расчете на тех, кто уже пони- — Sendmail я вынужден сразу ски- # cd cyrus-sasl-1.5.27.
мает механизмы, происходящие внут- нуть со счетов. То, что описывается
ри Linux. Если вы неделю как постави- ниже, sendmail не способен выполнить. Заберите патч для поддержки
ли Linux и желаете с помощью этого до- Или способен, но с очень большими уси- MySQL и LDAP отсюда: http://
кумента поставить свой hotmail.com, то лиями администратора системы. Я, по- www.surf.org.uk/downloads/sasl-1.5.27-
я ни за что, как обычно, не отвечаю. Все чему-то органически не могу смотреть ldap-ssl-filter-mysql-patch4.tgz.
логи и прочее были взяты с моей рабо- на творение djb. Ну а exim я просто не Распакуйте полученный архив и по-
чей системы. Я ничего не выдумывал и видел. ложите sasl-ldap+mysql.patch в корень
не придумывал — это все работает ре- — Courier-IMAP выбран по другой дерева исходников SASL.
ально. причине: где-то с полгода назад он ока-
В качестве базовой системы исполь- зался единственным IMAP сервером, # patch -b -p1 < sasl-ldap+mysql.patch
# autoheader
зовался RedHat 7.2. Но аналогично по- который смог собраться на моей маши- # autoconf
строенные системы работают и на не и работать под нагрузкой, не требуя # automake -i.
RedHat6.2, и ASPLinux 7.2. Поэтому не к себе внимания.
вижу причин, почему бы им не работать — Dr.WEB — просто хороший анти- Вот и все, патч установлен. Да-

14
администрирование
вайте сконфигурируем SASL. # tar zxvf postfix-1.1.11.tar.gz setgid_group: [postdrop]
# cd postfix-1.1.11. manpage_directory: [/usr/local/man]
# ./configure —with-mysql=/usr/ sample_directory: [/etc/postfix]
include/mysql —enable-login. readme_directory: [no]
Следующие строчки — это одна
Соберем и установим его: команда. Ее следует вводить в один Ура. Postfix встал. Теперь наша за-
прием. дача его отконфигурировать.
# make
# make install
# ln -s /usr/local/lib/sasl /usr/lib/ # make -f Makefile.init makefiles # cd /etc/postfix/
sasl # mcedit main.cf
«CCARGS=-DHAS_MYSQL -DUSE_SASL_AUTH
# echo /usr/lib/sasl >> /etc/ -I/usr/include/mysql
ld.so.conf -I/usr/local/include Весто mcedit может быть vi, emacs
# ldconfig -L /usr/local/lib -lsasl
# cd .. -lmysqlclient» или любой другой предпочитаемый
вами текстовый редактор — это не-
Устанавливаем методы аутенти- Собираем и устанавливаем: критично.
фикации: Редактируем главный файл кон-
# make фигурации postfix, обращая внимание
# cat > /usr/local/lib/sasl/smtpd.conf # adduser postfix
pwcheck_method: mysql # groupadd postdrop на следующие строчки:
mysql_user: postfix # make install
mysql_passwd: postfix
mysql_host: localhost broken_sasl_auth_clients = yes
mysql_database: mail smtpd_sasl_auth_enable = yes
Вот ответы, которые я дал инстал- transport_maps = mysql:/etc/postfix/
mysql_table: aliases
mysql_uidcol: alias лятору: transport.cf
mysql_pwdcol: password virtual_mailbox_base = /
^D virtual_uid_maps = mysql:/etc/postfix/
install _ root: [/] ids.cf
tempdir: [/usr/src/post/postfix- virtual_gid_maps = mysql:/etc/postfix/
Теперь пришла очередь postfix. 1.1.11] /tmp gids.cf
Здесь ситуация та же. В принципе по config_directory: [/etc/postfix] virtual_mailbox_maps = mysql:/etc/
daemon_directory: [/usr/libexec/ postfix/aliases.cf
сети бродит много rpm, которые со- postfix] virtual_maps = mysql:/etc/postfix/
держат в себе откомпилированный command_directory: [/usr/sbin] remote_aliases.cf
queue_directory: [/var/spool/postfix] relay_domains = $transport_maps
postfix с поддержкой MySQL. Я пред- sendmail_path: [/usr/sbin/sendmail] smtpd_recipient_restrictions =
почитаю собрать его сам, и быть newaliases_path: [/usr/bin/newaliases] p e r m i t _ m y n e t w o r k s ,
mailq_path: [/usr/bin/mailq] permit_sasl_authenticated,check_relay_domains
«чуть-чуть» уверенным в том, что я mail_owner: [postfix] disable_vrfy_command = yes
знаю, что собрал.

Установка и настройка MySQL права пользователю root без пароля и позволяет де-
из исходных текстов лать все с базами test и test_*, кроме раздачи при-
вилегий);
Для установки MySQL, необходимо: n chown -R root:mysql /usr/local/mysql (как root);
n иметь gcc выше, чем 2.8.1 (egcc 1.0.2), рекомендуется n chown -R mysql /usr/local/mysql/data (и отдельную ди-
2.95.2; ректорию для mysql.sockets с правами чтения для
n создать директорию для сборки, распаковать в нее всех; mysql не нужны права на запись для my.cnf);
mysql-3.23.32.tar.gz/mysql-3.23.37.tar.gz (взять на n support-files/mysql.server в /etc/rc.d/init.d для автома-
http://download.sourceforge.net/mirrors/mysql/); тического запуска и дать ему права на исполнение
n для версии ранее 3.23.34 распаковать туда db- и сделать линк K00mysql из rc0.d и rc6.d на него,
3.2.3h.tar.gz (это специальная версия bdb для MySQL) S99mysql из rc2.d, rc3.d и rc5.d на него;
n создать группу mysql; n скопировать my-medium.cnf в /usr/local/mysql/data/
n создать пользователя mysql (в группе mysql) (зачем ему my.cnf и слегка отредактировать [mysqld]:
bash?); • socket=имя-файла под Unix-socket (и в раздел [client]
n ./configure —prefix=/usr/local/mysql — localstatedir=/ тоже);
usr/local/mysql/data — with-unix-socket-path=путь — • skip-locking (не блокировать доступ к данным от ДРУ-
with-mysqld-user=mysql — disable-large-files — with- ГИХ процессов);
libwrap — without-debug — with-charset=cp1251 — • log-bin #журнал изменений для репликации;
with-extra-charsets=all; • log-slow-queries;
n make (70MB/91MB); • log-update #журнал изменений;
n если upgrade, то остановить mysql, сохранить базы дан- • skip-networking #если не нужен доступ по TCP/IP (а еще
ных и my.cfg (не забыть потом удалить!); лучше использовать ssh + port forward);
n make install (как root)(16 MB, из них 5МБ - тест): • safe-show-database;
• /usr/local/mysql/lib/mysql — эту директорию указывать • skip-show-database.
для libtool, либо занести в /etc/ld.so.conf. n тестовый запуск: /usr/local/mysql/bin/safe_mysqld —
n при первой установке: scripts/mysql_install_db (как user=mysql (как root)(или сразу /etc/rc.d/rc3.d/S99mysql
root — создание таблиц с правами доступа, дает все start);

№1, октябрь 2002 15


администрирование
n /usr/local/mysql/bin/mysqladmin — u root — p password n — enable-static (10% быстрее)[yes];
“пароль” (при запросе пароля нажать Enter); n — with-mit-threads (для linux 2.2 не надо);
n /usr/local/mysql/bin/mysqladmin — u root — h n — with-pthread (для linx 2.2 не надо);
localhost.localdomain -p password “пароль” (надо ли?); n — with-named-thread-libs=где;
n установка интерфейса с Perl: n — with-named-curses-libs=где;
• взять на www.mysql.com модули Data-Dumper, DBI и n — with-named-z-libs=где;
Msql-Mysql-modules; n — enable-thread-safe-client (если клиентская програм-
• каждый распаковать в отдельную директорию (Msql- ма использует потоки);
Mysql-modules последним); n— enable-assembler;
• зайти в нее; n— with-raid;
• perl Makefile.PL (опционально хочет RPC::PlServer, n— with-unix-socket-path=куда-класть-unix-socket;
RPC::PlClient, Storable, Net::Daemon); n— with-tcp-port=порт [3306];
• make; n— with-mysqld-user=имя-пользователя-для-mysqld;
• make test (mysqld должен работать); n— disable-large-files;
• make install (как root). n— with-libwrap;
n тестирование: n— without-debug (15% быстрее);
• зайти в sql-bench; n— without-server;
• ./run-all-tests — user=test (нужны права для записи в n— without-docs;
директорию output, час времени и 200 МБ на диске) n— without-bench;
(connect/disconnect временами грохает mysqld!); n— without-readline (использовать системный readline
• можно удалить sql-bench и mysql-test. вместо встроенного);
n настроить права доступа (как минимум, убрать ано- n — with-charset=кодировка-по-умолчанию (cp1251,
нимный доступ). koi8_ru, latin1, ...);
n — with-extra-charsets=список-дополнительных-кодиро-
Опции ./configure вок (включая - none, complex, all);
n — prefix=куда-устанавливать (множество мелочных оп- n — with-berkeley-db;
ций по установке); n — with-innodb;
n — enable-maintainer-mode [no]; n — with-gemini (Gemini DB).
n — enable-shared (делать разделяемые библиотеки)[yes]; Остальное смотрите на сайте www.mysql.com

Что они означают и каково их дей- # cat > remote_aliases.cf дется разбираться, что не понрави-
ствие, вы можете прочитать в доку- user = postfix лось системе.
password = postfix
ментации по postfix или в FAQ.Теперь dbname = mail Теперь необходимо создать
разбираемся, где и что у нас лежит. table = remote_aliases пользователя и все необходимые таб-
select_field = rcpt
where_field = alias лицы с помощью вызова mysql — p:
# cat > transport.cf hosts = localhost
user = postfix ^D
password = postfix mysql> create database mail;
dbname = mail Обратите внимание на отсутствие Query OK, 1 row affected (0.62 sec)
table = transport mysql> grant insert, select, delete,
select_field = transport лишних пробелов и других невидимых update on mail.* to postfix@localhost
where_field = domain знаков в концах строчек — это важно! identified by «postfix»;
hosts = localhost Query OK, 0 rows affected (0.72 sec)
^D Как, наверное, стало понятно из mysql> use mail;
# cat > ids.cf вышеприведенных файлов, я указал Database changed
user = postfix
password = postfix postfix искать MySQL на localhost, под- mysql> create table transport (domain
dbname = mail ключаться пользователем postfix с па- varchar(255) PRIMARY KEY, transport
table = aliases char(8));
select_field = id ролем postfix, использовать базу дан- Здесь будет храниться информация о
where_field = alias ных mail. Конечно, эти данные всего
hosts = localhost доменах, обслуживаемых postfix.
^D лишь для примера — вы должны или
# cat > gids.cf их изменить или понять чем грозит ис- mysql> create table aliases (id int(6),
user = postfix gid int(6), alias varchar(255) PRIMARY
password = postfix пользование паролей, сходными с ло-
KEY, maildir varchar(255), password
dbname = mail гинами. Понятно, что эти файлы не- varchar(128), infovarchar(128));
table = aliases
select_field = gid зачем читать всем (намек на правиль- Здесь информация о почтовых пользо-
where_field = alias ный chmod). вателях системы.
hosts = localhost
^D. Проверяем, все ли в порядке.
# cat > aliases.cf mysql> create table remote_aliases
user = postfix (alias varchar(255) PRIMARY KEY,
password = postfix # postfix check. rcptvarchar(255));
dbname = mail А здесь информация о почтовых пере-
table = aliases
select_field = maildir Команда должна отработать без адресациях и прочем. Небольшие списки
where_field = alias каких-либо сообщений об ошибках. рассылки тоже можно включать сюда.
hosts = localhost
^D Если все-таки она что-то вывела, при-

16
администрирование
Проверьте, имеет ли пользователь В /var/log/messages должны по- # authlib/authinfo
postfix доступ к MySQL. явиться аналогичные строчки: AUTHENTICATION_MODULES=«authdaemon»
AUTHDAEMONMODULELIST=«authcustom
# mysql -u postfix -p authcram authmysql authuserdb authpam»
Jun 28 16:24:21 multik postfix/ SASL_AUTHENTICATION_MODULES=«CRAM-
smtpd[21863]: connect frommultik.ip- SHA1 CRAM-MD5 PLAIN LOGIN»
Запускаем postfix. tel.int[127.0.0.1]
Jun 28 16:24:23 multik postfix/
# postfix start smtpd[21863]: 252BFEEAE6: Обращаем внимание на наличие
postfix/postfix-script: starting the client=multik.ip-tel.int[127.0.0.1]
Postfix mail system Jun 28 16:24:32 multik postfix/ authmysql. Иначе разбираемся, поче-
cleanup[21919]: 252BFEEAE6:message- му не так.
id=<20020628122423.252BFEEAE6@mail.test.ru>
И в консоли MySQL добавляем до- Jun 28 16:24:32 multik postfix/ Выходим из-под пользователя
мен test.ru. qmgr[21762]: 252BFEEAE6: courier и устанавливаем демонов:
from=<multik@test.ru>,size=340, nrcpt=1
(queue active)
Jun 28 16:24:32 multik postfix/ # exit
mysql> insert into transport values virtual[21921]: 252BFEEAE6: # make install
(“test.ru”,’virtual:’); to=<multik@test.ru>,relay=virtual, # make install_configure
delay=9, status=sent (maildir)
И пользователя multik@test.ru. Об- А теперь отдадим дань конфигури-
ратите внимание на путь к почтовому Если мы посмотрим в почтовый рованию:
каталогу пользователя и на заверша- спул, то увидим, что письмо принято
ющий «/» в конце строки. и дожидается своей очереди. # cd /usr/lib/courier-imap/etc/
# cp authdaemonrc.dist authdaemonrc
mysql> insert into aliases # ls -lR /var/spool/vmail
values(1000,12,’multik@test.ru’,’/var/ /var/spool/vmail:
spool/vmail/test.ru_multik/ Редактируем authdaemonrc — на-
èòîãî 4
’,’testpassword’,’info’); drwx——— 5 1000 mail 4096 ходим строчку:
Èþí 28 16:26 test.ru_multik
Число 1000 я взял из головы — /var/spool/vmail/test.ru_multik: authmodulelist=«authcustom authcram
главное, что бы оно было больше пос- èòîãî 12 authuserdb authmysql authpam»
drwx——— 2 1000 mail 4096
леднего UID в системе. В RedHat Èþí 28 16:26 cur
пользовательские UID начинаются с drwx——— 2 1000 mail 4096 и оставляем от нее только такой вот
Èþí 28 16:26 new
500, поэтому я думаю, что 500 локаль- drwx——— 2 1000 mail 4096 огрызок:
ных пользователей вполне достаточ- Èþí 28 16:26 tmp
/var/spool/vmail/test.ru_multik/cur:
но. А 12 — это GID группы mail на моей èòîãî 0 authmodulelist=«authmysql»
системе.
/var/spool/vmail/test.ru_multik/new:
Проверяем, что у нас получилось. èòîãî 4 Другие строки НЕ ТРОГАЕМ.
Заметьте, мы добавили пользователя -rw——— 1 1000 mail 389 Теперь указываем, где искать
Èþí 28
без затрагивания postfix и других под- 16:261025267217.21935_0.multik.ip- MySQL и информацию о пользовате-
систем — это открывает большой про- tel.int лях:
стор для написания различных управ- /var/spool/vmail/test.ru_multik/tmp:
ляющих почтовой системой про- èòîãî 0 # cat > authmysqlrc
грамм. MYSQL_SERVER localhost
MYSQL_USERNAME postfix
Создаем каталог, где будет хра- Теперь подошла очередь установ- MYSQL_PASSWORD postfix
ниться почта и устанавливаем на него ки IMAP и POP3 демонов. Иначе MYSQL_PORT 3306
MYSQL_DATABASE mail
права. пользователям не через что будет по- MYSQL_USER_TABLE aliases
лучать почту. (Из одного письма: «А MYSQL_CLEAR_PWFIELD password
DEFAULT_DOMAIN test.ru
# mkdir /var/spool/vmail чего у меня sendmail не отдает по pop3 MYSQL_UID_FIELD id
# chown nobody.mail /var/spool/vmail MYSQL_GID_FIELD gid
# chmod 770 /var/spool/vmail почту Outlook’у ?»). MYSQL_LOGIN_FIELD alias
Распаковываем и собираем MYSQL_HOME_FIELD maildir
И проверяем, как у нас работает Courier-IMAP. MYSQL_NAME_FIELD info
MYSQL_MAILDIR_FIELD maildir
прием почты: ^D
# tar zxvf courier-imap-1.5.1.tar.gz
$ telnet localhost 25 # adduser courier
# chown courier.courier courier-imap- Опция DEFAULT_DOMAIN указы-
Trying 127.0.0.1...
Connected to localhost. 1.5.1 вает, что добавлять к логину, если
Escape character is “^]”. # cd courier-imap-1.5.1
# su - courier пользователь пытается ввести логин
220 mail.test.ru ESMTP Postfix
mail from: multik@test.ru # cd /{êóäà ðàñïàêîâûâàëè}/courier- без доменной части. Остальное, я ду-
250 Ok imap-1.5.1
# ./configure маю, понятно из названия и описаний.
rcpt to: multik@test.ru
250 Ok # make И запускаем pop3.
data
354 End data with <CR><LF>.<CR><LF>
hello Тут останавливаемся и проверяем, /usr/lib/courier-imap/libexec/
. что у нас есть из демонов, которые бу- pop3d.rc start
250 Ok: queued as 252BFEEAE6
дут проверять почту:

№1, октябрь 2002 17


администрирование
Должен запуститься и не ругаться жет только находить вирусы, но не надо. Проверьте, все ли на месте.
ни на что. Проверяем: лечить их, плюс он не смотрит внутрь Запрещаем всяким лазить куда не
архивов. Но для функциональности следует:
# telnet localhost 110 почтового шлюза этого вполне хватит.
Trying 127.0.0.1... cd /var/drweb
Connected to localhost.
Но лучше купите лицензию — появит- chown -R drweb.drweb *
Escape character is “^]”. ся возможность проверять и лечить
+OK Hello there. пользовательские файлы. И запускаем демона Dr.WEB:
user multik@test.ru
+OK Password required. Тщательно прочитываем /opt/
pass testpassword drweb/doc/postfix/readme.rus. /etc/init.d/drwebd start
+OK logged in. Starting Dr. Web daemon...Key file: /
list+ И добавляем Dr.WEB в качестве opt/drweb/drwebd.key
OK POP3 clients that break here, they фильтра к postfix: Registration info:
violate STD53. 0100003943
1 400 Evaluation Key (ID Anti-Virus Lab.
. Ltd, St.Petersburg)
quit # adduser drweb
# mkdir /var/spool/drweb This is an EVALUATION version with
+OK Bye-bye. limited functionality!
Connection closed by foreign host. # chown drweb.drweb /var/spool/drweb
# chmod 770 /var/spool/drweb To get your registration key, call
regional dealer.Loading /var/drweb/
bases/drwebase.vdb - Ok, virus records:
Как видите, система нас пустила. Редактируем /etc/postfix/master.cf, 29405
Daemon is installed, TCP socket created
Наше отправленное письмо лежит и как указано в документации к Dr.Web: on port 3000
дожидается нас.
В maillog должны быть записи по- smtp inet n - n - 50 smtpd - Работоспособность проверяем
ocontent_filter=filter: dummy
хожие на эти: простым запуском /opt/drweb/drweb-
и добавляем в конец следующее: postfix. Он должен запуститься без
Jun 28 17:27:17 multik pop3d: LOGIN, какого-либо писка и висеть, томитель-
u s e r = m u l t i k @ t e s t . r u ,
ip=[::ffff:127.0.0.1] filter unix - n - n - pipe flags=R но выжидая и занимая консоль. А в
Jun 28 17:27:24 multik pop3d: LOGOUT, user=drweb argv=/opt/drweb/drweb логах должно появиться следующее:
user=multik@test.ru,ip=[::ffff:127.0.0.1], -postfix
top=0, retr=0 -f ${sender} — ${recipient}
Jun 29 13:41:08 multik drweb-postfix:
Радуемся — базовая функциональ- Затем тщательно читаем и редак- load configuration from/etc/drweb/
drweb_postfix.conf
ность достигнута. Можно смело запус- тируем /etc/drweb/drweb_postfix.conf. Jun 29 13:41:08 multik drweb-postfix:
кать imap сервер аналогично pop, раз- Лично я изменил следующие пара- Actions: infected=Q, suspicious=Q,
skip=P,mailbomb=P, scanning_error=T,
давать пользователей и совершать метры: processing_error=R, empty_from=C,
другие необходимые телодвижения. spam_filter=P
Jun 29 13:41:08 multik drweb-postfix:
Но лично мне этого мало. Я не хочу SkipObject = pass dwlib:read_conf(/etc/drweb/
видеть вирусы в своем почтовом ящи- drweb_postfix.conf): successfully loaded
ß õî÷ó ïðîïóñêàòü òå îáüåêòû, êîòîðûå Jun 29 13:41:08 multik drweb-postfix:
ке и в ящиках своих работников и кли- drweb íå ïåðåâàðèâàåò. dwlib: startup: set timeout for
ентов. Ставим Dr.Web. wholesession to 60000 milliseconds (-1
MailbombObject = pass means infinite)
Забираем с drweb.ru файлы: Jun 29 13:41:08 multik drweb-postfix:
Îäèí èç äîìåíîâ, êîòîðûé îáñëóæèâà- drweb-pipe: [2250] started ...
åòñÿ ó ìåíÿ, ïðèíàäëåæèò äèçàéíåðàì è
drweb-4.28.1-linux.tgz ïðî÷åìó õóäîæåñòâåííîìó ëþäó. Îíè î÷åíü
drweb-postfix-4.28.4-linux.tgz îáîæàþò êèäàòüñÿ äðóã â äðóãà àðõèâàìè
ñ êàðòèíêàìè, êîòîðûå ñæàòû î÷åíü ñèëü- У вас так? Значит все работает.
íî. DrWeb îáû÷íî ñ÷èòàåò òàêèå ïèñüìà Перезапускайте postfix и Dr.WEB вста-
çà ìýéëáîìáû.
И распаковываем их. Хотя могли нет на стражу вашей почты. Можете
бы взять rpm файлы — их содержа- AdminMail = root@test.ru проверить, послав какой-нибудь ви-
Êòî òóò ó íàñ àäìèíèñòðàòîð.
ние абсолютно идентично. рус. Вы получите лишь уведомление
Проверяем, то ли мы распаковали FilterMail = DrWeb-DAEMON@ip-tel.ru о том, что вы посылали вирус.
и туда ли. È îò êîãî áóäåò ïðèõîäèòü ïî÷òà ñ Но просто защиты мне мало. Мне
ðóãàíüþ è ïðî÷èìè ñîîáùåíèÿìè. необходима еще и свежая защита. А
# cd /opt/drweb Äàëåå âåçäå: для свежей защиты необходимо об-
# ./drweb новлять базы данных о вирусах. Для
Key file: /opt/drweb/drweb.key SenderNotify = no
Registration info: Íå íàäî èçâåùàòü ïîñûëàòåëÿ ïèñåì - â этого у Dr.Web есть обновлялка, на-
0100003942 90% ñëó÷àåâ ýòî áåçïîëåçíî è ëèøü çàáè- писанная на perl. Для нее нужен мо-
Evaluation Key (ID Anti-Virus Lab. âàåò ïî÷òîâûå êàíàëû. Åñëè îòïðàâèòåëü
Ltd, St.Petersburg) èçâåñòåí àäðåñàòó, òî îí ñàì íàïèøåò дуль String::CRC32. Делающие все
This is an EVALUATION version with åìó ãíåâíîå ïèñüìî. правильно могут вспомнить что напи-
limited functionality!
To get your registration key, call сано в man CPAN и с помощью install
regional dealer. Перемещаем /etc/rc.d/drwebd в /etc/ установить этот модуль. Мне оказа-
Loading /var/drweb/bases/drwebase.vdb
- Ok, virus records: 29405 init.d/drwebd и с помощью ntsysv или лось проще и быстрее сделать все
chkconfig, включаем автостарт Dr.WEB вручную:
Как видите, Dr.WEB работает в ог- при запуске системы. Тем, кто ставил Файл я взял с http://www.cpan.org/
раниченном режиме. То есть он мо- Dr.WEB через rpm, этого делать не modules/by-module/String/String-

18
администрирование
CRC32-1.2.tar.gz, и установил: ся не буду. Не забудьте — для $ telnet localhost 25
SquirellMail необходим запущенный Trying 127.0.0.1...
# tar zxvf String-CRC32-1.2.tar.gz Connected to localhost.
# cd String-CRC32-1.2 imap демон — так что не оплошайте. Escape character is “^]”.
# perl Makefile.PL Для завершения нашей эпопеи ос- 220 mail.test.ru ESMTP Postfix
# make ehlo multik
# make test талось всего два шага: 250-mail.test.ru
# make install 250-PIPELINING
250-SIZE 10240000
n Проверьте, все ли заработает при 250-ETRN
Проверяем: запуске системы. Если есть воз- 250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
можность — перезагрузитесь и 250-XVERP250 8BITMIMEÊ
# cd /opt/drweb/update проверьте, запустился ли Dr.WEB,
# ./update.pl postfix, courier pop3 и/или imap c
mysql. Отрабатывает ли update’р К вашему большому сожалению
update.pl должен сходить в инет на новые обновления и так далее. вы не увидите строчек:
сайт Dr.WEB, забрать все обновления Просмотрите все конфигурацион-
и перезапустить drwebd, если он есть. ные файлы еще раз — не остави- 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-
MD5
В логах после запуска вы должны ли ли вы где-нибудь ляпов или «со- 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-
увидеть следующее: плей»? Для самостоятельной ра- MD5
боты можете посмотреть на анти-
Key file: /opt/drweb/drweb.key спамерные возможности drweb. Либо вы увидите эти строчки, но
Registration info:
0100003942 n Проверьте, прикрыт ли MySQL и почему-то нормальные почтовые кли-
Evaluation Key (ID Anti-Virus Lab. Dr.WEB от посторонних людей. енты (к примеру, TheBat или stuphead)
Ltd, St.Petersburg)
This is an EVALUATION version with не смогут авторизоваться для отправ-
limited functionality! В общем все. Можете откинуться ки почты. Только для отправки! В чем
To get your registration key, call
regional dealer. на спинку кресла и наблюдать, как ра- же дело ?
Loading /var/drweb/bases/drwtoday.vdb ботает почта. Дело в одной маленькой библио-
- Ok, virus records: 173
Loading /var/drweb/bases/drw42807.vdb Но для меня это еще не все. Раз теке, называемой Cyrus-SASL. К со-
- Ok, virus records: 33Loading /var/ все равно для пользователей стоит жалению, ее писали люди, которые
drweb/bases/drw42806.vdb - Ok, virus
records: 57 Apache с PHP, то я написал простень- писали ее неправильно. Метод sasldb
Loading /var/drweb/bases/drw42805.vdb кую WWW — утилитку для управле- в ней важнее всех.
- Ok, virus records: 133
Loading /var/drweb/bases/drw42804.vdb ния почтовыми пользователями. Для Внимание, если /etc/sasldb не пус-
- Ok, virus records: 123 ее работы необходимо создать в той (смотреть вывод sasldblistusers),
Loading /var/drweb/bases/drw42803.vdb
- Ok, virus records: 73 MySQL такую таблицу: то в выводе postfix появляются строч-
Loading /var/drweb/bases/drw42802.vdb ки про DIGEST-MD5 и CRAM-MD5.
- Ok, virus records: 143 mysql> create table admins (login
Loading /var/drweb/bases/drw42801.vdb Если пустой -то не появляются. Если
varchar(20) NOT NULL, password
- Ok, virus records: 76 varchar(20),rights int(6)); в sasldb есть хоть одна запись, то
Loading /var/drweb/bases/drwebase.vdb
- Ok, virus records: 29405 аутентификация с участием методов
И руками занести туда значения LOGIN, DIGEST-MD5 и CRAM-MD5
Видите, появились свежие обнов- вроде “admin”, ’password’,0. идет через sasldb, НЕВЗИРАЯ на то,
ления для drweb с новыми вирусами. Эта таблица у меня используется что написано после pwcheck_method.
Теперь со спокойной душой запихи- в других внутрикорпоративных серви- Поэтому, если у вас есть активно миг-
ваем вызов update.pl в crontab. У меня сах, поэтому желающие могут залезть рирующие пользователи с нормаль-
он вызывается каждую ночь. Одно в код и все исправить. ными почтовыми клиентами (Outlook
НО: Необходимо периодически вруч- По адресу www.samag.ru/2002/01/ и Mozilla — не подходят — они оба ис-
ную отслеживать выход новых версий multik/ лежат 3 файла auth.php, пользуют только метод PLAIN), то для
drweb. Потому что как только выйдет global.php, index.php. Просто положи- отправки почты с таких клиентов их
новый DrWeb, ваш автоматически пе- те эти три файла в один каталог, дос- надо заводить вручную, используя ко-
рестанет получать новые вирусные тупный вам. Поправьте значения в манду аналогичную этой:
дополнения. global.php. Все, можете вызывать и
И последний шаг — установка пользоваться. Логин и пароль — те, saslpasswd -c -u `postconf -h
www-почты: которые вы вручную добавили в таб- myhostname` username
Я взял последнюю версию лицу admins.
SquirellMail с http://www.squirrelmail.org/ Ну а дальше, я думаю, вы пойме- Вот такой вот SASL. Правда, обе-
и установил согласно прилагающимся те. Утилитка писалась «на коленке», щают, что в v2 будет все по честному,
инструкциям. Для ее работы необхо- поэтому я вполне понимаю, что мож- но пока v2 не выбралась из бета-со-
дим настроенный Apache c PHP. Как но написать лучше и красивее. Пи- стояния, да и postfix в стабильных вер-
это делать я уже писал несколько раз. шите. Удачи ! сиях не поддерживает Cyrus-SASL v2.
Да и в сети куча документов, посвя- А тем, кто до сюда дочитал — ма- Courier-IMAP использует SASL по
щенным этому вопросу. Установка ленький бонус. другому, поэтому с ним все в поряд-
простая, поэтому я тут останавливать- Если вы поглядите на это: ке. Вот теперь точно все. Удачи!

№1, октябрь 2002 19


администрирование

МИГРАЦИЯ
С WINDOWS НА LINUX

Сначала давайте выясним


для себя,
зачем нам это нужно?
Если ваш сервер работает
под управлением Windows
и никаких нареканий
к стабильности и скорости
его работы нет, не стоит
ничего менять.
Как известно, лучшее - враг
хорошего.
К сожалению, везет далеко
ДМИТРИЙ ГАЛЫШЕВ не всем.

Данная статья предназначена в пер- Linux по нескольким критериям, не ную техническую поддержку заре-
вую очередь для тех, кто, считая, что претендуя на детальное сравнение, гистрированных пользователей,
«качественный продукт не может быть которому посвящено уже много ста- но не несет ответственности за
бесплатным», доверил управление тей. потери данных, произошедшие
своими серверами дорогой операци- из-за ошибок в коде ОС, и полу-
онной системе и вынужден периоди- Windows NT: ченный ущерб в случае чего при-
чески смотреть на «синий экран смер- дется доказывать в суде.
ти», терять время на перезагрузки и n Является коммерческим ПО, за ис- n В комплект поставки входит опе-
иногда на восстановление самой ОС. пользование каждой его копии не- рационная система, утилиты ад-
Приведем сравнение операцион- обходимо платить. министрирования, набор простых
ных систем Microsoft Windows NT и n Производитель обещает бесплат- игр, www- и ftp-сервер IIS.

20
администрирование
n Нужные Вам утилиты, не входя- или нетерпеливы, эта ОС не для Вас. ляет делать резервные копии уда-
щие в состав OС, приобретаются Не испугались? По прежнему полны ленных ресурсов.
отдельно. решимости? Ну что же, начнем осва- n Утилиты командной строки, под-
n Документация к системе приобре- ивать новую ось. В любом случае до- держивающие некоторые возмож-
тается отдельно. говоримся, что все работающие сер- ности администрирования NT, ко-
n Выполнение отдельных задач кли- вера остаются работать до тех пор, торые могут быть использованы в
ентом на сервере невозможно. пока к ним обращается хотя бы один Samba, NT workstation и NT server.
пользователь.
Linux: Для начала рассмотрим задачу ос- Вы можете получить больше инфор-
вобождения сервера под управлени- мации на нашем сайте: http://samba.org/
n Является свободно распространя- ем Windows NT от обязанностей ос- samba».
емым ПО, и, имея одну копию, Вы новного контроллера домена (PDC). Если Samba еще не установлена,
можете использовать ее на любом Основное преимущество доменной обращаемся к компакт-диску с дист-
количестве компьютеров и тира- структуры — выполнение скриптов, рибутивом и устанавливаем все па-
жировать без ограничений. определенных администратором для кеты, содержащие в имени слово
n Ядро ОС и поставляемые с ним выполнения при входе и выходе «samba». Типичные настройки обыч-
программы Вы используете на свой пользователя домена. Также, с помо- но описаны в файле /etc/samba/
страх и риск без каких-либо гаран- щью Samba, мы возьмем на себя за- smb.conf-sample и подходят большин-
тий. Это не значит, что техническая дачи файл-сервера — предоставле- ству пользователей.
поддержка отсутствует — вы може- ние коллективного доступа к своим Рассмотрим его поподробнее.
те обратиться за ней к автору про- файлам и папкам. Файл состоит из секций, которые оп-
граммы. Применить здесь понятие «к ре- ределяются именем в квадратных
n В дистрибутив входит комплект ин- сурсам» было бы не совсем коррект- скобках.
тернет-утилит как серверной, так но, так как к ресурсам относятся вре- В секции [global] объявляются об-
и клиентской направленности, ин- мя процессора, память и так далее. щие переменные, влияющие на рабо-
струменты для разработки сетево- Удаленные пользователи могут ис- ту демонов.
го взаимодействия с другими ОС, пользовать и их, но Windows NT та- В секции [homes] предоставляет-
игры и многое другое. кой возможности не предусматрива- ся возможность монтирования уда-
n Если какого-то нужного Вам паке- ет. ленными пользователями своих лич-
та нет в дистрибутиве, Вы можете Системы Microsoft Windows 3.11, 9х ных папок, находящихся на сервере.
бесплатно получить его с сайта и NT используют для предоставления Секция [printers] открывает дос-
разработчика. совместного доступа к файлам, пап- туп ко всем установленным в систе-
n В состав каждого дистрибутива кам и принтерам протокол Server ме принтерам.
входит комплект документации по Message Block (SMB). В Linux за рабо- Переименуем файл /etc/samba/
ОС и всем установленным про- ту с SMB отвечает пакет Samba. О нем smb.conf-sample в smb.conf и начнем
граммам. его создатели пишут следующее: его править под себя.
n Пользователи могут не только вы- «Вот короткий список того, что дела- Полезными могут оказаться «под-
полнять задачи на сервере, но и ет Samba. Для многих сетей можно становочные переменные», которые
планировать их выполнение в свое сказать коротко: «Samba предостав- при чтении заменяются актуальными
отсутствие. ляет полноценную замену серверам для сеанса значениями. Например,
Windows NT, Warp, NFS или Netware». «%u» будет заменено именем подклю-
Если Вы хотите узнать больше, чающегося пользователя, а «%L» —
обратитесь к статье «Microsoft n SMB сервер для предоставления NetBIOS-именем сервера. Полный спи-
Windows NT Server 4.0 против UNIX» доступа к файлам и принтерам, сок этих переменных Вы найдете в
Джона Кирха, сетевого консультанта клиентам с рабочих станций man-руководстве к smb.conf.
и сертифицированного специалиста Windows 95, Warp Server и подоб- Вот некоторые глобальные пара-
Microsoft (Windows NT) на http:// ным. метры, на которые стоит обратить
www.linux.org.ru/books/unix-nt.html. n Сервер имен NetBIOS, который ко внимание:
Статья датирована 1998 годом, и с всему прочему поддерживает спи- workgroup — задает имя рабочей
тех пор многое изменилось, но рас- сок доступных компьютеров в групппы или домена, в который вхо-
становка сил осталась прежней. сети. Samba может выступать в дит компьютер.
Управление Linux основано не на роли главного обозревателя Ва- hosts allow — список компьютеров
«интуитивно понятном интерфейсе», шей сети. и сетей, которым разрешен доступ,
а на правке различных конфигураци- n SMB клиент позволяет пользовать- разделенный пробелами.
онных файлов после чтения докумен- ся ресурсами других компьютеров guest account — имя пользовате-
тации, которая написана в основном сети (как файлами, так и принте- ля с правами гостя. По умолчанию —
на английском языке, хотя переводов рами) из Unix, Netware и других это nobody.
с каждым годом становится больше ОС. Пользователь должен быть заре-
и больше. Если Вы нелюбознательны n Расширение клиента «tar» позво- гистрирован в системе. Можно ука-

№1, октябрь 2002 21


администрирование
зать с помощью параметра username domain master — определяет, пре- кация файлов ресурса. Значение па-
map файл, содержащий толкование тендовать ли на роль основного обо- раметра writeable для этих пользова-
передаваемых клиентом имен для зревателя домена. Если сеть состоит телей не учитывается.
сервера. Файл будет читаться пост- из нескольких сегментов, то локаль- create mask — стандартная UNIX-
рочно; в строке слева от знака «=» ные обозреватели каждого сегмента маска, определяющая права доступа
должно стоять имя, воспринимаемое будут синхронизировать свои списки к вновь создаваемому файлу.
сервером, справа — соответствую- с ним. Directory mask определяет права дос-
щее ему имя, которое может переда- preferred master — сообщает де- тупа к вновь создаваемому каталогу.
вать клиент. мону nmbd, что он является приори- Полный список опций, употребляе-
Например, если в файле содер- тетным обозревателем рабочей груп- мых в файле, находится в man-руко-
жится строка nobody = guest pcguest пы или домена, и это вынуждает его водстве к smb.conf. После редактиро-
пользователям guest и pcguest будет устраивать выборы при запуске, а в вания рекомендуется проверить его на
предоставлен доступ как пользовате- случае проигрыша — каждые 6 минут. предмет ошибок и противоречий с по-
лю nobody. wins support — включает поддер- мощью программы testparm. Если
security — один из важных пара- жку Samba-сервером WINS — ошибки есть, она сообщит о них и, ско-
метров конфигурации — модель Windows Internet Naming Service, служ- рее всего, подскажет причину их воз-
аутентификации. бы определения адресов, сопоставля- никновения.
Возможны варианты: ющей имена компьютеров в сети Сначала настраиваем Samba как
security = user — для получения Microsoft с адресами IP. обычного сетевого клиента — она не
списка ресурсов пользователь дол- wins server — указывает IP-адрес должна претендовать ни на роль ло-
жен быть опознан сервером. Будьте WINS-сервера сети. Этот параметр не кального обозревателя, ни тем бо-
внимательны: по умолчанию ресурсы, может указывать на свой IP, т.е. ис- лее — контроллера домена. Если та-
открытые гостям, не будут доступны пользуется только в случае, когда ковые уже есть, то присваиваем
тем, кого сервер «не знает». wins support = no. значение «no» параметрам local
security = share — получение спис- domain logons — позволяет master, domain master и preferred
ка ресурсов не требует аутентифика- Samba выступать в роли контролле- master.
ции. Клиент посылает запрос при под- ра домена. Если разграничения доступа еще
ключении к конкретному ресурсу. logon script — задает имя скрип- нет, самое время подумать о нем. В
security = server — с точки зре- та, выполняемого при входе пользо- большинстве случаев несложно опре-
ния клиента, эта модель ничем не от- вателя в домен. делить группы пользователей, рабо-
личается от первой. Разница в том, logon path — указывает на ката- тающие с определенными группами
что полученные имя/пароль Samba по- лог, где будут храниться профили файлов. Например, бухгалтерия рабо-
пытается проверить на доверенном пользователей. тает с базами бухгалтерской програм-
сервере (например НТ). Если попыт- Из параметров, применимых к кон- мы и не интересуется проектами про-
ка провалится, для этого соединения кретным разделяемым ресурсам, сле- граммистов.
модель сменится на «user». дует отметить: Мне не известен сколько-нибудь
security = domain — отличается от comment — краткое описание ре- реальный способ переноса базы
предыдущей тем, что аутентификация сурса, выводимое большинством кли- пользователей из Windows NT в Linux,
происходит на PDC или BDC. Пароли ентов рядом с его именем. поэтому придется делать это вручную.
должны передаваться в зашифрован- path — реальное расположение Неудобно, но в этом есть свои поло-
ном виде. разделяемого ресурса в файловой си- жительные стороны — найдется не-
encrypt passwords — шифрова- стеме сервера. сколько неиспользуемых учетных за-
ние паролей. По умолчанию этот па- browseable — определяет, будет писей, а также можно будет перерас-
раметр выставлен в «no» и пароли пе- ли выдаваться имя ресурса на запрос пределить пользователей по группам.
редаются по сети открытым текстом. о доступных ресурсах сервера. На момент написания статьи
interfaces — привязка к конкрет- writeable — определяет, возмож- Samba (версии 2.2.5) имеет собствен-
ным интерфейсам. Можно указать на ли запись или модификация фай- ную базу пользователей, но не под-
имя интерфейса со звездочкой вмес- лов ресурса пользователем. держивает пользователей, не имею-
то номера, тогда привязка будет ко guest ok — разрешает доступ к ре- щих учетной записи в системе. Раз-
всем найденным интерфейсам этого сурсу всем пользователям сервера. работчики обещают со временем пе-
типа. Также можно указать просто IP- (См. глобальный параметр security). рейти на независимую базу пользова-
адрес или пару IP/маска сети. valid users — список пользовате- телей, но пока придется пользовате-
local master — определяет, пре- лей, которым разрешен доступ к ре- лей заводить сначала в системе.
тендовать ли на роль локального обо- сурсу. Как и в любой список пользо- Пользователям, у которых не будет
зревателя сети. вателей в файле smb.conf можно до- доступа к терминалу сервера (в иде-
os level — так называемый «уро- бавлять и группы, предварив имя але всем, кроме администратора) на-
вень» ОС. Для справки: у НТ-сервера группы символом «@». значаем /bin/false в качестве команд-
этот параметр равен 32, у PDC — 64. write list — список пользователей, ного интерпретатора и блокируем
Максимальное значение — 255. которым разрешена запись и модифи- учетную запись, после чего пропи-

22
администрирование
сываем их в базе Samba командой Если в Вашей сети используются ляется NetBIOS-имя рабочей станции
smbpasswd дважды: с ключом «-a» — домены, проверьте smb.conf сервера: домена.
для занесения в базу, с ключом «-e» — Второй метод рекомендован раз-
для снятия блокировки с записи. n Шифрование паролей должно работчиками Samba как более безо-
Хорошо, если новая операционная быть включено. пасный и требует лишь добавления
система ставится на специально куп- в секцию [global] строки:
ленный сервер, еще лучше, если сер- [global]
...
вер куплен с предустановленной нуж- domain master = yes add user script = /usr/bin/
ной нам ОС. Но как быть, если сер- ... adduserscript %u
вер один и нельзя его трогать до тех
пор, пока все пользователи не смогут n Сервер должен поддерживать Это подразумевает, что в ката-
получить гарантированный доступ к вход пользователей в домен и пре- логе /usr/bin сущес твует скрипт
своим файлам? Придется использо- доставлять разделяемый ресурс adduserscript примерно следующего
вать любой доступный компьютер как NETLOGON. содержания:
«временный сервер». На него ставим
тот же дистрибутив, что предполага- [global] #!/bin/sh
... /usr/sbin/useradd -d /dev/null -g 100
ем ставить на основной сервер, и по- domain logons = yes -s /bin/false -m $1
степенно перенесем все ресурсы, пре- ...
[netlogon]
доставляемые старым сервером. path = /usr/local/samba/lib/ После этого с добавляемой рабо-
Какая машина справится с этой netlogon чей станции заходим в домен как
read only = yes
работой зависит от предполагаемой write list = ntadmin привелигированный пользователь
нагрузки. Достоверно известно, что (root, если не указан другой парамет-
Celeron 700/196Mb RAM/30Gb HDD в n Сервер должен быть главным обо- ром admin user), и создаем учетную
состоянии обслуживать 15 клиентов, зревателем домена. запись рабочей станции домена.
интенсивно работающих с большим Пока это не сделано, попытки зайти
количеством файлов при средней на- [global] как обычный пользователь провалят-
грузке ~10%. ... ся, даже если пользователь суще-
encrypt passwords = yes
Разделяемые SMB ресурсы делим на: ... ствует.
После того, как вся нагрузка бу-
n Общедоступные (папка с драйве- Если все в порядке, можно добав- дет перенесена на «временный» сер-
рами и дистрибутивами программ, лять рабочие станции в домен. вер, а старый сервер — выключен,
«помойка»). Рабочие станции, функционирую- подождем денёк-другой возникнове-
n Доступные группе (папки с данны- щие под управлением Windows NT/ ния проблем и только послеэтого на-
ми, с которыми работает ограни- 2000, используют так называемые чинаем установку Linux на основной
ченное количество лиц, например «машинные» учетные записи — метод сервер. После установки системы
папка с базами бухгалтерской про- проверки подлинности рабочей стан- конфигурационные файлы и файлы
граммы). ции (не пользователя), чтобы избе- пользователей просто переносятся с
n Домашние (папки привилегиро- жать входа в домен рабочей станции «временного» сервера.
ванных пользователей, доступные с таким же именем NetBIOS и получе- Итак, все готово, начинаем. При-
только им). ния прав пользователя домена. Рабо- кидываем время на переброску всех
чие станции, функционирующие под необходимых файлов со старого сер-
Наиболее часто допускаемая управлением Windows 9x/Me, таких вера на новый и объявляем профи-
ошибка на этапе переноса данных — учетных записей не используют, по- лактику на нужное время плюс 10 ми-
невнимательность к атрибутам вновь этому не могут считаться полноправ- нут на неожиданности (если мы не
перенесенных файлов. Владельцем ными членами домена. допустили оплошностей при подго-
для них лучше всего назначить Существует два метода создания товке, работа возобновится как толь-
пользователя root, а группу-владель- «машинных» учетных записей — руч- ко файлы будут скопированы).
ца — ту, что будет с этими файлами ной и автоматический. Старому серверу меняем NetBIOS-
работать. Ну и не забыть позволить Первый метод подразумевает вы- имя и перезапускаем Samba. Новому
перезаписывать файлы членам груп- полнение вручную трех команд на сер- присваиваем имя старого. Перезапус-
пы-владельца. вере для каждой рабочей станции до- каем Samba.
Необходимо пройтись по пользова- мена: Все. Можем переходить к замене
телям и проверить доступ с их рабо- любимого лакомства для Nimda и
чих станций к новому серверу: чтение/ root# useradd -g 100 -d /dev/null -c CodeRed — Microsoft Information
запись в папку; чтение/запись в фай- <èìÿ_ìàøèíû> -s /bin/false <èìÿ_ìàøè- Server на самый популярный в
íû>$
лы, созданные другими членами груп- root# passwd -l <èìÿ_ìàøèíû>$ Internet www-сервер Apache, прокси-
пы, а также членами групп, имеющих root# smbpasswd -a -m <èìÿ_ìàøèíû> сервер Squid и ftp-сервер proftpd.
смежный доступ в папку; отсутствие Но это уже немного другая исто-
доступа в неразрешенные папки. Вместо <имя_машины> подстав- рия...

№1, октябрь 2002 23


администрирование

ЧТО ТАКОЕ
SAMBA? СЕРГЕЙ ЯРЕМЧУК
GRINDER@UA.FM
администрирование
Я думаю, что утверждение о том, Windows машин? Во-первых, контроль n только для чтения - чтение, запись
что самой популярной операционной доступа, который может быть реали- для владельца;
системой для клиентских машин яв- зован либо на уровне ресурсов (share n архивный - выполнение для вла-
ляется Windows, не вызовет больших level), когда какому-либо ресурсу в дельца;
споров. Хотя с появлением таких про- сети назначается пароль и соответ- n системный - выполнение для груп-
дуктов, как OpenOffice.ru, и активно- ствующие правила использования пы;
му продвижению своих продуктов («только для чтения», например), при- n скрытый - выполнение для группы.
компаниями AltLinux и ASPLinux, по- этом имя пользователя не имеет аб- Вот с проблемами так или иначе
ложение несколько изменилось, но до солютно никакого значения. Либо бо- разобрались. Давайте разберемся
массовости еще дело не дошло. По- лее совершенная и гибкая организа- теперь конкретно с реализацией и на-
этому большинство пользователей ция на уровне пользователя, когда стройкой SAMBA в Linux. Для работы
продолжают набирать документы в для каждого пользователя создается Samba необходимо, чтобы были запу-
Word’e и бродить по Интернету с по- учетная запись, где помимо имени и щены два демона: smbd обеспечива-
мощью Internet Explorer. А вот в каче- пароля содержится вся необходимая ет работу службы печати и разделе-
стве сервера положение последней информация о правах доступа к ре- ния файлов для клиентов Samba, та-
уже не так однозначно, здесь уже иг- сурсу. И прежде чем получить доступ ких как Windows всех мастей; демон
рают роль NetWare от Novell и, конеч- к требуемому ресурсу каждый пользо- nmbd обеспечивает работу службы
но же, Unix. Плюс за последний год ватель проходит аутентификацию, имен NetBIOS, а также может исполь-
пришлось всем потесниться из-за по- после успешного прохождения кото- зоваться для запроса других демонов
явления новых «игроков» на этом рой ему и предоставляется права на служб имен. Для доступа к клиентам
рынке - операционных систем с откры- использование согласно учетным за- используется протокол TCP/IP. Как
тым кодом - Linux, FreeBSD и писям. Во-вторых, необходима эмуля- правило, Samba устанавливается
OpenBSD, которые уже сейчас зани- ция прав доступа, определяемых фай- вместе с дистрибутивом Linux. Как
мают немалый процент рынка серве- ловой системой. Все дело в том, что проверить? Просто дайте команду:
ров. Прежде всего по причине своей у рассматриваемых систем права до-
надежности, устойчивости, совмести- ступа к файлам и каталогам на диске sergej@grinder sergej]$ whereis samba
мости со множеством платформ и бе- организованы по-разному. В Unix тра-
зопасности. Поэтому сейчас систем- диционно существует три категории и вы должны получить что-то вроде
ному администратору приходится ча- пользователей файла - владелец этого:
сто решать вопросы интеграции сер- (owner), группа (group) и остальные
веров под управлением Linux/Unix в (other). Каждому из этих субъектов samba: /usr/sbin/samba /etc/samba /
сеть, где преобладают клиентские usr/share/man/man7/samba.7.gz
могут быть предоставлены права на
машины от Microsoft, особенно в ка- чтение (read), запись(write) и выпол-
честве файл-серверов или серверов нение ( execute). В Windows NT систе- Если нет, то идите на ftp://
печати. ма доступа несколько гибче, доступ f tp.samba.org/pub/samba/samba-
Так как чуда от Microsoft особенно предоставляется нескольким группам latest.tar.gz или практически на любой
ждать не приходится и Windows вряд- или пользователям, причем соответ- сервер с программами для Linux и ка-
ли научится работать с сетевой фай- ствующие права доступа определяют- чайте в виде rpm или исходников. Па-
ловой системой Unix (NFS) стандарт- ся раздельно для каждого субъекта. кет прост в установке, поэтому, что-
ными средствами, то по принципу Поэтому полноценно эмулировать бы не занимать места, будем считать,
горы и Магомета, просто научили Unix средствами SAMBA права доступа что он у вас установлен. Теперь да-
притворяться, будто бы он - Windows заложенные в NTFS, невозможно. А вайте проверим, запущен ли демон:
NT. с клиентами, работающими под уп-
Взаимодействие компьютеров в равлением Windows 9x, дело обстоит [sergej@grinder sergej]$ ps -aux | grep
smbd
сети Windows построено на использо- иначе. Еще со времен дедушки ДОС, root 1122 0.0 0.6 4440 380 ?
вании протокола SMB (Server Message по причине того, что система одно- S 16:36 0:00 smbd -D
Block - блоки серверных сообщений), пользовательская и ни о каких пользо-
который обеспечивает выполнение вателях, а тем более группах, не мог- У меня уже запущен. Если у вас
всех необходимых в этих случаях за- ло быть и речи, для файловой систе- нет, то в Linux Mandrake, например,
дач по открытию и закрытию, чтению мы FAT определено всего четыре ат- чтобы он запускался при старте от-
и записи, поиску файлов, созданию и рибута - только чтение (read only), си- метьте нужный пункт в: DrakConf -
удалению каталогов, постановке за- стемный (system), архивный (archive) стартовые сервисы или control-panel
дания на печать и удалению его отту- и скрытый (hidden). Плюс ко всему в - Servise Configuration в Red Hat, обыч-
да. Все необходимые для этого дей- Windows, в отличие от Unix, имеет осо- но этого бывает достаточно, или за-
ствия реализуются в Unix-подобных бое значение расширение файла, так пускайте вручную: ./etc/rc.d/init.d/smb
операционных системах посредством файлы, предназначенные для выпол- start. Единственный конфигурацион-
использования пакета SAMBA. Что же нения, имеют расширение - exe, com, ный файл Samba называется smb.conf
должен обеспечить SAMBA сервер bat. Соответствия между правами до- и находится в каталоге /етс иногда
для нормальной работы в сети ступа Unix и DOS выражаются так: (в AltLinux, например, в каталоге /etc/

№1, октябрь 2002 25


администрирование
samba). Сервис SAMBA считывает его (до Service Pack 3). Для включения ва- вателя, сделавшего запрос и при на-
каждые 60 секунд. Поэтому измене- рианта использования шифрованного хождении делает его доступным для
ния внесенные в конфигурацию всту- пароля используется опция encrypt пользователя.
пают в силу без перезагрузки, но не password =yes (по умолчанию исполь- Типичное описание раздела:
распространяются на уже установлен- зуются не шифрованные пароли).
ные соединения. Для правильного отображения рус- [homes]
comment = Home Directories # êîììåíòà-
Вот за что я люблю Linux - так это ских имен файлов используются сле- ðèé êîòîðûé âèäåí â îêíå ñâîéñòâ ñåòè
за то, что конфигурационные файлы дующие опции: client code page = 866 browseable = no # îïðåäåëÿåò âûâîäèòü
ëè ðåñóðñ â ñïèñêå ïðîñìîòðà.
являются обычными текстовыми( к и character set = koi8-r. writable = yes # ðàçðåøàåò (no -
тому же хорошо комментированные Опция interfaces = 192.168.0.1/24 - çàïðåùàåò) çàïèñü â äîìàøíþþ äèðåêòîðèþ
create mode = 0750 # ïðàâà äîñòóïà äëÿ
внутри), и для того чтобы задейство- указывает в какой сети (интерфейсе) âíîâü ñîçäàííûõ ôàéëîâ
вать большинство параметров доста- должна работать, программа если directory mode = 0775 # òîæå, íî òîëüêî
äëÿ êàòàëîãîâ
точно только раскомментировать со- сервер подключен сразу к нескольким
ответствующую строчку. Файл сетям, а при установке параметра bind После настройки параметров по
smb.conf - не исключение. Он состоит interfaces only = yes, сервер будет от- умолчанию вы можете создать сете-
из именованных разделов, начинаю- вечать на запросы только из этих се- вые ресурсы, доступ к которым может
щихся из имени раздела, заключен- тей. получить определенные пользователь
ного в квадратные скобки. Внутри hosts allow = 192.168.1. 192.168.2. или группа пользователей. Создает-
каждого раздела находится ряд пара- 127. - определяет клиентов, для ко- ся такой ресурс из уже существующе-
метров в виде key=value. Файл кон- торых разрешен доступ к сервису. го каталога, для этого в файле пишем:
фигурации содержит четыре специ- В секции global возможно исполь-
альных раздела: [global], [homes], зование различных переменных для [public]
comment = Public Stuff
[printers] и отдельные ресурсы более гибкой настройки работы сер- path = /home/samba
(shares). Как следует из названия, вера, после установки соединения public = yes
writable = no
раздел [global] содержит наиболее об- вместо них подставляются реальные printable = no
щие характеристики, которые будут значения. Например, в директиве log write list = administrator, @sales
применяться везде, но которые, впро- file = /var/log/samba/%m.log , параметр
чем, затем можно переопределить в %m помогает определить отдельный Параметр path указывает на ката-
секциях для отдельных ресурсов. лог-файл для каждой клиентской ма- лог, в котором располагается ресурс,
Значения типичных параметров шины. Наиболее употребительные параметр public указывает, может ли
секции global: переменные, используемые в секции пользоваться ресурсом гость, а
global: printable - может ли использоваться
workgroup = èìÿ_ãðóïïû # íàçâàíèå данный ресурс для печати. Параметр
ðàáî÷åé ãðóïïû â ñåòè Windows
netbios name = èìÿ ñåðâåðà â ñåòè %a - àðõèòåêòóðà ÎÑ íà êëèåíòñêîé ìà- write list позволяет задать пользова-
server string = êîììåíòàðèé, êîòîðûé øèíå (âîçìîæíûå çíà÷åíèÿ Win95, Win NT,
âèäåí â îêíå ñâîéñòâ ïðîñìîòðà ñåòè UNKNOWN è ò.ä.) телей, которым разрешена запись в
guest ok = yes # ðàçðåøåíèå ãîñòåâîãî %m - NetBIOS-èìÿ êîìïüþòåðà êëèåíòà. ресурс независимо от значения
âõîäà ( guest ok = no - ãîñòåâîé âõîä %L - NetBIOS-èìÿ ñåðâåðà SAMBA.
çàïðåùåí) %v - âåðñèÿ SAMBA. writable (в данном примере это пользо-
guest account = nobody # èìÿ ïîä êîòî- %I - IP-àäðåñ êîìïüþòåðà êëèåíòà. ватель administrator и группа sales).
ðûì ðàçðåøåí ãîñòåâîé âõîä â ñèñòåìó %T - äàòà è âðåìÿ.
security = user # Óðîâåíü äîñòóïà. %u - èìÿ ïîëüçîâàòåëÿ, ðàáîòàþùåãî ñ Возможно использование и противо-
user - íà óðîâíå ïîëüçîâàòåëÿ, ñåðâèñàìè. положного списка - read list. Если есть
security = share - àóòåíòèôèêàöèÿ íà %H - äîìàøíÿÿ äèðåêòîðèÿ ïîëüçîâàòåëÿ
îñíîâå èìåíè è ïàðîëÿ. %u. необходимость скрыть некоторые
файлы, то в Unix/Linux для этого имя
При хранении базы паролей на Включение параметров preserve файла должно начинаться с точки (па-
другом SMB-сервере используются case и short preserve case заставляют раметр hide dot files, который регули-
значения security = server и password сервер сохранять всю вводимую ин- рует отображение скрытых файлов,
server = name_server_NT. В случае формацию с учетом регистра симво- по умолчанию = yes). Но кроме этого
если сервер является членом домена лов (в Windows регистр не имеет зна- есть возможность задать шаблоны
используется значение security = чения, а во всех Unix это не так). имен скрытых файлов, для этого ис-
domain, пароль для доступа указыва- Раздел [homes] позволяет пользо- пользуется параметр hide files. Каж-
ется в файле определенном с помо- вателям подключаться к своим рабо- дый шаблон начинается и заканчи-
щью опции smb passwd file = /path/to/ чим каталогам без явного их описа- вается с символа косой черты «/» и
file. ния. При запросе клиентом своего может содержать символы, применя-
Кроме того, при регистрации могут каталога, например //sambaserver/ емые в регулярных выражениях. На-
использоваться шифрованные sergej, система ищет соответствую- пример: hide files = /*.log/??.tmp/. Но
(encrypted) и незашифрованные (plain- щее описание в файле и, если не на- все эти ухищрения обходятся пользо-
text) пароли. Последние используются ходит его, то просматривает наличие вателям очень просто - установкой ре-
в старых Windows (Windows for этого раздела. Если раздел существу- жима «показывать скрытые и систем-
Workgroups, Windows 95 (OSR2), все ет, то просматривается файл паролей ные файлы» проводника Windows.
версии Windows NT 3.x, Windows NT 4 для поиска рабочего каталога пользо- Для уверенного ограничения доступ-

26
администрирование
ности (возможности удаления) файла ние о закрытии ресурса должен при- ным исключением, параметр printable
(каталога) используйте параметры: нять сервер и все потому, что клиен- = yes. Например:
veto files и delete veto files. С CD-ROM ты, как правило, не извещают об этом.
дисками дело обстоит несколько Но наиболее частой причиной явля- [printers]
сложнее. ется то, что ресурсом могут одновре- path = /var/spool/samba # óêàçûâàåò
íà êàòàëîã â êîòîðûé ïîìåùàþòñÿ çàäà
Все дело в том, что в Unix-подоб- менно пользоваться сразу несколько íèÿ íà ïå÷àòü
ных системах понятие диска отсут- пользователей или на одном компью- browseable = yes
printable = yes
ствует как таковое, и для того чтобы тере оставлен открытый файл на дан- read only = yes
получить доступ к нужному устрой- ном ресурсе. Поэтому CD-ROM авто-
ству, оно первоначально должно быть матически не размонтируется, един- После создания файла протести-
смонтировано в дерево каталогов (# ственный приемлемый способ, чтобы руйте его с помощью утилиты
mount -t iso9660 /dev/cdrom /mnt/ освободить ресурс - посмотреть с по- testparm, но при помощи данной про-
cdrom). А после использования, что- мощью утилиты smbstatus номер про- граммы можно обнаружить лишь син-
бы не разрушить файловую систему, цесса, использующего данный ресурс, таксические ошибки, а не логические,
должно быть размонтировано (# и убить его командой # kill pid_number поэтому нет никакой гарантии, что
umount /dev/cdrom), иначе устройство (или kill -s HUP pid_number). описанные в файле сервисы будут
просто не отдаст диск. Если у вас на Установив необходимую конфигу- корректно работать (при тестирова-
сервере запущен демон autofs, то про- рацию необходимо теперь создать нии будут выведены все установки -
блема решается просто. Для того что- учетные записи пользователей (за даже те, которые установлены по-
бы устройство которое не использу- исключением гостевого входа с мини- умолчанию). Но если программа не
ется в течении некоторого времени, мальными правами nobody). Для ин- ругается, можете надеяться, что при
было автоматически размонтировано, тентификации пользователей SAMBA запуске файл будет загружен без про-
установите нужное значение парамет- используется файл /etc/samba/ блем. А правильность установки прин-
ра timeout в файле /etc/auto.master, smbpasswd, в котором содержатся тера можно проверить с помощью
например: имена и зашифрованные пароли утилиты - testprns. Плюс не забывай-
пользователей. Так как механизм те о log-файлах - при возникновении
/mnt /etc/auto.misc —timeout=60 шифрования в сетях Windows-машин проблем там иногда можно найти ре-
не совместим со стандартными Unix шение.
А затем установите параметры для механизмами, то для заполнения фай- Теперь немного о хорошем. Кон-
соответствующего устройства в фай- ла паролей используется отдельная фигурирование Samba - довольно
ле /etc/auto.misc: утилита - smbpasswd. сложная процедура, но с дистрибути-
вом поставляется инструмент админи-
cdrom # useradd -s /bin/false -d /home/samba/ стрирования на основе Web, который
fstype=iso9660,ro,nosuid,nodev : / sergej -g sales sergej
dev/cdrom # smbpasswd -a sergej называется swat (Samba Web
Administration Tool). Swat запускается
После всего прописываем в /etc/ В этом примере добавляется но- в виде сервиса или с помощью сер-
smb.conf следующие строки, чтобы вый пользователь sergej с фиктивной вера Apache, и предназначен для ре-
сделать доступным данный ресурс: оболочкой, принадлежащий группе дактирования файла smb.conf, а так-
sales и домашним каталогом /home/ же для проверки состояния, запуска
[cdrom] samba/sergej. Затем создается пароль и остановки демонов Samba. Для ра-
path = /mnt/cdrom
writable = no для пользователя sergej. боты в виде сервиса в файле /etc/
С помощью SAMBA можно органи- services должна быть обязательно
Второй вариант состоит в исполь- зовать возможность сетевой печати с строка swat 901/tcp, а в файле /etc/
зовании директив preexec и postexec, компьютеров под управлением inetd.conf - swat stream tcp nowait.400
которые указывают какие команды Windows (если планируется отдель- root /usr/local/samba/bin/swat swat. Те-
необходимо выполнить при обраще- ный сервер печати, то для этого бы- перь для запуска Swat в окне браузе-
нии к ресурсу и после отсоединения вает достаточно и машины на базе ра введите:
от него. 486 процессора).
http://localhost:901
[cdrom]
Для этого в секции [global] необ-
path = /mnt/cdrom ходимо записать такие строки: После всех изменений в файле
read only = yes smb.conf иногда потребуется пере-
root preexec = mount /mnt/cdrom #ìîí-
òèðîâàòü ðåñóðñà èìååò ïðàâî òîëüêî root printcap name = /etc/printcap # ôàéë запустить демон smb: /etc/rc.d/init.d/
root postexec = umount /mnt/cdrom # îïèñàíèÿ ïðèíòåðîâ, ïîäêëþ÷åííûõ ê ñèñ-
åñòåñòâåííî ýòè òî÷êè ìîíòèðîâàíèÿ äîë- òåìå smb restart.
æíû áûòü îïèñàíû â ôàéëå /etc/fstab èíà÷å load printers = yes #, óêàçûâàåò íà Если после всех перечисленных
íåîáõîäèìî óêàçàòü è îñòàëüíûå äàííûå. íåîáõîäèìîñòü àâòîìàòè÷åñêîãî âêëþ÷åíèÿ
â ñïèñîê ñåòåâûõ ðåñóðñîâ действий так и не удалось организо-
Теперь при обращении к ресурсу printing = lprng # ñèñòåìà ïå÷àòè вать доступ к ресурсам SAMBA, то в
(äëÿ Linux ìîæåò åùå èñïîëüçîâàòüñÿ bsd)
автоматически монтируется CD-ROM. дальнейшей настройке помогут такие
А в идеальных случаях и размонтиру- Далее каждый принтер описывает- утилиты, как ping, nmblookup или на
ется. Вся проблема в том, что реше- ся как дисковый ресурс с единствен- крайний случай tcpdump. Вот и все.

№1, октябрь 2002 27


администрирование

В
АДМИНИСТРИРОВАНИИ
СЕРВЕРА: ПОЧЕМУ БЫ
И НЕТ?
РОМАН СУЗИ
Стандартных решений не существует. Каждый сис- ками при решении как повседневных, так и одноразо-
темный администратор рано или поздно начинает пи- вых задач.
сать свои скрипты, которые облегчают его работу, из- Python — интерпретируемый язык с развитыми вы-
бавляя от рутины. Для автоматизации в Unix-системах сокоуровневыми структурами данных, имеющий все не-
традиционно применяются командные оболочки (типа обходимое для вызова функций POSIX-совместимых си-
bash или ksh, разновидностей оболочек достаточно стем. Впрочем, Python является многоплатформенным
много) и язык Perl. Причем, следуя философии Unix, эти языком, так что его можно с успехом использовать и, к
оболочки используют для решения проблемы целый примеру, в среде Windows. Однако не буду долго гово-
набор инструментов, выполняющих небольшие частные рить о происхождении и синтаксисе языка: об этом за-
задачи: ls, wc, sort, grep, diff, tar, ... интересованный читатель узнает на http://python.ru или
Обычно простые задачи выполняются в командной из книги Сузи Р.А. Python. — СПб.: БХВ-Петербург,
оболочке запуcком соответствующих инструментов и 2002; а сразу перейду к делу.
организацией потока данных. Для более сложных за- Начнем с небольшого примера, в котором нам тре-
дач требуются и более сложные инструменты, напри- буется установить права и принадлежность файлов, что-
мер, awk, sed или даже Perl. Так принято. бы имена совпадали с именами пользователей в сис-
Однако хотелось бы обратить внимание системных теме (для простоты будем считать, что имена пользо-
администраторов на такой сценарный (скриптовый) вателей доступны из файла /etc/passwd). Подобная за-
язык как Python. Этот язык, благодаря своим хорошим дача может возникнуть, например, в каталоге с почто-
качествам, о которых поговорим далее, уверенно заво- выми ящиками, где каждый ящик должен принадлежать
евывает популярность, в том числе для задач систем- соответствующему его названию пользователю.
ного администрирования. Например, именно его при-
меняет Red Hat в инструменте под названием anaconda #!/usr/bin/python
для обеспечения начальной установки своего дистри-
import os, string
бутива Linux.
Конечно, системные администраторы — натуры users = {}
for line in open("/etc/passwd").readlines():
весьма консервативные, и потому я решил написать эту rec = string.split(line, ":")
статью, показывающую, что Python действительно име- users[rec[0]] = int(rec[2]), int(rec[3]) # uid è gid
for file in os.listdir("."):
ет преимущества по сравнению с языком Perl и оболоч-

28
администрирование
try: файлов, содержащих в имени точку, пробел или дефис
uid, gid = users[file] в начале!
except:
print "Ñèðîòà: ", file Я более чем уверен, что программу можно перепи-
uid, gid = 0, 0 # root сать, экранировав символы должным образом. Но тем
os.chmod(file, 0600)
os.chown(file, uid, gid) не менее, зная Python, можно быстрее написать скрипт,
решающий ту же задачу без скользких текстовых под-
В самом начале мы импортируем модуль для рабо- становок.
ты с функциями ОС (os) и работы со строками (string). Из приведенных примеров уже видна особенность
Перед началом цикла по строкам файла /etc/passwd сло- языка Python, не всеми воспринимаемая хорошо: для
варь users пуст. В цикле по строкам файла /etc/passwd выделения фрагментов кода в составных операторах
мы делаем следующее. Именем rec обозначаем кортеж используется единообразный отступ. Тем самым интер-
значений записи passwd-файла. Мы знаем, что первое претатор Python требует от программиста визуально вы-
поле этой записи — имя пользователя. Имя пользова- делять структуру программы, что положительно сказы-
теля и станет ключом в словаре users. В качестве зна- вается на читаемости кода. И это немаловажно, ведь
чения для данного ключа мы берем приведенные к це- написанный скрипт может пригодиться для похожей за-
лому типу поля 2 и 3, соответствующие идентификато- дачи.
рам пользователя и группы. Таким образом в первом В следующем примере мы рассмотрим еще одну ча-
цикле формируется отображение имени пользователя сто возникающую задачу: проверка работоспособнос-
и его идентификаторов. Во втором цикле, по именам ти POP3-сервиса и отправка сообщения электронной
файлов в текущем каталоге, пытаемся (try) найти вла- почты в случае неудачи. Заметьте, что приведенный
дельца файла, обращаясь к словарю users. Если это не пример одинаково хорошо подходит и для Unix, и для
удается, мы пишем на стандартный вывод соответству- NT.
ющую диагностику. В этом случае владельцем файла
станет root. Последние две команды, думается, ясны и #!/usr/bin/python
без комментариев. import smtplib, poplib
В приведенном на листинге примере были исполь-
try:
зованы очевидные решения, не требующие особого зна- p = poplib.POP3("mymail")
ния стандартной библиотеки Python. p.quit()
except:
Для любознательных укажем и второй путь реше- error = "connection"
ния, в котором используются «правильные» средства: try:
s = SMTP("othermail")
s.sendmail("admin@mymail", "admin@othermail",
#!/usr/bin/python """From: admin@mymail
To: admin@othermail
import os, pwd, glob Subject: POP3 down!!!

default = pwd.getpwnam("root") Please, restart pop3 at mymail.


for file in glob.glob("*"):
try: """)
rec = pwd.getpwnam(file) s.quit()
except: except:
print "Ñèðîòà: ", file print "íåò ñâÿçè"
rec = default
os.chmod(file, 0600)
os.chown(file, rec[2], rec[3]) С помощью конструктора объекта POP3-соединения
из модуля poplib мы пытаемся установить соединение
Здесь удалось добиться некоторого сокращения с POP3-сервером mymail. Если это не удается, мы дол-
программы за счет использования функции getpwnam жны отправить о происшес твии письмо на
модуля pwd стандартной библиотеки Python. Здесь так- admin@othermail. В этом нам поможет модуль smtplib и
же применена функция glob из одноименного модуля конструктор класса SMTP. Если и это не удается, про-
для получения списка файлов. сто выводится «нет связи». Заметьте, как легко в Python
Для сравнения приведен неправильный пример по- ввести многострочный текст.
добной же программы, написанный в оболочке bash. Надеемся, вы как минимум заинтересовались пред-
лагаемым инструментом.
#!/bin/bash Таблица 1 поможет быстро найти аналоги распрост-
# Íå ðàáîòàåò, åñëè èìåíà ôàéëîâ ñîäåðæàò òî÷êó èëè ïðîáåë
раненных команд среди функций стандартной библио-
for file in * теки языка Python. Перед использованием функции до-
do статочно импортировать соответствующий модуль.
chown $file.$file $file || chown root.root $file
chmod 600 $file Настало время разобрать более сложный пример --
done программу для анализа логов почтового сервера
Sendmail. Наверное, не нужно долго объяснять, что для
Конечно, на языке командной оболочки программа обеспечения бесперебойной работы сервера и требуе-
получилась раза в два короче. Однако в ней таится не- мого качества обслуживания просто необходимо вов-
приятная неожиданность: она работает некорректно для ремя выявлять аномалии в его работе и пресекать по-

№1, октябрь 2002 29


администрирование
Таблица 1
°ÔÒÆÓÊÆ8QL[ ºÙÓÐÜÎå3\WKRQ
FDOÒË×åÜÉÔÊ FDOHQGDUPRQWKFDOHQGDU ÉÔÊÒË×åÜ 
FGÐÆØÆÑÔÉ RVFKGLU ÐÆØÆÑÔÉ 
FKPRGÖËÌÎÒÕÙØâ RVFKPRG ÕÙØâÖËÌÎÒ 
FKRZQXLGJLGÕÙØâ RVFKRZQ ÕÙØâXLGJLG 
FSÚÆÏÑÕÙØâ VKXWLOFRS\ ÚÆÏÑÕÙØâ 
GDWH WLPHORFDOWLPH WLPHWLPH ÎÊÖÙÉÎËÚÙÓÐÜÎÎÒÔÊÙÑåWLPH
GLII ÚÙÓÐÜÎÎÒÔÊÙÑåGLIIOLE
HFKRÕËÖËÒ SULQW ÕËÖËÒ VYDUV 
HFKRØËÐ×Ø SULQWØËÐ×Ø
H[LW1 V\VH[LW 1 
IHWFKPDLO ÒÔÊÙÑâSRSOLE
IWS ÒÔÊÙÑâIWSOLE
JUHSHJUHS ÊÑåÖÆÇÔØá×ÖËÉÙÑåÖÓáÒÎÈáÖÆÌËÓÎåÒÎ×ÑÙÌÎØÒÔÊÙÑâUH
J]LS  ÒÔÊÙÑâJ]LS
OQVÕÙØâÕÙØâ RVV\POLQN ÚÆÏÑÚÆÏÑ 
OQÚÆÏÑÚÆÏÑ RVOLQN ÚÆÏÑÚÆÏÑ 
OVOÚÆÏÑ RVVWDW ÚÆÏÑ 
OVÐÆØÆÑÔÉ RVOLVWGLU ÐÆØÆÑÔÉ JOREJORE ÐÆØÆÑÔÉ 
PDQØËÒÆ KHOS ØËÒÆ ÎÑÎKHOS ÔÇàËÐØ 
PNGLUÕÙØâ RVPNGLU ÕÙØâ 
PYÕÙØâÕÙØâ  RVUHQDPH ÕÙØâÕÙØâ 
QVORRNXSÛÔ×Ø  VRFNHWJHWKRVWE\QDPH ÛÔ×Ø ÎÊÖÙÉÎËÚÙÓÐÜÎÎãØÔÉÔÒÔÊÙÑå
SZG RVJHWFZG 
UP5ÐÆØÆÑÔÉ VKXWLOUPWUHH ÐÆØÆÑÔÉ 
UPÚÆÏÑ RVXQOLQN ÚÆÏÑ ÎÑÎRVUHPRYH ÚÆÏÑ 
VHQGPDLO ÒÔÊÙÑâVPWSOLE
VRUW ×ÕÎ×ÔÐVRUW 
ZJHWÎØÕ ÒÔÊÙÑÎKWWSOLEXUOOLEXUOOLEXUOSDUVH
]LS  ÒÔÊÙÑâ]LSILOH
ÐÔÒÆÓÊÆ  RVV\VWHP ÐÔÒÆÓÊÆ 
ÐÔÒÆÓÊÆ_  I RVSRSHQ ÐÔÒÆÓÊÆU 
_ÐÔÒÆÓÊÆ  I RVSRSHQ ÐÔÒÆÓÊÆZ 
ÚÆÏÑ I RSHQ ÚÆÏÑU IUHDG IFORVH 
!ÚÆÏÑ  I RSHQ ÚÆÏÑU IZULWH  IFORVH 

пытки злоупотребления сервисом. Одна из основных


проблем - массовые несанкционированные рассылки,
или попросту спам. И проблема эта не только в том,
чтобы уберечь пользователей сервера от спама, но и
вовремя заметить попытки применения пользователя-
ми программ массовой рассылки.
Я почти уверен, что на рынке имеются готовые ин-
струменты для решения этой проблемы. Даже очень
возможно, что нужные программы есть в свободном
распространении. Однако простые средства во многих
случаях можно написать и самому, руководствуясь сво-
им опытом и, возможно, знанием местной специфики.
Тем более, что это не требует много времени.
Разбираемый ниже пример не претендует на полно-
ту решения проблемы спама, он решает частную зада-
чу. Мы будем отслеживать лишь попытки массовых рас-
сылок, в которых на один «mail from» следуют несколь-
ко «rcpt to». Конечно, программу легко адаптировать и
для другой ситуации, агрегируя данные по отправите-
лям, получателям и так далее, получая портрет ситуа-
ции на почтовом сервере. Следует заметить, что глядя

30
администрирование
на «сырые» логи Sendmail не всегда можно увидеть date, session_id, addr = found["date"], found["session"],
проблему. Во всяком случае, для этого требуется неко- found["addr"]
торое напряжение глаз. direc = found.get("direc", "to") # ïî óìîë÷àíèþ "to"
stat = found.get("stat", "") # ïî óìîë÷àíèþ - ïóñòàÿ
Наша небольшая программа на Python будет соби- ñòðîêà
рать данные о сеансах и показывать только сеансы с if direc == "to" and stat[:4] != "Sent" and stat !=
большим числом получателей. В логе эта информация "User unknown":
сильно размазана, поэтому простого применения инст- continue # òàêèå ñîîáùåíèÿ íå èíòåðåñóþò
румента вроде grep здесь оказывается недостаточно. if direc == "to": # â çàâèñèìîñòè îò íàïðàâëåíèÿ
Накапливаемые данные мы будем сохранять в фай- if t.has_key(session_id):
t[session_id] = t[session_id] + addr + ";"
лах-хэшах (аналогичных тем, в которых хранятся, на- else:
пример, псевдонимы), благо в Python есть для этого t[session_id] = addr + ";"
else:
нужные модули. (Конечно, в Python можно использовать f[session_id] = addr
и полновесные базы данных с языком запросов SQL, r[session_id] = found.get("relay_ip", "") or
found.get("relay", "")
однако это непринципиально). d[date + session_id] = session_id
Задача распадается на две: сбор данных из лога в input_file.close()
базу данных и выборка из базы данных. f.close(); t.close(); r.close(); d.close()

#!/usr/bin/python Построчно читаем из файла с логами и, в зависимо-


"""Àíàëèç ëîãà Sendmail""" сти от сработавшего шаблона, записываем в заранее
import os, re, sys, anydbm, time открытые базы данных. Обратите внимание, что база
date.db использует в качестве ключей как дату, так и
server_name = "mail" # èìÿ ñåðâåðà, ôèãóðèðóþùåå â ëîãàõ
идентификатор сессии. Последний делает ключ уни-
def createdb(file): кальным.
"""Ñîçäàíèå ÁÄ"""
try: os.unlink(file) # óäàëÿåì ñòàðóþ ÁÄ Заметим, что в Python логические операции or и and
except: pass # èãíîðèðóåì èñêëþ÷åíèÿ работают несколько необычно. Любой объект имеет так
return anydbm.open(file, "c")
# êëþ÷: çíà÷åíèå называемое истинностное значение. Нули, пустые пос-
f = createdb("from.db") # èä ñåññèè: îòïðàâèòåëü ледовательности, объект None считаются «ложью», а
t = createdb("to.db") # èä ñåññèè: ïîëó÷àòåëè
r = createdb("relay.db") # èä ñåññèè: ïî÷òîâûé õîñò остальные объекты -- истиной. В результате операции
d = createdb("date.db") # âðåìÿ è èä ñåññèè: èä ñåññèè A or B возвращается объект A, если он «истинен», а в
''' Ïðèìåðû ÷åòûðåõ õàðàêòåðíûõ ñëó÷àåâ: противном случае -- объект B. Именно поэтому имени
Aug 11 20:24:25 mail sendmail[4720]: g7BGOHY2004720: m будет соответствовать результат первого успешного
from=<tegeran@mail.ru>, size=103655, class=0, nrcpts=1,
msgid=<000c01c2414e$d175ad80$3075a8c0@awx.ru>, proto=SMTP, сравнения.
daemon=MTA, relay=[212.28.127.12] Python оперирует такими высокоуровневыми объек-
Aug 11 20:24:25 mail sendmail[4720]: g7BGOHY2004720:
to=<font@twogo.ru>, delay=00:00:07, pri=133655, stat=Headers тами, как список и словарь. Словарь задает отображе-
too large (32768 max)''' ние между ключами и значениями. В частности, found
Aug 11 04:02:31 mail sendmail[17058]: g7B02UY2017058:
from=<gluck@subscribe.ru>, size=33977, class=0, nrcpts=1, является словарем, отображающим имя найденной в
msgid=<20020811030027_hk_=2087=top=n=n_@subscribe:news.listsoft.lnx>, строке лога группы и значения этой группы. Имена групп
proto=SMTP, daemon=MTA, relay=relay1.aport.ru [194.67.18.127]
Aug 11 07:18:20 mail sendmail[28329]: g7B3ICY2028329:
<nouser@twogo.ru>... User unknown'''

# õàðàêòåðíûå ÷àñòè ðåãóëÿðíûõ âûðàæåíèé (ïðåôèêñ, õîñò è


àäðåñ)
P = "(?P<date>.{15}) %(server_name)s sendmail\[[0-9]+\]:
(?P<session>[^:]+): " % vars()
R = "relay=(?P<relay>.*(?:\[(?P<relay_ip>.+?)\])?)"
A = "\<?(?P<addr>[^(),>]+)\>?"
# ðåãóëÿðíûå âûðàæåíèÿ, ñîîòâåòñòâóþùèå ðàçíûì ñëó÷àÿì
log1_re = re.compile(r"%(P)s(?P<direc>to)=%(A)s, (.*),
stat=(?P<stat>.*)" % vars())
log2_re = re.compile(r"%(P)s(?P<direc>from)=%(A)s,
size=(?P<size>[0-9]+), .*, %(R)s" % vars())
log3_re = re.compile(r"%(P)s(?P<direc>from)=%(A)s, .*, %(R)s"
% vars())
log4_re = re.compile(r"%(P)s%(A)s\.\.\. (?P<stat>User
unknown)" % vars())
input_file = open(sys.argv[1], "r") # îòêðûâàåì ôàéë ñ
ëîãîì
while 1:
line = input_file.readline()
if not line: # îáíàðóæåí êîíåö ôàéëà: âûõîä èç öèêëà
break
# ñðàâíèâàåì ñòðîêó ëîãà ñ øàáëîíàìè
m = log1_re.match(line) or log2_re.match(line) \
or log3_re.match(line) or log4_re.match(line)
if m: # åñëè õîòü îäèí øàáëîí ñðàáîòàë:
found = m.groupdict() # ïîëó÷àåì ñëîâàðü ãðóïï ðåçóëü-
òàòà

№1, октябрь 2002 31


администрирование
в регулярных выражениях задаются через (?P<имя> ...). файлов логов сразу; обрабатывать ситуации, когда не-
Взять из словаря значение по ключу можно не только с сколько получателей указаны в одной строке лога; из-
помощью словарь[ключ], но и с помощью метода get(). менить правила фильтрации, изменить способ агреги-
В последнем случае можно задать значение «по умол- рования данных (например, агрегировать по именам по-
чанию», то есть значение, которое метод get() возвра- лучателей, именам отправителей, адресам почтовых хо-
тит в случае отсутствия ключа в словаре. стов); сделать из программы демона, непрерывно сле-
Стоит заметить, что работа с базами данных исполь- дящего за логами и т.п.
зует тот же синтаксис, что и работа со словарями (об Составляя наши программы, мы выразили логику
этом позаботились разработчики модуля anydbm). анализа логов почтового сервера и почти не отвлека-
Итак, мы поместили информацию из лога Sendmail лись на обдумывание применяемых конструкций языка
в более удобный для обработки вид -- в базы данных программирования и конструирование типов данных.
(хэши). Следующая программа - пример одного из об- Программисты на Python утверждают, что на этом
работчиков, которые мы теперь можем применить. языке можно писать «со скоростью мысли», то есть, по-
Для нашей цели (выявление чрезмерных списков чти все время оставаясь в предметной области. Если
рассылки) данные удобно отсортировать по времени. вы этому не верите, попробуйте переписать приведен-
Это легко сделать с ключами из базы date.db. По сути ные программы с использованием стандартного Си или
мы считываем все ключи этой базы в память (метод Java. С другой стороны, приведенную задачу обычно
keys()) и сортируем их по возрастанию. решают с помощью языка Perl. К сожалению, Perl вно-
сит очень много синтаксического мусора, что подчас
#!/usr/bin/python значительно затеняет логику программы.
"""Àíàëèçèðóåì ñîáðàííûå äàííûå"""
Основными преимуществами Python являются, на
import re, sys, anydbm, string мой взгляд:
# îòêðûâàåì áàçû
f
t
= anydbm.open("from.db", "r")
= anydbm.open("to.db", "r")
n удобный и легко читаемый синтаксис
r = anydbm.open("relay.db", "r") n многоплатформенность
d = anydbm.open("date.db", "r") n поддержка основных системных и сетевых протоко-
# ïîêàçûâàòü òîëüêî ñëó÷àè îòïðàâëåíèÿ >= LIM ïîëó÷àòåëÿì лов и форматов
try:
LIM = int(sys.argv[1])
n большой набор библиотек
except: n свободное распространение
LIM = 4 n дружелюбная поддержка в телеконференции
dkeys = d.keys() # ïîëó÷àåì âñå êëþ÷è â îäèí ñïèñîê comp.lang.python
# ñîðòèðóåì (ôàêòè÷åñêè, ïî âðåìåíè). Ïðåíåáðåãàåì ñìåíîé
ìåñÿöåâ
n надежный и устойчивый интерпретатор
dkeys.sort()
Все это делает Python отличным инструментом в ра-
good_guys_re = re.compile(".*(gluck@subscribe.ru|-
errors@maillist.ru|@lists.cityline.ru|@host4.list.ru)\Z") боте системного администратора.
def our_filter(x):
"""Ôóíêöèÿ äëÿ ôèëüòðàöèè ïîëó÷àòåëåé"""
return "@" in x

for date_sess_id in dkeys:


i = d[date_sess_id] # íàõîäèì èäåíòèôèêàòîð ñåññèè
try:
recps = string.split(t[i], ";") # ñïèñîê ïîëó÷àòåëåé
# áåðåì òîëüêî àäðåñà ñ @
recps = filter(our_filter, recps)
sender = f[i] # îòïðàâèòåëü
# ïîêàçûâàåì
if len(recps) >= LIM and not good_guys_re.match(sender):
relay = r[i]
dte = date_sess_id[:15] # ïåðâûå 15 ñèìâîëîâ -- äàòà
print f[i], relay, dte, "->\n ", string.join(recps,
"\n ")
except: # åñëè âîçíèêëè îøèáêè, ïðîïóñêàåì
pass
f.close(); t.close(); r.close()

Здесь особо следует отметить функцию filter(). Эта


встроенная функция применяет некоторую функцию (ар-
гумент 1) к списку (аргумент 2). В нашем случае ис-
пользуется our_filter, которая возвращает «истину» в
случае, когда ее аргумент содержит «@».
Составленная программа далека от совершенства,
однако ее легко доработать в любом нужном направ-
лении: добавить возможность обработки нескольких

32
ПРОГРАММИРОВАНИЕ
ЕВГЕНИЙ КОНОВАЛОВ

РАБОТА
С
ТЕКСТОМ
ИЛИ
ФИЛОСОФИЯ
PERL
программирование
Есть два основных аспекта, которые легли в ление пользователю возможности их раздельного про-
основу этой статьи. смотра. Это связано с сегодняшней философией жизни в
Первый - это периодически возникающая Интернет, например, с «диалапными» скоростями досту-
перед большинством Web-программистов па (впрочем, не только с этим).
проблема переноса тех или иных документов Подытоживая сказанное, Вашему вниманию представ-
в базу данных (БД), используемую, ляется задача, решенная в рамках разработки подсисте-
например, при генерации страниц скриптами. мы информационного наполнения БД.
Проблема эта из разряда концептуальных и Исходные данные:
решается по-разному: в зависимости от n тип вносимой информации - HTML-код значительного
рабочей среды, окружающей разработчика объема;
плотным кольцом всевозможных n весь HTML-код заключен в едином документе;
требований, условий и согласований. n документ необходимо разбить на составные части, ко-
Вторая идея, попавшая в фокус обсуждения - торые размещаются в БД (с тем же успехом их можно
использование Perl для решения сохранить в файл).
любопытной задачи, связанной с упомянутой Основная трудность решения: в результате разбиения
выше проблемой. Собственно об этом и документа необходимо получить фрагменты HTML-кода,
пойдет речь в дальнейшем. (Примечание: имеющие корректную HTML-структуру. Такого рода кор-
данная статья, пожалуй, рассматривается ректность подразумевает:
именно как посылка для обсуждения - иные n соответствие каждому начальному HTML-тегу завер-
подходы не имеют и половины такой же шающего HTML-тега (для HTML-элементов, обязатель-
привлекательности.) но обозначаемых именно парой тегов);
n переработку внутренних гиперссылок (для случаев,
Вместо лирического вступления когда сама ссылка оказывается в одном фрагменте, а
то место в оригинальном документе, на которое она
На всякий случай автор искренне просит не рассмат- ссылается, - в другом фрагменте).
ривать эту статью как очередное «УРА» в честь наиболее
развитого и удобного, свободного, попросту приятного и Отправная точка: исходные данные в
даже замечательного языка для работы с текстом и про- подробностях
граммирования в Web (разумеется, речь идет о Perl).
«Начинать нужно с чего-то...», - и с этим трудно не согласиться.

Сильным духом программистам Чтобы стало понятным все, о чем пойдет речь в даль-
посвящается... нейшем, необходимо внести большую ясность в то, что
касается исходных данных. На вход описанного ниже про-
«Если ты едешь тише - дольше ехать тебе. Если поспешишь, то не
разглядишь идей» граммного модуля, поступают должным образом подго-
товленные оглавление и текст HTML-документа. Далее
Следуя этой нехитрой идее, хочется без излишних под- приведено описание правил, по которым осуществляется
робностей, но, тем не менее, вдумчиво изложить уважае- такого рода подготовка документа.
мому читателю идеи, которые, вполне возможно, приго- Говоря коротко, необходимо иметь HTML-документ с
дятся в работе или просто послужат поводом для размыш- гипертекстовым оглавлением (в оглавлении должны ис-
лений. пользоваться внутренние гиперссылки - то есть те, значе-
Сначала несколько слов об истоках рассматриваемой ния атрибутов href которых начинаются со знака решетки
проблемы. Сегодня адаптация документов различных «#»). Такой подход достаточно удобен. Теперь о том же
форматов для внесения их в БД Web-сайта - занятие при- самом, но уже более подробно.
вычное во многих организациях, имеющих собственную Дабы исключить путаницу в терминах введем несколь-
«точку присутствия» в сети Интернет. Зачастую это свя- ко понятий.
зано с возможностью простой реализации полнотексто- «Внутренняя гиперссылка» - ссылка, при нажатии на
вого поиска в таблицах БД и простотой реализации меха- которую выполняется переход на анкер (см. ниже), распо-
низмадоступа к информации. ложенный в том же HTML-документе, в теле которого на-
Так или иначе, все выглядит достаточно просто и по- ходится и сама ссылка; значение атрибута HREF такой
нятно, когда речь заходит о небольших объемах данных, гиперссылки начинается со знака решетки #. Например:
особенно, если они подготавливаются централизовано
одним-двумя сотрудниками. Сложности возникают при <A HREF=»#Part1">1. Introduction</A>.
работе с документами больших размеров.{1} Что также
может осложняться острым желанием предоставить ад- «Анкер» - тег, размещаемый в том месте HTML-доку-
министратору БД и многочисленным контент-менеджерам мента, куда необходимо осуществить перемещение по на-
удобную методику для работы с данными. жатию на внутреннюю гиперссылку. Например:
Одновременно специфика Web, конечно же, делает
предпочтительной разбивку крупных документов на от- <A NAME=»Part1">
дельные составляющие (например, главы), и предостав-

№1, октябрь 2002 35


программирование
Примечание: связь между анкером и внутренней ги- Примечание: при программном анализе документа
перссылкой реализуется через значения атрибута HREF заголовочная часть (вплоть до тега <BODY>) и концевые
гиперссылки и атрибута NAME анкера, при этом значение теги </HTML> и </BODY> не рассматриваются.
NAME соответствует значению HREF с точностью до зна-
ка решетки. Например: <HTML>
<HEADER>Motherboard Description</HEADER>
<BODY>
<A HREF=»#Part1">1. Introduction</A>
<A NAME=»Part1"></A>
<TABLE>
является внутренней ссылкой на анкер <TR><TD>
<B><H3>Introduction.</H3></B><BR><BR ><FONT
COLOR=»#000099">Mainboard is the most vital component of every
<A NAME=»Part1"> PC. If we compare a PC with a living organism, the processor
can be compared to a heart, and the mainboard is like a system
of artherias and veins which deliver information from a processor
«Оглавление» - гипертекстовое оглавление докумен- to other parts of PC.</FONT>
та, состоящее из внутренних гиперссылок.
«Пункт оглавления» - внутренняя гиперссылка, внут- <A NAME=»Part2"></A>
<P><B><H3>CPUs and CPU Sockets.</H3></B>
ри тега которой заключен текст одного из пунктов содер- <P><FONT COLOR=»#000099">So, it is time for you to buy a new
жания документа. PC. We will not discuss the modern processors in this section,
as it is revealed in our </FONT><B><FONT
«Глава документа» или «Глава» - часть HTML-доку- COLOR=»#CC0000">processors section</FONT></B><FONT
мента, сопоставляемая одному из пунктов оглавления. COLOR=»#000099">, but will concentrate on the parts responsible
to connecting CPU (Central Processor Unit) to the mainboard,
«Специальный анкер разметки» или «Анкер размет- the sockets, since there is a great variety of them nowadays.</
ки» - анкер, на который ссылается один из пунктов оглав- FONT>
ления. (Такой анкер разметки располагается непосред-
<A NAME=»Part3"></A>
ственно перед главой (обычно перед заглавием главы), <P><B><H3>Supported Memory types and speed. Chipset features.</
которая соответствует пункту оглавления, ссылающему- H3></B>
<P><FONT COLOR=»#000099">Not only the CPU is responsible for
ся на данный анкер разметки.) overall performance of your desktop (or notebook) system. Even
«Документ» - текст HTML-документа (без оглавления). the extremely speedy processor will be found slowed by less
performing RAM (Random Ac
Далее, рассмотрим в качестве примера документа ss Memory).</FONT>
обзор по материнским платам.
<A NAME=»Part4"></A><P>
Вообще говоря, в рассматриваемом случае оглавле- <P><B><H3>Built-in video and audio cards.</H3></B>
ние в традиционном понимании имеет следующий вид: <P><FONT COLOR=»#000099">As you see, the latest chipsets
have built-in audio cardsnd video adapters.
1. Introduction <A NAME=»Part5"></A>
2. CPUs and CPU Sockets <P><B><H3>Hard drive support. USB ports and extension slots.</
3. Supported Memory types and speed. Chipset features H3></B>
4. Built-in video and audio cards <P><FONT COLOR=»#000099">The times when you had to install
5. Hard drive support. USB ports and extension slots multiple intput-output cards can be forgotten and now you have
all drive (hard and floppy) support on the mainboard.
Назовем это оглавление «Исходным оглавлением». </TD></TR>
</TABLE>
Однако для корректного разбиения документа на гла- </BODY>
вы оглавление должно быть представлено, например, сле- </HTML>
дующим образом:
Замечание 1: в тексте документа не может быть более
<A HREF=»#Part1">1. Introduction</A> одного анкера разметки с одним и тем же значением ат-
<A HREF=»#Part2">2. CPUs and CPU Sockets</A> рибута «NAME» (при этом количество анкеров разметки
<A HREF=»#Part3">3. Supported Memory types and speed. Chipset
features</A> равно числу пунктов оглавления).
<A HREF=»#Part4">4. Built-in video and audio cards</A> Замечание 2: очевидно, что при разбиении на фраг-
<A HREF=»#Part5">5. Hard drive support. USB ports and extension
slots</A> менты, первая глава будет содержать стартовые теги
<TABLE>, <TR> и <TD> без концевых (завершающих) те-
В данном представлении обязательным является ис- гов. Это явное нарушение HTML-структуры, которое мо-
пользование для каждого пункта исходного оглавления жет быть особенно неприятным при использовании SSI
(см. выше) синтаксической конструкции вида: (если HTML-код главы будет включаться в состав основ-
ной HTML-страницы средствами Web-сервера).
<A HREF=»#Unique_ID»>Òåêñò ïóíêòà îãëàâëåíèÿ</A>

где Unique_ID - произвольный идентификатор, который Программирование: идея, заложенная


должен быть уникальным применительно ко всем значе- в основу
ниям атрибутов HREF всех пунктов оглавления. «Идея - базис алгоритма», - и так повсюду.
Ниже приведен соответствующий документ - это фраг-
мент HTML-кода из пяти глав (содержательная часть, ра- Фактически Manual_Parser позволяет выполнить поте-
зумеется, беспощадно урезана - буквально до потери говый анализ документа. Все операции базируются на
смысла). использовании нескольких довольно сложных структур

36
программирование
данных (см. далее), позволяющих описать документ с точ- $attrseq, $origtext) и end($tag, $origtext). Первый из них
ки зрения типов тегов, содержащихся в нем, и их количе- вызывается при обнаружении начального HTML-тега, пе-
ства. На основе такой количественной статистики произ- редаваемые ему параметры: $tag - имя тега, $attr - хэш с
водится анализ корректности HTML-структуры всего до- именем атрибута тега в качестве ключа и значением ат-
кумента и глав, на которые он будет разделен в дальней- рибута в качестве значения хэша, $attrseq - массив имен
шем. атрибутов тега, $origtext - HTML-код тега. Второй метод
Идея проста: анализ сводится к подсчету количества выполняется при обнаружении завершающего HTML-тега:
стартовых и завершающих тегов и сравнении полученных $tag - имя тега, $origtext - HTML-код тега.
величин, исправление обнаруженных ошибок заключает- В программе методы start и end перегружаются (за-
ся в добавлении недостающих стартовых и завершающих мещаются). Именно в рамках этих процедур производит-
тегов. ся потеговый анализ документа и собирается упомянутая
Остановимся на этом чуть подробнее. Суть заключа- выше статистика, характеризующая его HTML-структуру.
ется в том, что первоначально для каждой главы из тех, на
которые разбивается документ подсчитывается число на- Программирование: основные
чальных и завершающих тегов (назовем их «Величина-1» структуры данных
и «Величина-2»). Затем при обнаружении несоответствия «Структура структурной структуризации структурирует
между этими значениями начинается итерационный поиск структурную типизацию», - и все это не случайно.
недостающего парного тега: последовательно перебира-
ются последующие главы и для каждой такой главы к ве- Речь идет о структурах данных объектов класса
личине-1 и величине-2 соответственно прибавляются чис- Manual_Parser - в них накапливается статистика, позво-
ла начальных и завершающих тегов этой главы. Условием ляющая проанализировать HTML-структуру документа.
успешного окончания поиска недостающего парного тега Существует три основные структуры данных:
является равенство величины-1 и величины-2. $Doc_Info, $Work_Container и массив @Pages_Info. Сразу
Если назвать главу, в которой обнаружилось несоот- же следует заметить, что речь идет о сложных, вложен-
ветствие, стартовой (Главой итерационного поиска); главу ных структурах данных. Их понимание играет ключевую
в которой был найден недостающий парный концевой тег - роль.
заключительной, а все главы между ними - промежуточ- Каждый элемент массива @Pages_Info представляет
ными, то процесс коррекции HTML-структуры глав можно собой структуру данных, с элементами в виде скаляров,
описать достаточно просто. Необходимо дополнить стар- списков и хэшей, описывающих HTML-структуру одной
товую главу концевым HTML-тегом, заключительную гла- главы (замечание в рамках борьбы с несозвучностью на-
ву - стартовым HTML-тегом, промежуточные главы - стар- званий и терминологии: под «Page» понимается HTML-
товым и концевым HTML-тегами. страница, соответствующая главе).

Программирование: иерархия классов


При работе упор был сделан на методику объектно-
ориентированного программирования. Именно это позво-
лило использовать уже существующие классы
HTML::Parser, предоставляющий методы для разбора
SGML-документов (так же используется HTML::Filter - до-
черний класс HTML::Parser).
Было разработано два класса: Manual_Cont_Parser и
Manual_Parser.
Manual_Cont_Parser наследуется от HTML::Filter, класс
занимается обработкой оглавления (см. выше), разбирая
его попунктно. Предоставляет методы:
Get_Links_Lines - возвращает массив, каждый элемент
которого содержит текст пункта оглавления (без тегов);
Get_Links_Hrefs - возвращает массив, каждый элемент
которого содержит значение атрибута HREF пункта ог-
лавления.
Manual_Parser наследуется от HTML::Parser. Отвеча-
ет за разбор документа и его разбиение на главы. Имен-
но этому классу и будет уделено основное внимание.
Класс HTML::Parser обладает рядом методов. Ключе-
выми являются методы parse($string) или parse_file($file),
вызов одного из них инициирует работу по анализу доку-
мента.
Еще два используемых метода - start($tag, $attr,

№1, октябрь 2002 37


программирование
@Pages_Info = #Èìÿ àíàëèçèðóåìîãî òåãà
{ Tag_Name => $Tag_Name,
#Õýø ñòðóêòóð (ñì. íèæå) #Íîìåð àíàëèçèðóåìîãî òåãà â Äîêóìåíòå
Tags => {%Tags}, Tag_Num_In_Doc => $Tag_Num_In_Doc,
#Õýø ñòðóêòóð (ñì. íèæå) #Íîìåð àíàëèçèðóåìîãî òåãà â Ãëàâå
Tags_Seq_Page => {%Tags_Seq_Page}, Tag_Num_In_Page => $Tag_Num_In_Page,
#Îáùåå êîëè÷åñòâî ñòàðòîâûõ òåãîâ â ïðåäåëàõ îäíîé ãëàâû #Ïåðåìåííàÿ äëÿ ïîäñ÷åòà îáùåãî êîëè÷åñòâà ñòàðòîâûõ HTML-
Num_Of_Tags => $Num_Of_Tags, òåãîâ â Äîêóìåíòå
#Ìàññèâ, êàæäîå çíà÷åíèå - ïîðÿäêîâûé íîìåð òåãà â Äîêóìåí- Start_Tag_Num_Total => $Start_Tag_Num_Total,
òå, äëÿ êîòîðîãî â Ãëàâå íåîáõîäèìî äîáàâèòü ñòàðòîâûé òåã. #Ïåðåìåííàÿ äëÿ ïîäñ÷åòà îáùåãî êîëè÷åñòâà çàâåðøàþùèõ HTML-
Start_Tags_To_Add => {@Start_Tags_To_Add}, òåãîâ â Äîêóìåíòå
#Ìàññèâ, êàæäîå çíà÷åíèå - ïîðÿäêîâûé íîìåð òåãà â Äîêóìåí- End_Tag_Num_Total => $End_Tag_Num_Total
òå, äëÿ êîòîðîãî â Ãëàâå íåîáõîäèìî äîáàâèòü çàâåðøàþùèé òåã. };
End_Tags_To_Add => {@End_Tags_To_Add},
#Õýø, êëþ÷ õýøà - çíà÷åíèå àòðèáóòà href Âíóòðåííåé Ãèïåð-
ññûëêè, çíà÷åíèå õýøà - íîìåð Ãëàâû
Local_Links => %Local_Links, Программирование: алгоритмы работы
#Õýø, êëþ÷ õýøà - çíà÷åíèå àòðèáóòà href Âíóòðåííåé Ãèïåð-
ññûëêè íà îäíó èç Ãëàâ, çíà÷åíèå õýøà - íîìåð Ãëàâû и программный код
Local_Links_To_Pages => %Local_Links_To_Pages
}; «Алгоритм - это, как минимум, четкая, осмысленная последователь-
#Õýø ñòðóêòóð, êëþ÷ õýøà - ïîðÿäêîâûé íîìåð òåãà â òåêóùåé ность действий», - редкая женщина обладает даром алгоритма.
Ãëàâå
%Tags_Seq_Page =
{ Ниже приведены прокомментированные строки кода,
#Èìÿ òåãà указываемые в приложении, использующем классы
Tag_Name => $Tag_Name,
#Ïîðÿäêîâûé íîìåð òåãà âî âñåì Äîêóìåíòå Manual_Cont_Parser и Manual_Parser.
Tag_Num_In_Doc => $Tag_Num_In_Doc
};
Создание объекта класса Manual_Cont_Parser:
#Õýø ñòðóêòóð, êëþ÷ õýøà - èìÿ (òèï) òåãà
%Tags = my $Man_Cont_Parser = Manual_Cont_Parser->new();
{
#Êîëè÷åñòâî íà÷àëüíûõ HTML-òåãîâ äàííîãî òèïà â Ãëàâå
Start_Tag_Num => $Start_Tag_Num,
#Êîëè÷åñòâî çàêëþ÷èòåëüíûõ HTML-òåãîâ äàííîãî òèïà â Ãëàâå Подготовка к анализу документа: разбор оглавления
End_Tag_Num => $End_Tag_Num документа, получение списка анкеров разметки:
};

$Doc_Info представляет собой структуру данных, с эле- $Man_Cont_Parser->parse($contents);


ментами в виде скаляров, списков и хэшей, описываю- my @Contents_Hrefs = $Man_Cont_Parser->Get_Links_Hrefs;
щих документ вцелом.
Создание объекта класса Manual_Parser:
$Doc_Info =
{
#Õýø ñòðóêòóð (ñì. íèæå) $Man_Parser->parse($Document_Text);
Tags_Seq_Doc => {%Tags_Seq_Doc},
#Õýø ñòðóêòóð (ñì. íèæå)
Tags => {%Tags}, Анализ документа, разбор, корректировка и разбие-
#Îáùåå êîëè÷åñòâî ñòàðòîâûõ òåãîâ â ïðåäåëàõ Äîêóìåíòà ние на главы:
All_Start_Tags_Num => $All_Start_Tags_Num,
#Îáùåå êîëè÷åñòâî çàâåðøàþùèõ òåãîâ â ïðåäåëàõ Äîêóìåíòà
All_End_Tags_Num => $All_End_Tags_Num, my $Man_Parser = Manual_Parser->new(\@Contents_Hrefs);
#Õåø, êëþ÷ õýøà - èìÿ àíêåðà, çíà÷åíèå õåøà - íîìåð Ãëàâû,
â êîòîðîé ðàñïîëîæåí àíêåð
Local_Anchors => %Local_Anchors Использование полученных результатов: внесение по-
};
лученных глав в БД Web-подсистемы:
#Õýø ñòðóêòóð, êëþ÷ õýøà - ïîðÿäêîâûé íîìåð òåãà â Äîêóìåíòå
%Tags_Seq_Doc =
{ $Man_Parser->Insert_Pages_In_DB();
#Èìÿ òåãà
Tag_Name => $Tag_Name,
#HTML-êîä òåãà
Tag_Orig_Text => $Tag_Orig_Text, Подготовка к анализу HTML-документа
#Íîìåð òåãà â Ãëàâå
Tag_Num_In_Page => $Tag_Num_In_Page,
#Íîìåð Ãëàâû, â êîòîðîé íàéäåí äàííûé òåã Первоначально объекту класса Manual _Cont_Parser
Tag_Page_Num => $Tag_Page_Num
}; передается оглавление. Вызываются методы parse($text)
или parse_file($file). Затем с помощью метода
$Work_Container - структура, представляющая собой Get_Links_Hrefs получается результирующий список строк,
контейнер «рабочих» данных, она используется для вре- каждая из которых содержит преобразованное значение
менного хранения данных в процессе анализа. атрибута href одного из пунктов оглавления (преобразо-
вание заключается в удалении символа «#», с которого
$Work_Container начинается значение данного атрибута). Затем строки по-
{
#Â ýòó ïåðåìåííóþ çàíîñèòñÿ íîìåð Ãëàâû, â êîòîðîé íåäîñòà- лученного списка будут использоваться объектом класса
åò çàâåðøàþùåãî òåãà. Ãëàâà ñòàíîâèòñÿ ñòàðòîâîé äëÿ èòåðàöè- Manual_Parser для нахождения анкеров разметки в доку-
îííîãî ïîèñêà òîé Ãëàâû, â êîòîðîé ñîäåðæèòñÿ íåäîñòàþùèé çà-
âåðøàþùèé òåã менте.
Start_Page_Num => $Start_Page_Num,

38
программирование
Анализ HTML-документа: тег за тегом #Ïðîâåðêà: íàéäåííûé òåã - Âíóòðåííÿÿ Ãèïåðññûëêà
Далее подробно комментируется программная реали- if ($attr->{“href”} =~ /^#/ )
{
зация решения описанной выше задачи. Приведенный my $Temp2 = $attr->{“href”};
наже программный код относится к классу Manual_Parser. $Temp2 =~ s/#//;
$self->{Pages_Info}[$self->{Current_Page}]-
@Array - массив (инициализируется в конструкторе при >{Local_Links}{$Temp2}=$self->{Current_Page};
создании объекта), в котором содержатся имена анкеров.
}
Разметки, полученные в виде результирующего списка на }
стадии подготовки к анализу документа (см. ранее). }
Процедура start() - замещает процедуру start() класса #Îáíîâëåíèå ñòàòèñòèêè ïî íàéäåííîìó òåãó â ñòðóêòóðå äàí-
HTML::Parser, вызывается при нахождении стартового (на- íûõ ïî òåêóùåé Ãëàâå
$self->{Pages_Info}[$self->{Current_Page}]-
чального) HTML-тега. Процедура работает со всем тек- >{Num_Of_Tags}++;
стом документа, «физического» разбиения на главы на $self->{Pages_Info}[$self->{Current_Page}]->{Tags}{«$tag»}-
>{Start_Tag_Num}++;
данном этапе не произодится - каждая глава рассматри- $self->{Pages_Info}[$self->{Current_Page}]-
вается как составная часть документа. >{Tags_Seq_Page}{$self->{Tag_Num_In_Curr_Page}}-
>{Tag_Name}=$tag;
$self->{Pages_Info}[$self->{Current_Page}]-
sub start >{Tags_Seq_Page}{$self->{Tag_Num_In_Curr_Page}}-
{ >{Tag_Num_In_Doc} = $self->{Tag_Num_In_Doc};
my $self = shift;
#Îáíîâëåíèå ñòàòèñòèêè ïî íàéäåííîìó òåãó â ñòðóêòóðå äàí-
#Ïîëó÷åíèå ïàðàìåòðîâ òåãà (îïèñàíèå ñì. ðàíåå) íûõ ïî Äîêóìåíòó
my ($tag, $attr, $attrseq, $origtext) = @_; $self->{Doc_Info}->{Tags}{«$tag»}->{Start_Tag_Num}++;
$self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}-
my @Temp_Cont_HREFs = @{$self->{Contents_HREFs}}; >{Tag_Name}=$tag;
$self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}-
#Ïðîâåðêà: âõîäèò ëè íàéäåííûé ñòàðòîâûé òåã â ñïèñîê òåõ >{Tag_Orig_Text}=$origtext;
òåãîâ, äëÿ êîòîðûõ íå îáÿçàòåëüíî óêàçûâàòü ïàðíûé êîíöåâîé $self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}-
òåã >{Tag_Num_In_Page}=$self->{Tag_Num_In_Curr_Page};
if (!($self->Tag_Is_Exception_Check($tag))) $self->{Doc_Info}->{Tags_Seq_Doc}{$self->{Tag_Num_In_Doc}}-
{ >{Tag_Page_Num}=$self->{Current_Page};
$self->{Doc_Info}->{All_Start_Tags_Num}++;
#Ïðîâåðêà: ÿâëÿåòñÿ ëè íàéäåííûé òåã ãèïåðññûëêîé èëè
àíêåðîì $self->{Tag_Num_In_Curr_Page}++;
if ($tag eq “a”) #Ñ÷åò÷èê ÷èñëà òåãîâ âäîêóìåíòå (èíèöèàëèçèðóåòñÿ â êîíñò-
{ ðóêòîðå ïðè ñîçäàíèè îáúåêòà)
my $Last_Tag_Is_Cont_Href=0; $self->{Tag_Num_In_Doc}++;
#Äëÿ êàæäîãî èìåíè Àíêåðà Ðàçìåòêè }
foreach $Temp (@Array) }
{
#Ïðîâåðêà: íàéäåííûé òåã - Àíêåð Ðàçìåòêè
if ($attr->{“name”} eq $Temp && $attr->{“name”} ne “”)
Âûñòàâëåíèå ôëàãîâ, èçìåíåíèÿ ñ÷åò÷èêîâ
{
#Ñ÷åò÷èê ÷èñëà òåãîâ â Ãëàâå (èíèöèàëèçèðóåòñÿ â êîíñò-
ðóêòîðå ïðè ñîçäàíèè îáúåêòà) - çíà÷åíèå ðàâíî ïîðÿäêîâîìó
íîìåðó (â Ãëàâå) àíàëèçèðóåìîãî òåãà
$self->{Tag_Num_In_Curr_Page}=0;
#Ñ÷åò÷èê ÷èñëà ñòðàíèö (èíèöèàëèçèðóåòñÿ â êîíñòðóêòîðå ïðè
ñîçäàíèè îáúåêòà) - çíà÷åíèå ðàâíî ïîðÿäêîâîìó íîìåðó òåêóùåé
àíàëèçèðóåìîé Ãëàâû
$self->{Current_Page}++;

$Last_Tag_Is_Cont_Href=1;
last;
}
#Ïðîâåðêà: íàéäåííûé òåã - ãèïåðññûëêà íà îäíó èç Ãëàâ
elsif ($attr->{“href”} eq join(“”,’#’,$Temp))
{
#Ñîõðàíèòü â ñòðóêòóðå äàííûå î íàéäåííîé Âíóòðåííåé
Ãèïåðññûëêå íà îäíó èç Ãëàâ
my $Temp2 = $attr->{“href”};
$Temp2 =~ s/#//;
$self->{Pages_Info}[$self->{Current_Page}]-
>{Local_Links_To_Pages}{$Temp2}=$self->{Current_Page};
$Last_Tag_Is_Cont_Href=1;
}
}
#Ïðîâåðêà: ïîñëåäíèé íàéäåííûé òåã - íå Àíêåð Ðàçìåòêè è
íå Âíóòðåííÿÿ Ãèïåðññûëêà íà îäíó èç Ãëàâ
if ($Last_Tag_Is_Cont_Href==0)
{

#Ïðîâåðêà: íàéäåííûé òåã - àíêåð


if ($attr->{“name”} ne “”)
{
$self->{Doc_Info}->{Local_Anchors}{$attr-
>{“name”}}=$self->{Current_Page};
}

№1, октябрь 2002 39


программирование
Процедура end() - замещает процедуру end() класса Процедура Generate_Manual_Parts() - в данной про-
HTML::Parser, вызывается при нахождении стартового (на- цедуре реализован анализ статистики , собранной ранее
чального) HTML-тега. Процедура работает со всем тек- по главам и документу. На основании анализа корректи-
стом документа, «физического» разбиения на главы на руется HTML-структура глав.
данном этапе не произодится - каждая глава рассматри-
вается как составная часть документа. sub Generate_Manual_Parts
{
my $self = shift;
sub end
{ #Ïîëó÷åíèå Ãëàâ â âèäå îòäåëüíûõ ôðàãìåíòîâ HTML-êîäà è ñî-
my $self = shift; õðàíåíèå èõ â ìàññèâ
#Ïîëó÷åíèå ïàðàìåòðîâ òåãà (îïèñàíèå ñì. ðàíåå) my @Manual_Parts=$self-
($tag, $origtext)=@_; >_Separate_Manual_Into_Pages($Manual_Text);
#Ïðîâåðêà: âõîäèò ëè íàéäåííûé ñòàðòîâûé òåã â ñïèñîê òåõ #Èíèöèàëèçàöèÿ ñ÷åò÷èêà ñòðàíèö
òåãîâ, äëÿ êîòîðûõ íå îáÿçàòåëüíî óêàçûâàòü ïàðíûé êîíöåâîé my $Current_Page_Num=0;
òåã
if (!($self->Tag_Is_Exception_Check($tag))) #Äëÿ êàæäîé ïîëó÷åííîé Ãëàâû
{ for (my $i=0; $i<=$#Manual_Parts; $i++)
#Îáíîâëåíèå ñòàòèñòèêè ïî êîíöåâûì HTML-òåãàì äëÿ òåêóùåé {
Ãëàâû $Current_Page_Num=$i;
$self->{Pages_Info}[$self->{Current_Page}]->{Tags}{«$tag»}-
>{End_Tag_Num}++; #Äëÿ êàæäîãî òåãà àíàëèçèðóåìîé Ãëàâû
for (my $j=0; $j<$self->{Pages_Info}[$i]->{Num_Of_Tags};
#Îáíîâëåíèå ñòàòèñòèêè ïî êîíöåâûì HTML-òåãàì äëÿ âñåãî $j++)
Äîêóìåíòà {
$self->{Doc_Info}->{Tags}{«$tag»}->{End_Tag_Num}++; #Çàïîìíèòü èìÿ (òèï) òåãà â $Current_Tag_Name
$self->{Doc_Info}->{All_End_Tags_Num}++; my $Current_Tag_Name = $self->{Pages_Info}[$i]-
} >{Tags_Seq_Page}{$j}->{Tag_Name};
} #Ïðîâåðêà: äëÿ òåãîâ çàïîìíåííîãî òèïà êîëè÷åñòâî ñòàðòî-
âûé è êîíöåâûõ òåãîâ â Ãëàâå íå ñîâïàäàåò
Процедура _Separate_Manual_Into_Pages() - использу- if ($self->{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}-
>{Start_Tag_Num} != $self->{Pages_Info}[$i]-
ется для выборки глав из HTML-кода документа и сохране- >{Tags}{$Current_Tag_Name}->{End_Tag_Num})
ния их в массиве в виде отдельных текстовых фрагментов. {
#Ïîìåòèòü òåêóùóþ ñòðàíèöó êàê ñòàðòîâóþ â ðàìêàõ èòåðà-
При этом их HTML-структура не откорректирована. öèîííîãî ïîèñêà ïàðíîãî çàâåðøàþùåãî òåãà
Для выборки требуемых фрагментов HTML-кода ис- $self->{Work_Container}->{Start_Page_Num}=$i;
пользуются регулярные выражения. Синтаксис выраже-
ния зависит от того, какая именно глава должна быть выб- Инициализировать счетчик: его значение соответству-
рана (первая, последняя или любая из промежуточных). ет общему числу стартовых тегов - тегов запомненного
типа, обнаруженных в главах, которые будут проанализи-
sub _Separate_Manual_Into_Pages рованы на конкретной итерации поиска парного концево-
{
my $self = shift; го тега:
my $Manual_Text = shift; $self->{Work_Container}->{Start_Tag_Num_Total} = $self-
>{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}->{Start_Tag_Num};
my @Manual_Parts;

for ($i=-1; $i<=$#Array; $i++)


{ Инициализировать счетчик: его значение соответству-
#Ïðîâåðêà: âûáèðàåì èç Äîêóìåíòà ïåðâóþ Ãëàâó
if($i==-1) ет общему числу концевых тегов запомненного типа, об-
{ наруженных в главах, которые будут проанализированы
$Manual_Text =~ /(.*)<\s*a\s+name=»*$Array[$i+1]»*\s*>/
is; на конкретной итерации поиска парного концевого тега:
$Manual_Parts[$i+1]=$1;
} $self->{Work_Container}->{End_Tag_Num_Total} = $self-
#Ïðîâåðêà: âûáèðàåì èç Äîêóìåíòà ïðîìåæóòî÷íóþ Ãëàâó >{Pages_Info}[$i]->{Tags}{$Current_Tag_Name}->{End_Tag_Num};
elsif ($i<$#Array)
{
$Manual_Text =~ /(<\s*a\s+name=»*$Array[$i]»*\s*>.*<\s*\/ До тех пор пока не будет найден парный концевой тег
a\s*>.*)<\s*a\s+name=»*$Array[$i+1]»*\s*>/is;
или не будут просмотрены все главы:
$Manual_Parts[$i+1]=$1;
} >{End_Tag_Num};
#Ïðîâåðêà: âûáèðàåì èç Äîêóìåíòà ïîñëåäíþþ Ãëàâó }
else
{ #Ïðîâåðêà: êîíöåâîé òåã áûë íàéäåí
if ($Current_Page_Num <= $#Manual_Parts)
$Manual_Text =~ /(<\s*a\s+name=»*$Array[$i]»*\s*>.*<\s*\/ {
a\s*>.*)/is;
#Äëÿ âñåõ Ãëàâ ó÷àñòâîâàâøèõ â èòåàðöèîííîì ïîèñêå ïàð-
íîãî òåãà
$Manual_Parts[$i+1]=$1; for ($k=$self->{Work_Container}->{Start_Page_Num}; $k
}
} <= $Current_Page_Num; $k++)
{
#Äëÿ ñòàðòîâîé Ãëàâû èòåðàöèîííîãî ïîèñêà
#Âîçâðàùàåò ìàññèâ, êàæäûé ýëåìåíò êîòîðîãî ñîäåðæèò òåêñò if ($k == $self->{Work_Container}->{Start_Page_Num})
îäíîé èç Ãëàâ {
return @Manual_Parts; #Çàïîìíèòü ïîðÿäêîâûé íîìåð òåãà â Äîêóìåíòå, äëÿ êî-
} òîðîãî íåîáõîäèìî äîáàâèòü ïàðíûé êîíöåâîé òåã â Ãëàâå

40
программирование

push (@{$self->{Pages_Info}[$k]->{End_Tags_To_Add}}, {
$self->{Work_Container}->{Tag_Num_In_Doc}); $Manual_Parts[$i] =~ s/
$self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}- (<\s*?a\s{1}.*?href\s*?=\s*?»?)#$key(«?\s?.*?>)/$1ñòðîêà_http-
>{End_Tag_Num}++; çàïðîñà_óêàçûâàþùàÿ_ ïóòü_ñ_òî÷íîñòüþ_äî_äîêóìåíòà&page=$i$2/
$self->{Doc_Info}->{Tags}{$Current_Tag_Name}- is;
>{End_Tag_Num}++; }
}
#Äëÿ çàêëþ÷èòåëüíîé Ãëàâû èòåðàöèîííîãî ïîèñêà #Êîððåêòèðîâêà ïðî÷èõ Âíóòðåííèõ Ãèïåðññûëîê
elsif ($k == $Current_Page_Num) foreach $key (keys %{$self->{Pages_Info}[$i]->{Local_Links}})
{ {
#Çàïîìíèòü ïîðÿäêîâûé íîìåð òåãà â Äîêóìåíòå, äëÿ êî- $Manual_Parts[$i] =~ s/
òîðîãî íåîáõîäèìî äîáàâèòü ïàðíûé ñòàðòîâûé òåã â Ãëàâå (<\s*?a\s{1}.*?href\s*?=\s*?»?)(#$key»?\s?.*?>)/$1ñòðîêà_http-
push (@{$self->{Pages_Info}[$k]->{Start_Tags_To_Add}}, çàïðîñà_óêàçûâàþùàÿ_ ïóòü_ñ_òî÷íîñòüþ_äî_äîêóìåíòà&page=$self-
$self->{Work_Container}->{Tag_Num_In_Doc}); >{Doc_Info}->{Local_Anchors}{$key}$2/is;
$self->{Doc_Info}->{Tags}{$Current_Tag_Name}- }
>{Start_Tag_Num}++;
$self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}-
>{Start_Tag_Num}++; Примечание: если ограничиться таким способом при-
}
#Äëÿ ïðîìåæóòî÷íûõ Ãëàâ èòåðàöèîííîãî ïîèñêà менения элементов Local_Links и Local_Links_To_Pages из
else структуры данных Pages_Info (см. ранее), то вместо хэ-
{
#Çàïîìíèòü ïîðÿäêîâûé íîìåð òåãà â Äîêóìåíòå, ñòàðòî- шей могут быть использованы обычные списки.
âûé è êîíöåâîé òåãè êîòîðãî áóäóò äîáàâëåíû â ïðîìåæóòî÷íóþ
ñòðàíèöó
push (@{$self->{Pages_Info}[$k]->{Start_Tags_To_Add}}, Вместо заключения
$self->{Work_Container}->{Tag_Num_In_Doc});
$self->{Doc_Info}->{Tags}{$Current_Tag_Name}-
>{Start_Tag_Num}++; Возможности Perl по работе с текстом действительно
$self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}- велики. Это прописная истина, и в ней можно убеждаться
>{Start_Tag_Num}++;
снова и снова.
push (@{$self->{Pages_Info}[$k]->{End_Tags_To_Add}},
$self->{Work_Container}->{Tag_Num_In_Doc});
$self->{Doc_Info}->{Tags}{$Current_Tag_Name}- {1} Хранение крупных страниц целиком в БД - это, по-
>{End_Tag_Num}++; жалуй, не всегда является оптимальным решением. Иног-
$self->{Pages_Info}[$k]->{Tags}{$Current_Tag_Name}-
>{End_Tag_Num}++; да гораздо приятнее иметь структурированный HTML-ар-
} хив в файлах на диске, а для удобства поиска размещать
}
} результаты его индексирования в БД.
}
}
}
#Äëÿ âñåõ Ãëàâ
for (my $i=0; $i<=$#Manual_Parts; $i++)
#Âûïîëíèòü äîáàâëåíèå íåäîñòàþùèõ ñòàðòîâûõ è êîíöåâûõ òå-
ãîâ (êîððåêòèðîâêà HTML-ñòðóêòóðû)
{
@{$self->{Pages_Info}[$i]->{Start_Tags_To_Add}}= sort {$b
<=> $a} @{$self->{Pages_Info}[$i]->{Start_Tags_To_Add}};
@{$self->{Pages_Info}[$i]->{End_Tags_To_Add}} = sort {$b
<=> $a} @{$self->{Pages_Info}[$i]->{End_Tags_To_Add}};
#Äîáàâèòü ñòàðòîâûå òåãè
foreach my $value (@{$self->{Pages_Info}[$i]-
>{Start_Tags_To_Add}})
{
$Manual_Parts[$i]=join (“”, $self->{Doc_Info}-
>{Tags_Seq_Doc}{$value}->{Tag_Orig_Text}, $Manual_Parts[$i]);
}

#Äîáàâèòü êîíöåâûå òåãè


foreach my $value (@{$self->{Pages_Info}[$i]-
>{End_Tags_To_Add}})
{
$Manual_Parts[$i]=$Manual_Parts[$i].’</’.$self-
>{Doc_Info}->{Tags_Seq_Doc}{$value}->{Tag_Name}.’>’;
}
}
#Âåðíóòü Ãëàâû ñ îòêîððåêòèðîâàííîé HTML-ñòðóêòóðîé
return @Manual_Parts;
}

Ранее при постановке задачи было замечено, что кор-


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

Êîððåêòèðîâêà Âíóòðåííèõ Ãèïåðññûëîê íà Ãëàâû


foreach $key (keys %{$self->{Pages_Info}[$i]-
>{Local_Links_To_Pages}})

№1, октябрь 2002 41


программирование

ЭФФЕКТИВНОЕ

ИСПОЛЬЗОВАНИЕ ПАМЯТИ В PERL ПРИ РАБОТЕ С


БОЛЬШИМИ СТРОКАМИ
ДАНИИЛ АЛИЕВСКИЙ
Обычно при программировании в сложный скрипт, предназначенный мы, возникающие в Perl при работе
Perl не приходится задумываться о для обработки и парсинга произ- с большими данными, и нашел спо-
расходе памяти. Этот язык содержит вольных HTML-страниц. Основным собы их решения. После соответ-
достаточно качественную систему типом данных в скрипте были обыч- ствующего переписывания, мой
сборки мусора. Кроме того, при ис- ные текстовые строки. Поначалу я скрипт стал потреблять адекватное
полнении Perl-программ как обыч- обращался со строками очень сво- количество памяти, а утечка памя-
ных CGI-сценариев, с запуском ин- бодно, как это и принято в Perl и по- ти прекратилась.
терпретатора Perl на каждое обра- добных языках, не задумываясь Результаты своих исследований
щение к скрипту, вся использован- пользовался функцией substr; конка- я предлагаю вашему вниманию.
ная память гарантированно осво- тенацией строк; регулярными выра- Итак, имеют место 2 основные
бождается при завершении скрипта. жениями; писал функции, возвраща- общие проблемы.
Но если Perl-скрипт обрабатыва- ющие в результате строку (HTML-
ет действительно большие дан- текст Web-страницы), и т.п. Приве- ПРОБЛЕМА I
ные, — скажем, мегабайтные тексто- ло это к тому, что типичный HTTPD-
вые файлы — проблема разумного процесс с mod_perl'ом (Web-сервер Свободное употребление Perl-
использования памяти может стать Apache на Unix) тратил только на об- средств для работы со строками -
достаточно актуальной. Особенно рабатываемые данные в среднем regexp'ов, substr, конкатенаций типа
это важно, если скрипт исполняется несколько мегабайт. Это при том, $a.$b или "$a$b" - приводит к порож-
под управлением mod_perl или ана- что типичный размер HTML-страни- дению лишних копий строки, т.е.
логичной среды. Если всецело поло- цы, которую следовало обработать, там, где по логике вещей алгоритму
житься на встроенный сборщик му- составлял всего 20-30 KB. А когда я должно хватить 2 MB, будет потра-
сора, может неожиданно оказаться, попробовал "пропустить" через свою чено 5 или 10 MB.
что процессы Web-сервера, исполня- программу 10-мегабайтный HTML -
ющие скрипты с помощью mod_perl, HTTPD-процесс "съел" 100 MB. При ПРОБЛЕМА II
с каждым вызовом начинают зани- этом возникало впечатление утечки
мать все больше памяти - вплоть до памяти - процессы, по мере своей Если не предпринять специаль-
десятков мегабайт, постепенно по- "жизни", занимали все больше и ных усилий, то после завершения
глощая всю свободную RAM. больше обьема памяти. Perl-функции рабочая память, израс-
Я столкнулся с этой проблемой, В процессе тестирования и экс- ходованная в этой функции, НЕ БУ-
когда реализовывал под mod_perl периментов я выявил общие пробле- ДЕТ освобождена! (Ситуация совер-

42
программирование
шенно отличная от традиционной 1.Как завести внутри undef $$v; #îñâîáîæäàåì ïàìÿòü, îòâå-
практики в языках без сборки мусо- функции большую временную äåííóþ ôóíêöèåé a
ра типа C++ или Pascal, когда все ра- текстовую переменную, а
бочие переменные, созданные внут- перед выходом из функции Такой код "съест" только 1 мега-
ри функции, уничтожаются при вы- освободить память из-под байт, который освободится при вызо-
ходе из функции.) нее? ве undef.
Это не так важно в обычном CGI- Неправильное решение: Проблема на самом деле доволь-
скрипте, исполняемом внешним ин- но общая: никогда не следует писать
терпретатором Perl. По завершении sub a { выражение, результат которого - боль-
my $text= "very large string.... (1 MB)";
скрипта процесс будет полностью ðàáîòàåì ñ $text; шая строка. Нельзя писать даже так:
уничтожен вместе со всей своей па- #ïðîñòî âûõîäèì èç ôóíêöèè, ïðåäïîëà-
ãàÿ,
мятью. Но в mod_perl или FastCGI, #÷òî ñáîðùèê ìóñîðà àâòîìàòè÷åñêè îñâî- my $v= $text."\n";
или в независимых приложениях, áîäèò
#ïàìÿòü èç-ïîä $text (êàê ýòî ïðîèñõî-
или серверах на Perl это очень су- äèò если строка $text потенциально может
щественно. #ñî ñòåêîâûìè ïåðåìåííûìè â C++ è Pascal) быть большой (десятки килобайт или
}
Обратите внимание - описанная больше).
проблема НЕ ЕСТЬ истинная утеч- Правильное решение - добавить
ка памяти. Встроенный сборщик му- перед выходом вызов undef: 3.Как передать большую
сора действительно обеспечивает строку в функцию?
утилизацию ненужных переменных. sub a { Неправильное решение:
my $text= "very large string.... (1 MB)";
Просто он делает это не совсем так, ðàáîòàåì ñ $text;
как можно было бы ожидать. А имен- undef $text; sub a {
} my $text= $_[0]; #ïàðàìåòð $_[0]
но: занятая память будет использо- ñîäåðæèò ñòðîêó äëèíîé 1 MB
вана повторно ПРИ СЛЕДУЮЩЕМ Вызов undef освободит память, за- ðàáîòàåì ñ $text;
undef $text;
ВЫЗОВЕ той же самой функции, т.е. нятую переменной $text. Без такого }
многократные повторные вызовы вызова получаем общую проблему II). my $text= "very large string.... (1 MB)";
a($text);
функции не будут приводить к посте-
пенному исчерпанию RAM - явле- 2.Функция должна создать В этом примере общей проблемы II
нию, которое традиционно называ- большую строку и вернуть ее нет, но память расходуется напрасно.
ется утечкой памяти. Зато много- в результате. Оператор присваивания $text= $_[0]
кратные вызовы приведут к друго- Неправильное решение: расходует второй мегабайт под копию
му: со временем будет занят наи- $text переменной $_[0] (который осво-
больший объем памяти из всех, ко- sub a { бождается в конце вызовом "undef").
my $text= "very large string.... (1 MB)";
торые были нужны при различных return $text; Если есть возможность, лучше ра-
вариантах вызова этой функции. В } ботать непосредственно с $_[0] - т.е.
my $v= a();
моем случае, после того как мои ðàáîòàåì ñ $v; с алиасом внешней переменной. А
Perl-функции один раз обработали еще лучше - нагляднее - всегда пере-
HTML-страницу размером 10 MB и Такой Perl-код "съест" не 1 мега- давать большие строки по ссылке.
соответствующий процесс с байт, действительно необходимый Предлагаемое правильное реше-
mod_perl "съел" 100 MB, он так и про- для сохранения переменной $v, а 2 ние:
должал всегда занимать 100 MB, мегабайта. Лишний мегабайт будет
хотя все последующие обрабатыва- занят интерпертатором Perl при вы- sub a {
my $text= $_[0]; #ïàðàìåòð $_[0]
емые страницы были небольшими. числении строкового выражения "a()" ñîäåðæèò ÑÑÛËÊÓ íà ñòðîêó
Внешне такое поведение очень по- для последующего копирования этих ðàáîòàåì ñ $$text;
}
хоже на утечку - объем памяти, за- данных в переменную $v. my $text= "very large string.... (1 MB)";
нятый процессом, никогда не умень- Мегабайт, занятый $v, можно впос- a(\$text);
шается, но постепенно медленно ледствии освободить вызовом "undef
увеличивается - по мере того как $v", но мегабайт, занятый при вычис- 4.Как выполнить
этому процессу случайно попадают- лении строкового выражения в пра- конкатенацию нескольких
ся данные все большего размера. вой части, по-моему, уже не освобо- строк, одна из которых
Теперь рассмотрим конкретные дить никак. может быть очень большой?
типовые задачи, возникающие при Правильное решение - функция Неправильное решение:
обработке данных в Perl. Я приведу должна вернуть ссылку на созданную
примеры традиционного решения большую строку: my $newtext= "$a$text$b";
этих задач - неправильного в свете
описанных проблем - и возможные sub a { или
my $text= "very large string.... (1 MB)";
варианты аккуратного решения, не return \$text;
приводящие к перерасходу памяти. } my $newtext= $a.$text.$b;
my $v= a();
ðàáîòàåì ñ $$v;

№1, октябрь 2002 43


программирование
Если строка $text велика, то подоб- кого и изящного решения. Возможный привело бы к напрасному расходу па-
ный код "съест" память, которую корректный подход использует следу- мяти под лишнюю копию исходной
нельзя освободить (см. задачу 2). ющую функцию substrlarge: строки (см. также задачу 3).
Правильное решение - конкатени- С использованием функции
ровать по очереди: sub substrlarge { substrlarge правильное решение будет
# - Returns a reference to
my $newtext= $a; substr($_[0],$_[1],$_[2]) таким:
$newtext.= $text; # and doesn't use extra memory when
$len is very large my $text= "very large string.... (1 MB)";
$newtext.= $b; my $v= substrlarge($text,100000,500000);
# Example:
# my $ps= ðàáîòàåì ñ $$v;
substrlarge($text,500,1000000);
5.Как удалить/заместить # some actions with $$ps;
небольшую подстроку в # undef $$ps; 7.При использовании
# - it is an economical equivalent for
очень большой строке? # my $s= substr($text,500,1000000); регулярных выражений, с
Неправильное решение: # some actions with $s; большой строкой нельзя
my $offset= $_[1];
my $len= $_[2]; генерировать переменные
$len= length($_[0])-$offset unless $1,$2 и пр. Скажем,
my $text= "very large string.... (1 MB)"; defined $len;
$text= substr($text,10); if ($len*2<length($_[0])) { следующий код
my $k= 0; неэффективен:
my $r= "";
Такой код потратит лишний нео- for (;$k<$len;$k+=32768) {
$r.= my $text= "very large\012\012 string....
свобождаемый мегабайт при вычис- (1 MB)";
substr($_[0],$offset+$k,$k+32768<=$len?32768:$len-
лении выражения substr($text,10) - см. $k); $text=~s/^(.*?\015?\012\015?\012)//s;
} my $prefix= $1; #ïðåäïîëàãàåòñÿ, ÷òî
задачу 2. ýòîò ïðåôèêñ íåâåëèê
return \$r;
Правильное решение - использо- } else {
вать так называемую "магию lvalue": my $r= $_[0]; Хотя от этого регулярного выраже-
substr($r,0,$offset)= "";
substr($r,$len)= "" if defined $_[2]; ния нам требуется, очевидно, только
my $text= "very large string.... (1 MB)"; return \$r; префикс строки $1, который может
substr($text,0,10)= ""; }
} быть и небольшим, Perl все равно за-
Правда, в документации написано, полнит переменные $&, $` и $'. А одна
что Perl 5.004 в этом случае работал Если нужно выделить сравнитель- из них будет большой - сравнимой с
неэффективно. Но начиная с Perl но небольшой фрагмент исходной самой $text. Причем память из-под
5.005 это работает прекрасно: лишняя строки (в данной реализации - мень- этих переменных автоматически не
память не расходуется. ше половины общей длины), то нуж- освободится.
Эквивалентное правильное реше- ный фрагмент конструируется циклом, Здесь единственное известное
ние - использовать 4-й параметр блоками по 32 KB. Потеря памяти при мне правильное решение - избегать
substr: этом составляет порядка 32 KB - применения регулярных выражений к
столько расходует вычисление выра- потенциально большим строкам. В
my $text= "very large string.... (1 MB)"; жения "substr($_[0],...)" внутри цикла. данном случае можно было написать
substr($text,0,10,"");
Если же требуется получить боль- цикл поиска пары переводов строки
Но если предыдущий вариант в шой фрагмент - больше половины на основе вызовов функции index.
Perl 5.004 работает неэффективно, то исходной строки - то используется Можно также пользоваться "стати-
такой вариант в Perl 5.004 вообще не иной, более быстрый алгоритм. Со- ческими" регулярными выражениями -
скомпилируется. здается полная копия исходной стро- не использующими скобок (или ис-
ки, после чего у нее обрезаются ко- пользующими только (?:...) ). Такие
6.Как выделить в большой нец и начало, как описано в задаче 5. регулярные выражения не заполняют
строке большую подстроку? При этом временно занимается па- переменных $1,$2,...,$&,$`,$' и соответ-
Скажем, как в мегабайтной мять под целую копию, но затем - при ственно не расходуют много памяти.
строке выделить обрезании - занятый объем уменьша-
полумегабайтную подстроку, ется. Так как требуемый объем памя- 8.Нужно прочитать из файла
начиная со смещения ти под конечный результат в данной или сокета большой текст.
100,000? ветке сравним с размером полной Типичное решение выглядит при-
По описанным выше причинам сле- копии, то кратковременный расход мерно так:
дующий очевидный код неправилен: памяти под полную копию представ-
ляется разумной платой за более вы- my $text= "";
my $text= "very large string.... (1 MB)"; for (;åñòü ÷òî ÷èòàòü;) {
сокую скорость. my $buf= ÷èòàåì î÷åðåäíûå 32 KB;
$text= substr($text,100000,500000);
Обратите внимание: функция $text.= $buf;
undef $buf;
В таком решении при вычислении substrlarge работает непосредственно }
"substr($text,100000,500000)" расходу- с аргументом $_[0], не копируя его во ðàáîòàåì ñ $text;
undef $text;
ются лишние полмегабайта, которые временную переменную - как это
впоследствии невозможно освободить. обычно делается в начале Perl-функ- Хотя на вид этот код вполне акку-
Для этой задачи я не нашел крат- ций. Копирование типа "my $s= $_[0]" ратный и следует приведенным выше

44
программирование
рекомендациям, на самом деле он на NT 4.0 и в стандартном Perl из или в любом другом смысле - никогда
все-таки может привести к проблеме. FreeBSD 4.2. Под ActivePerl 5.6 в не стоит полностью полагаться на до-
А именно, если общий объем читае- Windows 2000 все оказалось несколь- кументацию, общие рекомендации и
мого текста порядка 1 MB, то в про- ко хуже: undef не освобождает память. советы. В том числе, приведенные в
цессе чтения в пике может израсхо- (По крайней мере, TaskManager не по- этой статье. Всегда измеряйте эффек-
доваться не 1, а 2 мегабайта. Второй казывает сокращения памяти у процес- тивность сами! Если реальная эффек-
мегабайт потом обычно освобождает- са Perl, пока длится 10-секундный sleep, тивность программы не соответствует
ся, но не гарантированно. следующий за вызовом undef.) Впро- вашим априорным оценкам, ищите "уз-
Эта тонкая проблема, по-видимо- чем, к моменту, когда вы будете читать кое место" - тот "плохой оператор", ко-
му, связана с механикой переотведе- эту статью, возможно, этот недостаток торый отвечает за перерасход памяти
ния памяти в Perl. Оператор "$text.= уже будет исправлен фирмой или долгое выполнение. После чего
$buf" время от времени увеличивает ActiveState. создайте тест - минимальную програм-
память, занятую переменной $text. В В завершение хотелось бы сделать му, в которой "плохой оператор" прояв-
процессе такого переотведения ин- небольшое замечание. Если вас дей- ляет свои скверные качества, - и ищи-
терпретатору Perl, вероятно, требует- ствительно интересует эффективность те более качественное эквивалентное
ся двойной объем памяти: под пре- работы вашей программы - в плане эко- решение. Именно так были найдены все
жнюю строку $text и под новый, уве- номии памяти, в плане быстродействия описанные выше приемы.
личенный буфер для этой перемен-
ной. В этот момент процесс и занима-
ет лишний мегабайт. Видимо, если
переотведение происходит в конце
цикла, второй мегабайт может и не
освободиться: в соответствии в общей
идеологией Perl "запасать буфера
памяти на будущее повторное исполь-
зование".
Правильное решение описанной
задачи - взять отведение памяти на
себя. Например:

àêêóðàòíî îòâåñòè ïîä $text 1 MB (1000000


áàéòîâ);
for ($n=0; åñòü ÷òî ÷èòàòü; $n+=32768)
{
my $buf= ÷èòàåì î÷åðåäíûå 32 KB;
substr($text,$n,32768)= $buf; #"ìàãèÿ
lvalue"
}
if ($n<1000000) {
substr($text,$n)=""; #î÷èùàåì íåíóæíûé
"õâîñò" $text
}

Если заранее неизвестно, что


предстоит читать именно 1 MB, мож-
но изредка (именно изредка!) аккурат-
но выполнять самостоятельное пере-
отведение памяти.
Для аккуратного отведения памя-
ти можно предложить один из следу-
ющих приемов:

$text=" "; $text x= 1000000;


ëèáî
$text=" "; vec($text,1000000-1,8)= 32;
#êîä ïðîáåëà; ìîæíî èñïîëüçîâàòü ëþáîé
äðóãîé ñèìâîë

Оба способа отводят ровно 1000000


байтов памяти, ничего не тратя зря. Вто-
рой способ ("магия lvalue" для функции
vec) можно использовать также для пе-
реотведения памяти. ©IGUS
Все вышеописанное протестирова-
но и неплохо работает в ActivePerl 5.005

№1, октябрь 2002 45


программирование

«СТЕММЕР»
МОРФОЛОГИЧЕСКИЙ
АНАЛИЗ
ДЛЯ НЕБОЛЬШИХ ПОИСКОВЫХ СИСТЕМ
Сейчас уже никого не удивишь поисковой системой со встроенным
русским, украинским или английским морфологическим анализатором,
однако такой модуль достаточно дорог, и использование его в небольших
продуктах не всегда коммерчески оправданно.

Поисковые и не только поисковые Один достаточно иллюстративный та под конкретного заказчика, когда
системы Интернет столь популярны диалог произошел на выставке бюджет проекта не сильно превыша-
сегодня, что люди проводят часы, об- SofTool в 1995 году. Компания «Ага- ет эту сумму - не самая лучшая идея.
суждая достоинства и недостатки той ма», где я в тот момент работал, пред- Что же делать в такой ситуации?
или иной, алгоритмы и программы для ставляла одноименную систему поис- Есть два решения. Первое - не исполь-
поиска полнотекстовой информации ка информации, размещенной на ло- зовать лингвистических алгоритмов
на тех или иных носителях. И все это кальных дисках компьютера. Надо вообще, если это возможно. Второе -
время не утихают горячие споры «про- сказать, эта поисковая система уже остановиться на стемминге, то есть на
фи» поиска и «ленивых пользовате- тогда, несмотря на убогость DOS, под формальном выделении основы - ста-
лей». Первые - сторонники чисто «ме- управлением коего она работала, уже бильной, графически неизменной при
ханических» машин, поисковых сис- делала полноценный словарный мор- склонении или спряжении, части сло-
тем, которые вычисляют строгие ло- фологический анализ обрабатывае- ва.
гические запросы и поддерживают мых текстов и поисковых запросов, то Программы, выполняющие стем-
усечение слова справа «звёздочкой»; есть умела искать с учетом всех форм минг, или стеммеры, также существу-
они убеждены, что лучше всяких ал- русских слов. И вот одна дама, про- ют примерно столько же, сколько и
горитмов сформулируют, что же им тестировав систему и побеседовав о поисковые системы. И, надо отметить,
нужно найти. Другие - наоборот, ста- морфологической обработке текстов в случае английского языка, (доста-
раются отдать на откуп алгоритмам с господином Пархоменко, идеологом точно простого и не склонного к из-
поисковика все магические преобра- и руководителем проекта, воскликну- лишней флективности, то есть измен-
зования исходного запроса и не за- ла на весь зал: «Да ведь это никому чивости слов), стеммеры успешно
думываться о том, что же там проис- не нужно! Я сама прекрасно все най- справляются с поставленной перед
ходит внутри. ду, дайте мне только оператор усече- ними задачей. Так, классический
Обе точки зрения имеют право на ния!». На беду, оператор усечения, «портеровский» алгоритм, хоть он и
существование, и я даже знаю не- как и весь традиционный набор логи- грешит сведением упоминавшейся
скольких таких профи, которые спо- ческих операторов, в языке запросов аббревиатуры DOS к формальной
собны действительно грамотно сфор- присутствовал. После получаса стуча- основе - глаголу do, тем не менее дает
мулировать логический запрос на по- ния по клавишам дама со словами: вполне корректное значение отноше-
иск нужной информации, корректно «Вы меня не убедили!», - покинула ния «сигнал/шум» в случае поиска ин-
расставив скобки и применив соответ- стенд. формации.
ствующие усечения. Однако подавля- Действительно, лицензировать Хуже обстоит дело со славянски-
ющее большинство пользователей, к систему морфологического анализа ми языками. Выделить правила, по
коим я отношу и себя, все-таки ско- за несколько тысяч долларов для ис- которым можно отсечь часть слова
рее «лентяи». пользования ее в разработке продук- справа, да так, чтобы не породить

46
программирование
сильного шума, очень сложно, и по- внутренним соображениям. Именно в ловии, что оно встретилось после
строение такого набора правил срав- силу указанных недостатков и было фрагмента -ор-.
нимо по трудоемкости с построением принято решение о разработке свое- Далее была изготовлена програм-
полноценного словарного морфологи- го алгоритма и правил стемминга. ма обработки массивов полнотексто-
ческого анализатора, чем обычно и Отмечу сразу, что мыслей разра- вой информации, которая, разбив
заканчиваются доведенные до логи- батывать вручную правила усечения текст на слова, выполняла обработку
ческого завершения разработки в русских слов не было даже изначаль- очередной потенциальной словофор-
этой области. Приведем простой при- но, но было большое желание изго- мы точным морфологическим анали-
мер - слово кровать. До сих пор все товить алгоритм, который учитывал затором; при этом неизвестные сло-
программы стемминга для русского бы и сочетаемость букв в слове на варному анализатору строки игнори-
языка успешно и совершенно логич- границе возможного усечения, и час- ровались, что является допустимой
но признавали это слово изменяю- тотность такой модели словоизмене- погрешностью, поскольку, имея базу
щимся по модели глагола, выделяя ния. Поэтому было принято решение более 150,000 основ и распознавая
или формальную основу «кр» или в строить правила стемминга автомати- более четырех миллионов различных
лучше случае, «кров». Уважаемый чи- чески, обрабатывая большие масси- форм русских слов, анализатор игно-
татель, мне кажется, по достоинству вы русских текстов. Задача сильно об- рирует менее одного процента встре-
оценит такой глагол - «я крую/кроваю, легчалась наличием русского морфо- тившихся строк, которые по большей
он кровает/круёт». логического анализатора, разрабо- части оказываются либо орфографи-
Однако за истекший год в этом танного еще в 1994 году во время ра- ческими ошибками, либо аббревиату-
направлении были сделаны серьез- боты над проектом «Пропись 4.0». рами, либо экзотическими названия-
ные шаги. Мартин Портер (Martin Анализатор этот работает и поныне в ми или именами собственными. Для
Porter) сделал доступными свои нара- поисковых системах Апорт! опознанных же словоформ выделя-
ботки в области стемминга, наборы (www.aport.ru),Рамблер лась их точная основа, то есть часть
правил и инструменты для работы с (www.rambler.ru), <META> (www.meta- слова, остающаяся неизменной при
ними, для открытого сетевого сооб- ukraine.com), хотя и пережил несколь- склонении или спряжении; выделен-
щества, опубликовав проект на ко преобразований, в результате чего ное таким способом окончание вкупе
S o u r c e F o r g e ( h t t p : / / словарь уже придирчиво выверен, а с последними двумя символами фор-
snowball.sourceforge.net/).Проект код позволяет обрабатывать до 20 - мальной основы поступало в накопи-
представляет собой специализиро- 30 тысяч слов в секунду. Подробнее с тель, который либо регистрировал
ванный язык обработки строк, пред- этим анализатором и условиями его новое правило, либо увеличивал вес
назначенный для изготовления алго- распространения можно ознакомить- уже существующего правила отщеп-
ритмов стемминга в информационном ся на странице автора (linguist.nm.ru). ления окончания.
поиске. Довольно быстро проект стал Для представления автоматичес- По завершении работы сканера
расширяться, и сейчас доступны в ких правил усечения слов была выб- текстов получившийся массив данных
исходных текстах стеммеры для анг- рана модель хранения возможного был отранжирован в соответствии с
лийского, французского, испанского, окончания с двумя предшествующи- убыванием вероятности встретить
португальского, итальянского, немец- ми буквами неизменяемой части сло- каждую из присутствующих моделей
кого, датского, шведского и норвежс- ва. Так, например, словоформа сло- словоизменения, после чего модели,
кого языков. Русский поначалу остал- варями порождает правило, разреша- вероятность реализации которых со-
ся в стороне, однако недавно появи- ющее отщепление окончания -ями ставляла менее 10-4, были отброше-
лась поддержка и для него (http:// при условии, что ему предшествует ны как редкие и потенциально опас-
snowball.sourceforge.net/russian/ последовательность -ар-. Аналогично, ные, т. е. способные породить избы-
stemmer.html). встретив словоформу морями, мы по- точный шум.
Тем не менее, по ряду причин, родим правило о возможном отщеп- Результат - набор потенциальных
часть из которых обсуждалась выше, лении того же окончания (-ями) при ус- окончаний с условиями на предше-
стеммер Snowball все равно далек от
идеала. И основная причина заклю-
чается в том, что он базируется на
обобщенных правилах, составленных
людьми, в лучшем случае - лингвис-
тами. А как известно, любое общее
правило имеет исключения, приводя-
щие в данном случае к неправильно-
му выделению основы. Еще одно на-
рекание - это «убежденность алгорит-
ма в собственной непогрешимости»:
алгоритм всегда дает один-един-
ственный вариант формальной осно-
вы слова, игнорируя все остальные по

№1, октябрь 2002 47


программирование
ствующие символы - был инвертиро- но дополнительное правило, ограни- размерностью 256 байт. Если этот
ван для удобства сканирования сло- чивающее свободу алгоритма. Суть параметр не задан, то по умолчанию
воформ «справа налево» и представ- правила состоит в том, что формаль- используется встроенная таблица
лен в виде таблицы переходов конеч- ная основа слова должна содержать преобразования символов верхнего
ного автомата. Подробнее устройство хотя бы одну гласную, иначе возмож- регистра кодовой страницы Windows
таких таблиц описано в статье о сло- но построение весьма некорректных Cyrillic в символы нижнего регистра.
варном морфологическом анализато- основ, как, например, усечение сло- Хочется отметить, что весной 2002
ре, доступной по адресу http:// ва спам до основы сп, что, как извес- года в лаборатории общей и компью-
linguist.nm.ru/ling/rus/help.htm. Мы же тно, является распространенной абб- терной лексикографии кафедры рус-
лишь покажем на рисунке фрагмент ревиатурой словосочетания «совме- ского языка Московского Государ-
такой таблицы, построенный для не- стное предприятие». Интересно, что ственного университета было прове-
большого набора окончаний и усло- большинство стеммеров, в том числе дено сравнительное тестирование
вий на предшествующие символы ос- и используемый в поисковой системе трех программ русского стемминга:
новы. В качестве примера выберем Яndex (www.yandex.ru) для обработки Snowball, описываемого алгоритма и
уже упоминавшиеся формально пост- неизвестных слов, грешат в подобных стеммера, разработанного там же под
роенные правила: (-ар-)-ями, (-ор-)- ситуациях, в чем несложно убедить- руководством докторов филологичес-
ями, добавив к ним для наглядности ся, дав соответствующий запрос. ких наук А. А. Поликарпова и О. В.
еще два - (-ск-)-и и (-сн-)-а, постро- Для обеспечения должной произ- Кукушкиной. По результатам тестиро-
енные, например, по словоформам водительности анализатор не строит вания описываемый стеммер показал
диски и весна. строк, а лишь возвращает набор воз- наиболее корректные в лингвистичес-
Далее был разработан переноси- можных длин формальных основ, ко- ком отношении результаты при мак-
мый программный код на языке C, торые можно выделить из строки. симальной полноте и минимальном
обеспечивающий сканирование пода- Инициализация модуля также не тре- шуме, то есть количестве неверно
ваемых на вход форм слов на полу- буется, так как все таблицы перехо- выделенных основ.
ченных таблицах переходов. Особо дов представлены статическими дан- Позже описанным способом был
следует отметить, что код этот не бе- ными. разработан также словарь стеммин-
рет на себя ответственность за выбор Особое внимание пришлось уде- га украинского языка, который вмес-
конкретного способа усечения, но лить поддержке различных кодовых те с русским успешно применяется
лишь строит все допустимые вариан- страниц и возможной капитализации для анализа неизвестных слов в ук-
ты выделения формальной основы. - начертанию слов в верхнем регист- раинской поисковой системе <META>
Так, для словоформы начинающих- ре. Действительно, при всей привле- (www.meta-ukraine.com), а также изго-
ся анализатор выделит формальные кательности кодировки 1251 Windows товлена программа, позволяющая
основы длиной 6, 8 и 10 символов, что Cyrillic и удобстве работы с ней, она автоматически строить таблицы стем-
соответствует глаголу начина-ть, при- не является ни единственной, ни даже минга для других языков из словарей
частию начинающ-ий и возвратной самой популярной. Преобразовывать ISpell, доступных в сети Интернет.
форме - начанающий-ся, то есть вы- же строку из иных кодировок в жела- Однако следует оговориться, что в
делит псевдоморфемы, участвующие емую перед анализом весьма и весь- последнем случае качество получае-
в образовании этого слова. Програм- ма дорого по меркам «скорострель- мого словаря напрямую зависит от
ма-клиент, например поисковая ма- ных» алгоритмов. Для обеспечения качества используемых материалов,
шина, вправе либо самостоятельно совместимости анализатора с текста- которое чаще всего весьма низко.
принять решение о выборе того или ми в различных кодировках предус- Представленный продукт доступен
иного варианта усечения, либо запом- мотрена возможность его настройки в исходных текстах и может исполь-
нить все построенные варианты, что передачей дополнительного парамет- зоваться в свободной форме с усло-
обеспечит максимальную полноту по- ра - таблицы преобразования кодов вием ссылки на источник. Архив мож-
иска. С другой стороны, для много- символов из произвольной кодиров- но загрузить по адресу http://
страдального слова кровать обучен- ки в нижний регистр кодировки 1251 linguist.nm.ru/stemka/stemka.tar.gz.
ный таким образом алгоритм выделит
лишь одну формальную основу - ше- Зовут меня Андрей Коваленко, я работаю в на-
стибуквенную последовательность стоящий момент в компании Рамблер, занимаюсь
кроват-ь, что полностью соответству- развитием поисковой машины, до этого работал в
ет истине. В ряде ситуаций анализа- МедиаЛингве, еще раньше в Агаме, где занимался
тор принимает решение о невозмож- разработкой поисковой системы Апорт!.
ности какого-либо усечения по данной
последовательности, как, например, За несколько лет занятий прикладной лингвис-
в случае слова компьютер, что так- тикой у меня накопилось некоторое количество на-
же полностью соответствует действи- работок, которыми я бы с удовольствием поделил-
тельности. ся с другими разработчиками. Кое-что доступно бес-
По ходу тестирования и отладки платно, кое-что - только в виде демонстрационных
построенной технологии было введе- версий.

48
ОПРЕДЕЛЕНИЕ ©1990
Графический лист из серии Художник Игорь УСКОВ
«Смыслограммы» www.igus.nm.ru
JAVA:
МАГИЯ ОТРАЖЕНИЙ

ЧАСТЬ I. ОСНОВЫ

ДАНИИЛ АЛИЕВСКИЙ
программирование
Один из самых удивительных и ярких механизмов языка но узнать списки членов класса — полей, методов, конст-
Java — технология «отражения» (Java Reflection). К сожале- рукторов, а также обратиться к этим членам. Кроме того,
нию, в популярных учебниках нелегко найти подробную ин- класс Class содержит ряд статических методов, обеспечи-
формацию об этой интереснейшей области. А тем более — вающих взаимодействие с внутренними механизмами Java,
о подводных камнях и неожиданных возможностях, возни- отвечающими за загрузку и управление классами.
кающих при программировании с использованием отраже- Как получить экземпляр Class, соответствующий дан-
ний. Между тем, именно отражения позволяют Java с непод- ному классу (или интерфейсу) — например, классу
ражаемым изяществом справляться с задачами, традици- java.io.File? Для этого есть два основных способа.
онно непростыми в других языках — такими, как создание A. Просто добавляем к имени класса суффикс «.class»,
оболочки для компиляции Java-проектов (наподобие Borland например:
JavaBuilder или NetBeans), визуальное проектирование гра-
фических компонентов (JavaBeans), сериализация объектов Class clazz= byte.class
и распределенные вычисления (RMI), и многие другие.
Мне бы хотелось предложить Вам небольшую экскур- («clazz» — сознательно искаженное от «class»: компиля-
сию по миру отражений Java. По ходу дела я буду пока- тор не позволяет использовать в качестве идентификато-
зывать, как использовать мощь этой технологии для изящ- ра зарезервированное слово «class».)
ного решения различных практических задач. B. Если мы располагаем экземпляром некоторого клас-
Мы начнем с простых вещей — простых для Java, но не са, может быть даже неизвестного в данной точке програм-
столь тривиальных в других языках. Пример такой «простой мы, можно вызвать метод getClass(), присутствующий в
вещи» — динамическое расширение вашей программы но- каждом Java-объекте (унаследованный от класса Object):
выми классами от сторонних разработчиков, не требущее
переписывания и даже перекомпиляции вашей системы. По void myFunction(Object o) {
Class clazz= o.getClass();
мере знакомства с отражениями, задачи будут усложнять- ÷òî-òî äåëàåì ñ îáüåêòîì clazz;
ся. В качестве завершающего примера, полностью исполь- }
...
зующего возможности отражений, я расскажу, как реализо- java.io.File f= new java.io.File("/tmp/1.txt");
вать высокоэффективный интерпертатор выражения (фор- myFunction(f);
...
мулы), написанного на языке Java — эквивалент оператора
eval() в скриптовых языках типа Perl или JavaScript.
Все написанное ниже относится к последней (на мо- Здесь есть любопытный нюанс. Вообще-то, примитивные
мент написания статьи) версии Java: Sun Java SDK 1.4. типы Java — boolean, char, byte, short, int, long, float, double —
обычно не считаются полноценными классами. Они не унас-
Где искать мир отражений? ледованы от Object, для них не работает наследование и т.д.
Отражения в Java — это два класса Class и Тем не менее, с ними тоже ассоциированы экземпляры Class,
ClassLoader, расположенных в пакете java.lang, и специ- которые можно получить способом A, например:
альный пакет java.lang.reflect, содержащий (в версии Java
SDK 1.4) 12 вспомогательных классов: Array, Member, Class clazz= java.io.File.class;
Constructor, Field, Method, Modifier, InvocationHandler, Proxy,
ReflectAccess, ReflectPermission, InvocationTargetException, Существует даже специальный объект void.class — он
UndeclaredThrowableExceptioCfn. используется в довольно экзотических ситуациях при вы-
Проще всего осваивать технику отражений, начиная с зове методов через отражения. Экземпляры типа Class
класса Class. Более сложные вещи потребуют применения есть также у любого массива, например:
классов из пакета java.lang.reflect, прежде всего классов,
описывающих: Constructor, Field и Method. «Высший пило- Class clazz= byte[].class
èëè
таж» работы с отражениями — это, пожалуй, создание гра- byte[] v= new byte[34]; Class clazz= v.getClass();
мотных наследников ClassLoader, позволяющих реализовать
собственную систему загрузки классов. Что же можно сделать, располагая переменной типа
В этой статье я покажу, как грамотно использовать эти Class для некоторого класса?
классы и их методы. Прежде всего, можно получить полное имя класса (ска-
В любом случае, статья не заменяет справочную до- жем, для отладочной печати) методом getName(). Например:
кументацию фирмы Sun. Самую полную и свежую фир- String.class.getName() возвращает «java.lang.String».
менную документацию можно найти в Internet по адресу: Интересно посмотреть на имена классов для прими-
http://java.sun.com/ тивных типов и для массивов:

Класс по имени Class float.class.getName() âîçâðàùàåò «float»


float[].class.getName() âîçâðàùàåò «[F»
В мире отражений все начинается с класса float[][].class.getName() âîçâðàùàåò «[[F»
«java.lang.Class». Для каждого Java-класса или интерфей- String[].class.getName() âîçâðàùàåò [Ljava.lang.String;»
са, присутствующего в системе, существует экземпляр Class,
содержащий подробную низкоуровневую информацию об Полностью алгоритм построения такого имени описан
этом классе. Например, с помощью этого экземпляра мож- в документации на Java.

№1, октябрь 2002 51


программирование
Можно проверить, не является ли класс интерфейсом, è àíàëîãè÷íûå ìåòîäû äëÿ òèïîâ byte, short, int, long,
массивом или примитивным типом: методы isInterface(), float, double
isArray() и isPrimitive(). Если класс — массив, можно полу-
чить тип его элементов (т.е. соответствующий объект специальные версии get на случай, когда массив состоит
Class): метод getComponentType(). Для не-массивов этот из элементов соответствующего примитивного типа;
метод вернет null.
Даже такие простейшие механизмы позволяют сделать public static void set(Object array, int index, Object value),
нечто полезное. Вот пример — функция для подсчета public static void setBoolean(Object array, int index, boolean
z),
объема памяти, занятого массивом примитивных типов: public static void setChar(Object array, int index, char c)
è ò.ä.
public static int sizeOfArray(Object v) {
if (v==null) return 0;
Class componentClass= v.getClass().getComponentType(); обратные функции для записи элементов в массив.
if (componentClass==null) Наконец, можно создать массив c помощью одного из
throw new IllegalArgumentException(«Not an array»);
int s= componentClass==byte.class? 2: двух методов:
componentClass==short.class? 4:
componentClass==char.class? 4:
componentClass==int.class? 4: public static Object newInstance(Class componentType, int
componentClass==float.class? 8: length),
componentClass==long.class? 8: public static Object newInstance(Class componentType, int[]
componentClass==double.class? 16: dimensions)
-1;
// íå îáðàáàòûâàåì òèï boolean: äëÿ íåãî ðàçìåð ýëåìåíòà
// íå ñïåöèôèöèðîâàí äîêóìåíòàöèåé ïî ÿçûêó Java Первый метод удобен для одномерных массивов, второй —
if (s==-1) позволяет создавать сразу многомерные массивы. В каче-
throw new IllegalArgumentException(«Unknown component type»);
return java.lang.reflect.Array.getLength(v)*s; стве componentType передается класс элементов — напри-
// ìåòîä getLength() êëàññà java.lang.reflect.Array ïîëó÷àåò мер, один из классов примитивных типов boolean.class,
// íà âõîäå ìàññèâ ïðîèçâîëüíîãî òèïà è âîçâðàùàåò åãî äëèíó
} char.class и т.д.
Все подробности — в документации фирмы Sun.
На первый взгляд у неискушенного Java-программис-
java.lang.reflect.Array — «странный» та все это вызывает некоторое недоумение. Зачем столь
способ работать с массивами замысловатым способом манипулировать с массивом,
Прежде чем переходить к более мощным (и ценным) воз- когда можно просто воспользоваться встроенными язы-
можностям класса Class, давайте обратим внимание на ковыми средствами? Зачем писать:
java.lang.reflect.Array. Этот очень простой класс из пакета
java.lang.reflect представляет собой библиотеку статических v.getByte(n)
методов, позволяющих выполнять все основные примитив-
ные действия с массивом неизвестного (на этапе компиля- для массива v типа byte[], когда можно просто написать v[n]?
ции) типа, представленного общим типом данных Object. Ответ — класс Array позволяет писать универсаль-
Пример мы видели в приведенной выше функции ные функции, принимающие на вход массив произволь-
sizeOfArray() — метод getLength(). Этот метод объявлен в ного, неизвестного заранее типа. Пример такой функ-
исходном коде фирмы Sun следующим образом: ции мы уже видели — sizeOfArray() из предыдущего пун-
кта. Если бы не класс Array, нам бы либо пришлось на-
public static native int getLength(Object array) писать 7 версий этой функции для каждого варианта мас-
throws IllegalArgumentException; сивов примитивных типов, либо заменить вызов
java.lang.reflect.Array.getLength(v) чем-то вроде:
Любой массив в Java является объектом, т.е. наследни-
ком Object, поэтому его можно передать в getLength() в ка- v instanceof byte[]? ((byte[])v).length:
честве параметра. На самом деле, только массивы и можно v instanceof short[]? ((short[])v).length:
v instanceof char[]? ((char[])v).length:
передавать в этот метод — как и в большинство других ме- v instanceof int[]? ((int[])v).length:
тодов класса Array. Для объектов других типов методы класса v instanceof long[]? ((long[])v).length:
v instanceof float[]? ((float[])v).length:
Array возбуждают исключение IllegalArgumentException. v instanceof double[]? ((double[])v).length: -1
Большинство остальных методов класса Array предназ-
начены для чтения и записи элементов в массив: Приведем более практичный пример использования
класса Array. Достаточно часто возникает задача преоб-
public static Object get(Object array, int index) разовать произвольный объект в текстовую строку — на-
пример, в целях отладки. Предназначенный для этого
возвращает элемент номер index; если массив состоит из метод toString() обычно выдает достаточно внятную ин-
элементов примитивного типа (byte, char и т.п.), возвра- формацию о содержимом объекта. Но, к сожалению, для
щается объект-оболочка (Byte, Char, ...); массивов этот метод ведет себя весьма тупо — просто
возвращает внутренний адрес массива.
public static boolean getBoolean(Object array, int index), Я написал простую функцию toS(Object v, String
public static char getChar(Object array, int index)
separator), преобразующую произвольный массив в стро-

52
программирование
ку. Все элементы массива преобразуются (стандартным изображения, обычно существуют более удобные спосо-
образом) в строки и конкатенируются через разделитель бы прочитать ресурс — например, метод getImage() клас-
separator, заданный в качестве параметра функции. Вот са java.applet.Applet. Но для текстовых файлов и файлов
текст этой функции: нестандартного формата getResourceAsStream(), как пра-
вило, — самое разумное решение.
public static String toS(Object v, String separator) { Вот пример законченного класса, использующего эту
if (v==null) return «»;
if (v.getClass().isArray()) { технику:
int len;
if ((len=java.lang.reflect.Array.getLength(v))==0) return «»;
StringBuffer result= new StringBuffer(); import java.io.*;
for (int k=0; k<len; k++) { public class MyClassWithResource {
if (k>0) result.append(separator); public static final String myTextResourceName= «mydata.txt»;
result.append(String.valueOf( public static final String myTextResource;
java.lang.reflect.Array.get(v,k))); static {
} String s= «»;
return result.toString(); try {
} InputStream stream= MyClassWithResource.class
return String.valueOf(v); .getResourceAsStream(myTextResourceName);
} if (stream==null)
throw new FileNotFoundException(myTextResourceName+» not
found»);
Если аргумент v не является массивом, действие toS StringBuffer sb= new StringBuffer(stream.available());
InputStreamReader reader= new InputStreamReader(stream);
не отличается от стандартного метода v.toString(). Если char[] buf= new char[32768];
v==null, возвращается пустая строка (обычно это удобнее int len;
while ((len=reader.read(buf,0,buf.length))>=0) {
стандартной реакции — возврата строки «null»). sb.append(buf,0,len);
Без класса Array пришлось бы написать 9 вариантов }
s= sb.toString();
такой функции — для 8 примитивных типов и для масси- } catch (IOException e) {
ва объектов произвольного типа Object[]. e.printStackTrace();
}
Здесь нужно сделать одно важное замечание. Хотя myTextResource= s;
класс Array действительно позволяет существенно эконо- }
public static void main(String[] args) {
мить текст программы и не писать разные варианты мето- System.out.println(«Loaded resource:»);
да для массивов разных типов, следует иметь в виду — System.out.println(myTextResource);
}
получаемый код сравнительно неэффективен. Скажем, }
цикл суммирования всех элементов числового массива
через вызов java.lang.reflect.Array.getDouble() будет рабо- В этом примере файл «mydata.txt» должен быть рас-
тать на порядок дольше банального: положен в том же каталоге, что и class-файл
«MyClassWithResource.class».
double s= 0.0; Файл ресурса необязательно размещать в том же ката-
for (int k=0; k<v.length; k++) s+= v[k]; логе, что и class-файл. Если он расположен в одном из под-
каталогов этого каталога, в качестве имени ресурса нужно
В случае функции toS() разница была бы непринципи- передать относительный путь, разделяя имена подкатало-
альной, так как преобразование числа в строку — срав- гов символом «/» (как это принято в Internet и Unix). Можно
нительно медленная операция. также указать в качестве имени ресурса «абсолютный»
путь, начинающийся с символа «/». Тогда Java будет ис-
Class.getResourceAsStream()- ресурсы кать ресурс во всех каталогах, перечисленных в путях по-
Один из самых очевидных примеров использования иска классов CLASSPATH — т.е. по тем же правилам, по
класса Class — загрузка ресурсов. которым отыскиваются class-файлы программы.
Ресурс в Java — это файл с данными, прилагаемый к Может возникнуть вопрос — зачем нужен специаль-
Вашей программе и обычно размещаемый «рядом» с ный метод класса Class, когда можно прочитать файл ре-
class-файлами. В графических приложениях и апплетах сурса обычными средствами файлового ввода/вывода?
это чаще всего изображения (jpg- или gif-файлы), но с Основная причина — использование метода
таким же успехом это может быть и файл специального getResourceAsStream() является гораздо более общим ре-
формата — например, содержащий справочные таблицы шением, работающим в большем числе ситуаций.
или какие-либо ваши объекты. Например, по традиции, законченные наборы классов —
Наиболее общий способ прочитать файл-ресурс произ- Java-приложения или библиотеки — принято упаковывать в
вольного формата, расположенный «рядом» с некоторым архивы JAR и устанавливать на компьютер именно в таком
классом — обратиться к методу getResourceAsStream() виде. Классы Java прекрасно загружаются непосредствен-
объекта Class, соответствующего данному классу. В ка- но из архива JAR, без предварительной распаковки. То же
честве параметра этому методу нужно передать путь к фай- самое относится и к ресурсам, загружаемым методом
лу ресурса относительно каталога, в котором размещен ваш getResourceAsStream() — или более специальными метода-
класс. В качестве результата getResourceAsStream() вернет ми типа java.applet.Applet.getImage(). В то же время, обыч-
объект InputStream, с помощью которого можно прочитать ные средства файлового ввода/вывода для чтения ресурса
файл ресурса стандартными средствами ввода/вывода Java. из JAR уже непригодны — нужно использовать специаль-
Для наиболее популярных типов ресурсов, таких как ные классы для анализа и чтения JAR-файлов.

№1, октябрь 2002 53


программирование
Аналогичная ситуация — апплеты, когда файлы ресур- пробелы, русские буквы или другие символы, недопусти-
сов и class-файлы находятся на сервере. Метод мые в стандарте URL, то они будут закодированы комби-
getResourceAsStream() в этом случае обеспечит чтение ре- нациями вида %XX, где XX — ASCII-код символа. Такое имя
сурса с сервера через Internet. файла непригодно для обработки средствами ввода/выво-
Тем не менее, в некоторых случаях для чтения ресур- да Java. Для восстановления «нормального» имени файла
са все-таки может быть целесообразным использование нужно вызвать метод java.net.URLDecoder.decode(), обяза-
традиционных средств файлового ввода/вывода — конеч- тельно указав при этом кодировку символов (encoding).
но, если вы не используете JAR и пишете сервлет или при- Опытным путем я установил, что для кодирования не-
ложение, а не апплет. Например, для достижения макси- латинских букв в имени файла Java использует кодиров-
мальной производительности может понадобиться отобра- ку UTF-8 — по крайней мере, на Windows-платформе. К
зить ресурс в память средствами отображения файлов из сожалению, я не нашел в документации прямых указа-
пакета java.nio, появившегося в Java начиная с версии SDK ний, что это всегда будет так на всех платформах. Поэто-
1.4. Или, может быть, вы захотите просканировать ката- му я бы порекомендовал, по возможности, все же разме-
лог с ресурсами и загрузить все файлы с определенным щать свои классы в каталогах, путь к которым состоит
расширением — для сканирования каталога в классе Class только из латинских символов — тогда приведенная выше
нет соответствующих средств. функция будет достаточно надежна.
Если ресурс, как это принято, расположен рядом с
class-файлами — или, скажем, если вы хотите поработать Class.forName() и Class.newInstance() —
с самими class-файлами вашего приложения — то при- динамическая загрузка классов
дется решить следующую задачу: определить дисковый Мы подошли к рассмотрению по-настоящему важных и
каталог, в котором находится ваш class-файл. Будьте вни- интересных технологий мира отражений. Речь пойдет о фун-
мательны: это отнюдь не простая задача. даметальном и чрезвычайно мощном механизме Java: ди-
Ниже приведен текст функции, возвращающей полный намической загрузке произвольного класса по заданному
путь к class-файлу. имени. Эта возможность встроена непосредственно в Java
и реализуется классом Class. В других языках типа С++ или
public static java.io.File getClassFile(Class clazz) { Delphi аналогичных целей можно достигнуть, используя спе-
// The file will exist only if it is usual class-file,
// not a part of JAR or Web resource циальные средства конкретной операционной системы (типа
String s= clazz.getName(); загрузки dll в Windows функцией loadLibrary()), но в Java это
s= s.substring(s.lastIndexOf(«.»)+1);
s= clazz.getResource(s+».class»).getFile(); сделано по-настоящему удобно.
try { Итак, Вашему вниманию предлагается статический ме-
s= java.net.URLDecoder.decode(s,»UTF-8");
} catch(java.io.UnsupportedEncodingException e) { тод Class.forName(). Вот его формальное объявление:
}
return new java.io.File(s);
} public static Class forName(String className)
throws ClassNotFoundException
Прежде всего мы получаем имя класса без имени па-
кета, добавляем к нему расширение «.class» и передаем Метод отыскивает в системе (среди путей поиска клас-
методу getResource() объекта Class. Этот метод возвра- сов CLASSPATH) класс с заданным именем className и
щает экземпляр класса java.net.URL, представляющий возвращает соответствующий экземпляр класса Class.
файл в виде универсального пути к ресурсу (URL, Universal Имя className должно быть полным, т.е. включать имя
Resource Locator). В нашем случае (когда классы распо- пакета. Например:
ложены в обычных файлах в локальной файловой систе-
ме) URL будет выглядеть примерно так: Class clazz= Class.forName(«java.lang.String»);

file://ïóòü_ê_äèñêîâîìó_êàòàëîãó/èìÿ_êëàññà.class Если такой класс отсутствует, возбуждается исключе-


ние ClassNotFoundException.
Метод getFile() объекта URL «отрежет» префикс «file:/ После получения переменной типа Class, следующее
/», оставив все остальное без изменений. наиболее типичное действие — создание экземпляра
А вот дальше начинаются сложности. Оставшийся путь только что загруженного класса. Для этого служит метод
к файлу записан в стандарте URL, который может отли- Class.newInstance(). Его объявление:
чаться от формата имен файлов, принятого в текущей
файловой системе. public Object newInstance()
Первое отличие: в URL подкаталоги всегда разделя- throws InstantiationException, IllegalAccessException
ются прямым слэшем /, в то время как в операционных
системах, отличных от Unix, могут использоваться другие В нашем примере вызов newInstance() мог бы выгля-
символы (например, обратный слэш \ в случае Windows). деть так:
Это различие несущественно для Java — классы пакета
java.io прекрасно будут работать и с именем файла, запи- Object object= clazz.newInstance();
санным через прямые слэши.
Но есть и второе отличие. Если путь к файлу содержит Чтобы этот метод мог выполниться, у загруженного клас-

54
программирование
са должен существовать пустой конструктор (без аргумен- (заранее неизвестный) класс и создать его экземпляр. Можно
тов), либо — что по существу то же самое — не должно получить полный список всех конструкторов, полей и мето-
быть описано вообще никаких конструкторов. В противном дов класса и обратиться к любому из них, передав (в случае
случае будет возбуждено исключение InstantiationException. конструктора или метода) список всех параметров. Для это-
Конечно, этого еще мало, чтобы работать с полученным го служат следующие методы класса Class:
объектом. Если вы не знаете, что умеет делать класс — ка-
кие у него есть методы, что они ожидают получить на входе public Constructor[] getConstructors(),
public Field[] getFields(),
и для чего они предназначены — вы не сможете извлечь из public Method[] getMethods(),
него ничего полезного. Для формального определения, что public Constructor[] getDeclaredConstructors(),
public Field[] getDeclaredFields(),
«умеет» класс, в Java существует стандартный механизм — public Method[] getDeclaredMethods()
интерфейсы. Остается просто применить этот механизм.
Например, предположим, ваша программа должна в Перечисленные методы возвращают массивы объек-
некоторые моменты выполнять перевод с одного языка тов типа Constructor, Field и Method. Эти классы содер-
на другой. Формально это можно описать интерфейсом: жатся в пакете java.lang.reflect и обеспечивают исчерпы-
вающий доступ к полям, конструкторам и методам.
public interface LanguageTranslator { Сразу бросается в глаза наличие двух версий методов:
public String translate(String source,
String sourceLanguage, getXXX и getDeclaredXXX (где XXX — «Constructors»,
String targetLanguage); «Fields» или «Methods»). Поначалу это может даже несколь-
// ïåðåâîäèò òåêñò source ñ ÿçûêà sourceLanguage
// íà ÿçûê targetLanguage ко сбить с толку — какой версией следует пользоваться?
} getDeclaredXXX возвращает список членов класса (кон-
структоров, полей или методов), объявленных при описа-
Пусть Ваша программа сама по себе не умеет выпол- нии класса, но не унаследованных от классов-предков. При
нять перевод, но ее можно расширить классами сторон- этом в список включаются все члены, независимо от их
них разработчиков, которые эту задачу решать умеют. Все уровня защиты — т.е. public, protected, private и друже-
эти классы реализуют интерфейс LanguageTranslator и ственные члены.
имеют конструктор без параметров (либо лишены конст- getXXX возвращает полный список членов, объявленных
руктора). В настройках Вашей программы пользователь в самом классе либо унаследованных от одного из предков.
указывает имя такого класса, независимо инсталлирован- Но в этот список уже попадают только public-члены.
ного в систему (или выбирает из списка классов), после При желании, конечно, можно получить и максимально
чего для выполнения перевода ваша программа исполь- полную информацию — список всех членов, объявленных
зует следующие операторы: в самом классе либо в любом из его предков. Для этого
достаточно организовать цикл по цепочке классов-пред-
String source= «òåêñò, òðåáóþùèé ïåðåâîäà»; ков, пользуясь специальным методом Class.getSuperclass().
String sourceLanguage= «Russian»;
String targetLanguage= «English»; Кроме получения полного списка, можно также отыс-
Class clazz= Class.forName(«ïîëíîå_èìÿ_êëàññà_ïåðåâîä÷èêà»); кать в классе конкретный член. Для этого служат методы:
Object object= clazz.newInstance();
if (!(object instanceof LanguageTranslator)) {
throw new Exception(«...»); public Constructor getConstructor(Class[] parameterTypes),
// ñîîáùàåì îá îøèáêå: óêàçàííûé êëàññ public Field getField(String name),
// íå ðåàëèçóåò òðåáóåìûé èíòåðôåéñ, public Method getMethod(String name, Class[]
// ò.å. íå ÿâëÿåòñÿ ïåðåâîä÷èêîì parameterTypes),
} public Constructor getDeclaredConstructor(Class[]
String result= ((LanguageTranslator)object) parameterTypes),
.translate(source,sourceLanguage,targetLanguage); public Field getDeclaredField(String name),
public Method getDeclaredMethod(String name, Class[]
parameterTypes)
Описанная техника может оказаться очень полезной
практически в любой достаточно большой и серьезной си- (Собственно, чаще используются как раз эти методы,
стеме, рассчитанной на разработку многими участника- а не описанные выше методы получения списков.)
ми. Многие компоненты таких систем являются достаточ- Аргумент name в этих методах должен содержать имя
но изолированными, и их набор может быть совершенно требуемого члена. Аргумент parameterTypes связан с воз-
неизвестен на этапе компиляции основной программы — можностью Java перегружать конструкторы и методы —
известны лишь интерфейсы, который они обязуются реа- определять несколько конструкторов, либо несколько ме-
лизовывать. Например, так обычно строятся системы тодов с одинаковым именем, отличающихся только типа-
plugin’ов — модулей, добавляемых к уже работающей си- ми параметров. (В случае конструкторов это единствен-
стеме. В подобных случаях механизм отражений — мето- ный способ создать много конструкторов класса.) В каче-
ды Class.forName() и Class.newInstance() — становится стве parameterTypes нужно отдать массив объектов типа
единственным грамотным решением. Class, соответствующих типам всех параметров конструк-
тора или метода.
Constructor, Field, Method — работа с Разница между вариантами getXXX и getDeclaredXXX
классами через отражения здесь та же самая, что и в случае методов получения
На самом деле технология отражений позволяет сделать списков.
гораздо больше, чем просто загрузить по имени некоторый Здесь я бы порекомендовал написать небольшой тест,

№1, октябрь 2002 55


программирование
который распечатает списки всех конструкторов, полей и public static void main(String[] args) throws Exception {
методов (объявленных и унаследованных) какого-нибудь Class clazz= TestClass.class;
Constructor c= clazz.getConstructor(new Class[]
класса. Для преобразования объектов Constructor, Field, {int.class});
Method в строки можно использовать стандартный метод Object o= c.newInstance(new Object[] {new Integer(23)});
Field f= clazz.getField("a");
toString(). Попробуйте распечатать такие списки для клас- System.out.println(f.getInt(o));
сов без конструкторов, с пустым конструктором, с конст- f.setInt(o,24);
System.out.println(o);
руктором, обладающим параметрами, с private- и public- Method m= clazz.getMethod("b",new Class[] {});
полями. Попробуйте унаследовать класс и переопреде- m.invoke(o,new Object[] {});
System.out.println(o);
лить в нем (под тем же именем) public-, protected- или m= clazz.getMethod("b",new Class[] {int.class});
private-поле. Такой текст хорошо помогает понять, как в m.invoke(o,new Object[] {new Integer(2)});
System.out.println(o);
Java устроены классы, конструкторы и наследование. }
Как реально работать с классами Constructor, Field, }
Method?
Прежде всего, заметим, что все они реализуют общий Создание экземпляра объекта с помощью конструктора
интерфейс Member, позволяющий: похоже на вызов метода Class.newInstance(). Но в данном
n получить символьное имя члена (конструктора, поля случае можно легко создать экземпляр класса, не обладаю-
или метода) с помощью метода: щий конструктором без параметров — нужно только знать
список типов параметров требуемого конструктора.
public String getName(); Для передачи параметров в конструкторы и методы
используется массив объектов (типа Object[]). Если нуж-
n получить обратную ссылку на класс, в котором объяв- но передать параметр примитивного типа, он «заворачи-
лен данный член (т.е. тот класс, который возвращает вается» в соответствующий класс-оболочку — в случае
этот член в соответствующем массиве int это объект Integer. Такое «заворачивание» — традици-
getDeclaredXXX()), с помощью метода: онная практика в мире отражений.
Для доступа к полю в классе Field реализованы общие
public Class getDeclaringClass(); методы get() и set(), рассчитанные на произвольный объект,
и частные версии getBoolean(), getInt(), ..., setBoolean(),
n получить набор битовых флагов — модификаторов setInt(), ..., рассчитанные на примитивный тип поля. Пер-
данного члена с помощью метода: вым параметром у этих методов всегда передается экзем-
пляр объекта, к полю которого нужно получить доступ.
public int getModifiers(); Для вызова метода служит метод invoke() класса
Method. Ему передается экземпляр объекта, метод ко-
Модификаторы — это битовые флаги, описывающие, торого следует вызвать, и список параметров в виде мас-
обладает ли данный член следующими свойствами: public, сива Object[].
private, protected, static, final, synchronized, volatile, transient, Если метод класса возвращает результат (а не опи-
native, abstract или strictfp. Полный список модификато- сан как void, как в данном примере), этот результат бу-
ров и средства работы с ними можно найти в классe дет возвращен в качестве результата invoke — в виде
java.lang.reflect.Modifier — см. документацию фирмы Sun. общего типа Object. (Примитивный тип в этом случае,
Кстати заметим, что некоторые модификаторы (напри- как обычно, «заворачивается» в класс-оболочку.)
мер, public) определены также у классов; их можно получить Располагая объектом Constructor или Method, можно уз-
методом Class.getModifiers(). Для классов определен еще нать список типов всех параметров в виде массива Class[]:
один модификатор — java.lang.reflect.Modifier.INTERFACE, для этого служат методы Constructor.getParameterTypes()
означающий, что объект Class ассоциирован не с классом, и Method.getParameterTypes(). В случае метода можно так-
а с интерфейсом. же узнать тип результата: Method.getReturnType(). Кстати,
Главное назначение классов Constructor, Field, Method — это та самая экзотическая ситуация, когда находит приме-
конечно, получить доступ к соответствующим членам, т.е. в нение исключительно редкий объект void.class (я упоми-
случае конструкторов — создать экземпляр класса, в слу- нал об этой объекте в разделе 2): void.class будет возвра-
чае полей — прочитать или изменить поле, в случае мето- щен getReturnType(), если метод объявлен как void.
дов — вызвать метод. С помощью классов Field и Method можно также по-
Это делается достаточно просто. Чтобы не дублиро- лучить доступ к статическим полям и методам класса,
вать документацию по Java, я просто приведу пример го- даже не создавая экземпляра объекта. При этом в ка-
тового тестового класса, где использованы все основные честве первого параметра методов getXXX(), setXXX()
приемы доступа к членам: или invoke() допускается передать null. Можно, скажем,
легко написать Java-код, аналогичный по действию
import java.lang.reflect.*; стандартной утилите «java» — а именно, запускающий
public class TestClass {
public int a; статическую функцию «public static void main(String[]
public TestClass(int a) {this.a= a;} args)» у произвольного класса. Техника интерфейсов,
public void b() {a= 1;}
public void b(int p1) {a= p1;} описанная в предыдущем разделе, не позволила бы это
public String toString() {return a+"";} сделать.

56
программирование
Обход защиты Java Приведем два примера. Во-первых, в Java поддержива-
В очень многих учебниках по Java подчеркивается, что ется механизм сериализации. Достаточно реализовать в
модификаторы protected и private вместе с «дружествен- вашем классе пустой интерфейс-индикатор
ным» уровнем доступа (отсутствие модификаторов) по- java.io.Serializable, и появляется возможность полностью (т.е.
зволяют обеспечить «100-процентную защиту» ваших по- со всеми полями и вложенными объектами) записать этот
лей и методов от использования посторонними класса- объект в поток java.io.ObjectOutputStream и впоследствии
ми. Например, вы никогда не сможете добраться до поля прочитать из потока java.io.ObjectInputStream.
Те, кто изучал механизм сериализации Java, согласят-
private char value[];
ся — во многом этот механизм напоминает черную магию.
объявленного в исходном коде класса String и представ- Каким-то образом классы java.io.ObjectInputStream и
ляющего реальное содержимое строки. java.io.ObjectOutputStream, без всяких дополнительных под-
На самом деле все это не так. Смотрите, как можно сказок со стороны разработчика класса, «догадываются»,
добраться до этого самого поля и «нелегально» изменить как записать или прочитать все поля объекта, включая
строку — вопреки известному утверждению, что тип String private-поля. Более того, для «подсказки», когда она все
является абсолютно неизменяемым: же требуется, используются private-методы writeObject() и
readObject() — классы java.io.ObjectOutputStream и
import java.lang.reflect.*; java.io.ObjectInputStream каким-то образом обнаруживают
public class HackString {
public static void main(String[] args) throws Exception { и вызывают эти методы.
String s= «Hello!»; Конечно же, в действительности эта «черная магия» —
System.out.println(s);
Field f= s.getClass().getDeclaredField(«value»); не что иное, как магия отражений. Описанные выше техно-
// Èìåííî getDeclaredField, à íå getField: логии, в том числе метод setAccessible(), в принципе, позво-
// ïîñëåäíèé ìåòîä ïðîñòî íå íàøåë áû ñêðûòîãî ïîëÿ
f.setAccessible(true); ляют даже разработать свою собственную схему сериали-
char[] value= (char[])f.get(s); зации, отличную отстандартной, предлагаемой фирмой Sun.
value[5]= “?”;
System.out.println(s); Второй пример — технология RMI. Это чрезвычайно
} мощный механизм, позволяющий распределять вычисле-
}
ния по сети — так, чтобы с точки зрения кода Java вызов
Здесь «магический» метод — setAccessible(). Этот ме- метода выглядел, как обычно, а на самом деле этот ме-
тод (и симметричный getAccessible()) имеется у всех клас- тод отрабатывался на другом компьютере. Здесь тоже не
сов Constructor, Field, Method и предназначен специально обойтись без технологий отражения. Только они позволя-
для того, чтобы отключить стандартную проверку моди- ют динамически превратить объект (со всеми своими по-
фикаторов, осуществляемую Java-машиной. лями) в поток данных, передать его по сети, реконструи-
(Естественно, все это сработает только при условии, ровать объект на другом компьютере и вызвать нужный
что в вашей версии Sun Java SDK реализация класса String метод — и все это скрыто от пользователя класса.
точно так же основана на private-поле «char value[]». Так Конечно, все это не повод, чтобы использовать отра-
как это поле скрытое, фирма Sun вправе в любой момент жения не по назначению для «взлома» защиты Java. От-
переименовать его или вообще заменить чем-нибудь дру- ражения следует использовать только тогда, когда без это-
гим.) Спрашивается — зачем же это сделано? И разве го нельзя обойтись — иначе вы потеряете все преимуще-
это не является брешью в системе безопасности? ства, которые дает идеология объектно-ориентированно-
Что до второго вопроса — разумеется, метод го программирования.
setAccessible() контролируется менеджером безопаснос-
ти Java (так же как, например, работа с файлами), и ни- Заключение
какая мало-мальски защищенная Java-система не позво- Я постарался описать самые, на мой взгляд, важные и
лит злоупотребить подобной возможностью. интересные аспекты технологии отражений. Статья — не
А чтобы понять, зачем это нужно, взгляните на любую справочник и не учебник. Многие вещи «остались за бор-
среду разработки Java-проектов — скажем, NetBeans или том». Не все методы классов были описаны; некоторые спе-
JavaBuilder. Традиционная возможность подобных сред — цифичные классы, такие как java.lang.reflect.Proxy, я вооб-
показать все поля и методы некоторого класса, напри- ще не рассматривал. Чтобы получить полную и точную ин-
мер, визуальной компоненты — в том числе и скрытые, а формацию, всегда лучше обращаться к первоисточнику —
в некоторых случаях — дать возможность отредактиро- документации фирмы Sun. Кроме сайта http://java.sun.com,
вать значения полей. Язык Java уникален в том отноше- документацию почти всегда можно найти в комплекте по-
нии, что подобные действия можно легко выполнить со- ставки Java или извлечь из комментариев к исходным тек-
вершенно законными средствами самого языка, не при- стам фирмы Sun.
бегая, скажем, к анализу исходного текста программы. Я также хотел бы порекомендовать книгу: «Язык Про-
На самом деле неограниченный доступ ко всем конст- граммирования Java», К.Арнолд, Дж.Гослинг, Д.Холмс, Из-
рукторам, полям и методам даже гораздо более ценен. дательский дом «Вильямс», Москва — С.-Петербург —
Этот механизм мира отражений дает возможность удоб- Киев, 2001. Это наиболее грамотная из попадавшихся мне
но и естественно реализовать чрезвычайно мощные тех- книг на русском языке. Она написана сотрудниками фир-
нологии, нереализуемые (или крайне сложно реализуе- мы Sun, участвовавшими в разработке технологии Java —
мые) другими способами. т.е. в некотором роде является первоисточником.

№1, октябрь 2002 57


программирование

COLDFUSION Я намеренно вынес спорное утверждение в

ИЛИ, заголовок, главным образом с целью привлечь


внимание читателя к этому, безусловно,
выдающемуся продукту.

возможно, лучшее решение для создания


динамических сайтов Александр Меженков

Эта статья – первая в серии, адресованной читателям, де- (взаимодействия, обмена данными, если угодно) между
лающим первые шаги в программировании на ColdFusion, Web-сервером и прикладными программами, выполняющи-
а также тем, кто все еще не определился с выбором ми ту или иную задачу. По существу, написанные вами CGI
средств, позволяющих создавать динамические сайты, уп- программы расширяют возможности Web-сервера, допол-
равляемые данными. няя его нужной вам функциональностью. Отсюда, собствен-
Сколько раз вам приходилось слышать или самим за- но и второе название CGI программ – серверные расшире-
давать вопросы типа “как я могу сохранить информацию ния. В принципе, CGI программа может быть написана на
из HTML-форм на своих Web-страницах?” или “как мне сде- любом языке: C/C++, Perl, TCL, Visual Basic, Clipper, Fortran.
лать счетчик посещений?” или, наконец, “как мне защитить Все зависит от того, какая у вас система и в какой среде
код своих Web-страниц от всеобщего обозрения?” В самом программирования вы чувствуете себя более комфортно.
языке HTML нет никаких простых способов обработки дан- Главное, чтобы ваша программа обеспечивала средства
ных HTML-форм. Броузер может лишь собрать информа- общения с Web-сервером, или, говоря иначе, удовлетворя-
цию из форм и передать ее на Web-сервер. Обработка ин- ла стандарту CGI. Естественно, если вы пишите на С, то
формации может быть возложена на CGI серверные рас- перед запуском вам нужно скомпилировать программу.
ширения. Одно название чего стоит! А ведь их еще нужно Если же вы используете один из языков-сценариев типа
написать и отладить своими руками, потратив немало вре- Perl, TCL или Unix shell, то все, что вам надо сделать перед
мени на изучение различных стандартов и протоколов! Бе- запуском, это поместить файлы в каталог /cgi-bin, где по
зусловно, это полезное, с точки зрения общего развития, умолчанию их будет искать Web-сервер. В чем же недоста-
занятие. Кто из нас не проходил на первых курсах институ- ток CGI программ? Во-первых, не каждый специалист по
та сопромат или теорию машин и механизмов, с тем, что- электронной торговле или Web-дизайнер знает С или Visual
бы позже получить диплом программиста или специалиста Basic. Во-вторых, CGI программы слишком расточительны
по микропроцессорам?! Так ли уж это было необходимо? по отношению к системным ресурсам. При каждом обра-
Пара слов пояснений для тех, кто не совсем в курсе. щении на вашу страницу и, соответственно, при каждом
Хотя бы для того, чтобы понять от чего Вас может уберечь запуске CGI программы, система порождает новый поток,
ColdFusion. выделяя для него в оперативной памяти компьютера но-
CGI, или Common Gateway Interface, - это стандартный вое пространство. И если у вашего сайта много посетите-
шлюзовый интерфейс. Проще говоря – это некоторый стан- лей (а ведь именно для этого вы и создаете сайт), то легко
дарт или набор правил, определяющий порядок общения может сложиться ситуация, когда в памяти сервера одно-
программирование
временно будет находиться много копий одной и той же название ColdFusion – сервер Web-приложений, но в зави-
CGI программы, что, в свою очередь, очень быстро исчер- симости от того, как вы решите его использовать, он мо-
пает всю оперативную. Наконец, при создании CGI про- жет быть средством разработки Web-страниц, сервером баз
грамм немало усилий будет затрачено на программирова- данных или вашим счастливым билетом в благополучную
ние рутинных задач ввода/вывода, вместо того чтобы на- жизнь. От версии к версии ColdFusion предлагал все боль-
править в созидательное русло решения прикладных за- ше возможностей. Версия 1.5 в 1996 году содержала всего
дач сайта. лишь 35 тегов, обеспечивавших простейшие функции дос-
Однако вернемся к теме статьи... тупа к базам данных и поддержки электронной почты. Се-
Вначале немного истории. В далеком (по меркам Сети) годня 5-я версия ColdFusion предоставляет более 80 тегов
1995 году два брата Дж.Дж. И Джереми Эллейр (J.J. И и 255 функций для решения практически любой задачи,
Jeremy Allaire) в США (ну а где же еще?) основали новую которая может возникнуть в Web-программировании.
компанию «Allaire Corporation» для продвижения первого в ColdFusion позволяет начать создание нового приложе-
мире сервера Web-приложений, который они назвали ния на основе прочного фундамента развитого и полнос-
ColdFusion. Одной из причин создания ColdFusion как раз тью ориентированного на Web-среду языка программиро-
и была сложность создания сайтов, управляемых данными вания, обеспеченного серьезной поддержкой солидной ком-
с помощью CGI программ. Перед одним из братьев – Дже- пании (Macromedia), ее бизнес - партнерами и тысячами
реми – стояла задача периодического обновления элект- разработчиков, публикующих свои решения на специаль-
ронной версии издававшегося в печатном виде журнала. ном Web-ресурсе под названием Developers exchange. Этот
Занятие это было крайне утомительным, и Джереми обра- ресурс настолько богат, что прежде, чем приступать к раз-
тился к своему брату-программисту с просьбой написать работке решения какой–бы то ни было задачи, имеет смысл
для него какое-нибудь приложение, которое избавило бы заглянуть сюда. Очень часто здесь можно найти совершен-
его от излишней траты времени и сил, и позволило бы ему но бесплатно готовое решение.
сосредоточиться на основной задаче – собственно, обнов- Конечно, на рынке существует много других техноло-
лению электронной версии журнала. Когда проект был за- гий, которые вы можете использовать для создания дина-
вершен, оба брата осознали, что они испекли горячий пи- мических Web-приложений. Диапазон их достаточно ши-
рожок, который наверняка многим придется по вкусу. Не- рок: от “open source” технологий, таких как Perl и PHP, до
долго думая, братья основали новую компанию и, не мудр- коммерческих Java Server Pages (JSP) или Microsoft Active
ствуя лукаво, дали ей свою фамилию. Вложив в свое дети- Server Pages (ASP). При таком богатстве выбора, что нас
ще 18 тысяч долларов личных сбережений, шестью года- может побудить использовать ColdFusion, который, кстати
ми позже, в 2001 году, братья Эллейр продали его корпо- сказать, является коммерческим и весьма недешевым про-
рации Macromedia более чем за 360 миллионов долларов. дуктом. Его цена больше $1,000. Но пусть вас это не сму-
У вас еще осталось желание писать низкоуровневые про- щает. Во-первых, его покупают, как правило, не частные
цедуры ввода/вывода? лица, а фирмы. А во-вторых, практика показывает, что сто-
Что же представляет собой этот чудесный продукт, ко- имость конечного продукта или, говоря другими словами,
торый принес своим создателям такую прибыль? Безус- “стоимость владения” (Total Cost of Ownership), включаю-
ловно, вам знакомы термины hardware и software. Так вот, щая, помимо цены инструментальных средств, стоимость
ColdFusion – это middleware. Термином middleware называ- разработки алгоритмов, кодирования и отладки, часто ока-
ют программное обеспечение, осуществляющее некоторые зывается ниже, чем стоимость приложения, разработанно-
преобразования. В самом общем смысле сервер приложе- го с помощью “бесплатных” средств. Деньги, вложенные в
ний ColdFusion является посредником, преобразующим ваш ColdFusion окупаются очень быстро.
код, написанный на языке высокого уровня, называемом Одной из главных причин, побуждающих, выбирать
CFML (ColdFusion Markup Language) в теги HTML-докумен- ColdFusion в качестве рабочего инструмента является лег-
та, который может отобразить Web-броузер. Официальное кость разработки. В отличие от большинства упоминавших-
программирование
ся технологий, вам не нужно быть гуру программирования Вот лишь несколько из них: работа с почтой, встроенные
для того, чтобы начать работу с ColdFusion и успешно за- HTTP, POP и FTP клиенты, встроенный поисковый меха-
вершить свой первый проект, который приятно удивит ва- низм. Это ни в коем случае не агитация в пользу ColdFusion,
шего шефа. Эта простота использования отнюдь не озна- просто объективная попытка познакомить с малоизвест-
чает отсутствия мощи и функциональной гибкости. Просто ным в России пакетом.
благодаря усилиям программистов из Allaire, а теперь и Технология ASP, в свою очередь, также имеет преиму-
Macromedia, многие сложные вещи происходят за сценой. щества. Но это уже тема другой статьи.
ColdFusion упрощает решение большинства задач, таких За названием ColdFusion на самом деле скрываются два
как обработка данных форм или выполнение запросов баз понятия: собственно сервер Web-приложений ColdFusion и
данных. Однако когда у вас возникает нужда выполнения язык программирования ColdFusion, называемый CFML
более сложных операций, ColdFusion предоставляет вам (ColdFusion Markup Language). Файлы приложения
такие возможности. В компаниях, использующих ColdFusion имеют расширение .cfm и называются шабло-
ColdFusion, сложные задачи, сборку всего приложения и нами (templates). Шаблоны ColdFusion наряду с тегами и
его отладку осуществляет, как правило, действительно функциями CFML могут содержать обычные HTML-теги и
классный высокооплачиваемый специалист, тогда как про- встроенные JavaScript и/или VBScript сценарии. CFML часть
стые операции возлагаются на новичков, которые еще толь- шаблона обрабатывается сервером приложений, в резуль-
ко учатся программированию. тате чего динамически генерируется часть выходной стра-
Другим важным достоинством ColdFusion является на- ницы основанная на данных формы, базы данных и так
личие версий для всех популярных систем и Web-серве- далее, которая позже объединяется со статической его
ров. Неважно, на чем вы работаете: Windows 95/98/NT/2000, частью, представленной HTML-тегами и сценариями
Solaris, Linux или HP-UX. ColdFuison совместим с большин- JavaScript/VBScript в единый выходной HTML-документ, пе-
ством известных Web-серверов: Netscape Enterprise и редаваемый Web-серверу.
iPlanet, Microsoft IIS и PWS, O’Reilly Website, Apache. Вы Рассмотрим, как ColdFusion обрабатывает пользова-
можете переносить ColdFusion приложения с платформы тельские запросы.
на платформу и легко переключаться между различными 1. Web-броузер направляет запрос к Web-серверу с
системами управления базами данных. требованием открыть файл ColdFusion. Эти файлы имеют
Наиболее близким конкурентом ColdFusion является расширение .cfm.
технология ASP. Какими, качествами кроме межплатфор- 2. Получив запрос, Web-сервер перенаправляет его
менной совместимости, может еще похвастать ColdFusion? серверу приложений ColdFusion.
Легкость и простота. Языком ASP является VBScript – под- 3. Сервер приложений анализирует шаблон и выпол-
множество Visual Basic, который сложнее ColdFusion. Срав- няет действия, предписанные встретившимися тегами и
ним, например, два фрагмента кода. Вначале на ASP функциями CFML, взаимодействуя, если надо, с другими
(VBScript): службами и приложениями, например источниками данных
или почтовым сервером. В результате динамически созда-
1. <% ется часть результирующей HTML страницы.
2. Dim RandomFraction
3. Randomize 4. Далее сервер приложений собирает воедино только
4. RandomFraction = Rnd что созданную динамическую часть страницы со статичес-
5. Response.Write(RandomFraction)
6. %> кой частью исходного шаблона и возвращает результиру-
ющую страницу Web-серверу.
Теперь то же самое на ColdFusion: 5. Web-сервер отправляет полученную от сервера
ColdFusion страницу на пославшую запрос клиентскую ма-
1. <cfset RandomFraction = Rand()> шину.
2. <cfoutput>#RandomFraction#</cfoutput>
В заключение приведем полезные ссылки, а также при-
Что проще – решать вам. меры сайтов, построенных с использованием ColdFusion.
При использовании ASP одной распространенной про-
блемой является необходимость закрывать базы данных 1. Îôèöèàëüíàÿ ñòðàíèöà îïèñàíèÿ ñòàíäàðòà CGI
http://www.w3.org/CGI/
после окончания работы с ними. Часто программисты за-
бывают это делать. Конечно, если все делать без ошибок, 2. Äîêóìåíòàöèÿ è ïðèìåðû CGI ïðîãðàìì
http://hoohoo.ncsa.uiuc.edu/cgi/
эта проблема не возникает.
Но покажите мне программиста, который пишет без 3. Ïðîãðàììíûå ïðîäóêòû Macromedia - íûíåøíåãî âëàäåëüöà
ColdFusion.
ошибок. Подобная забывчивость приводит к тому, что про- Îòñþäà æå ìîæíî ñêà÷àòü ïðîáíûå (trial)âåðñèè
странство памяти, занимаемое объектом базы данных, не http://www.macromedia.com/software/
освобождается. Страницы с таким кодом, обращение к ко- 4. Developer's exchange. Êîëëåêöèÿ ãîòîâûõ ðåøåíèé íà ColdFusion
торым происходит несколько раз в минуту, весьма интен- http://devex.macromedia.com/developer/gallery/index.cfm
сивно расходуют оперативную память, что очень скоро 5. ColdFusion ôîðóìû
может привести к зависанию сервера. ColdFusion отсле- http://webforums.macromedia.com/coldfusion/
живает подобные ситуации автоматически. В части функ- 7. On-line äîêóìåíòàöèÿ ïî ColdFusion
циональных возможностей (без привлечения продуктов сто- http://www.macromedia.com/support/coldfusion/documentation.html
ронних фирм) ColdFusion также дает некоторую фору ASP.

60
программирование
программирование
Недавно я занимался разработкой утилитки, позволяющей запускать с сервера по сети некоторые
программы на определённое время. При этом обмен происходил через UDP-сокеты. Для написания
клиентской части необходимы были требования, чтобы она работала под управлением Windows
2000, и обычный непривилегированный пользователь не мог выключить или включить её.

ПРОГРАММИРОВАНИЕ
СЕРВИСОВ
В WINDOWS 2000 ВСЕВОЛОД СТАХОВ
62
программирование
Для этой цели идеально подходят сервисы — слу- функции открытия базы данных сервисов:
жебные программы, выполняющиеся в фоновом режи-
ме и без возможности завершения процесса через дис- SC_HANDLE OpenSCManager (const char *MachineName, const char*
петчер задач. Для управления сервисами используется DatabaseName, DWORD Desired Access);
админис тративный компонент Windows NT —
services.msc (находится в папке «Администрирование» Функция возвращает дескриптор базы данных серви-
панели управления). Как сервисы организовано множе- сов с именем DatabaseName, если данный параметр ра-
ство системных служб Windows, таких как сетевые кли- вен NULL, то используется база данных по умолчанию (для
енты и серверы, утилиты управления оборудованием этой же цели можно использовать константу
(пресловутый Plug and Play), а также драйверы ядра. SERVICES_ACTIVE_DATABASE) на компьютере
Из компонента управления сервисами можно изменять MachineName или на локальном компьютере, если дан-
режим работы сервисов. Сервис характеризуется со- ный параметр NULL. Параметр DesiredAccess определя-
стоянием: запущен, остановлен, приостановлен. Для ет режим доступа к файлу. Обычно используются констан-
управления сервисами используются соответствующие ты GENERIC_READ, для чтения GENERIC_WRITE для со-
кнопки: запустить, остановить, приостановить (доступ- здания новых сервисов или изменения параметров ста-
но лишь для некоторых сервисов), перезапустить. Мож- рых и GENERIC_EXECUTE разрешение на выполнение
но сделать, чтобы некоторые сервисы запускались при сервисов. При ошибке возвращается NULL, иначе — дес-
запуске компьютера. Для этого существует три режи- криптор базы данных.
ма запуска сервиса: автоматический (auto) — сервис Далее происходит регистрация сервиса функцией
запускается при входе в систему вручную, (manual) — CreateService, которая возвращает дескриптор сервиса
сервис запускается по требованию пользователя или для использования в данном процессе:
системы, отключено (disabled) — сервис вообще не бу-
дет запускаться. SC_HANDLE CreateService(
Для изменения режима запуска откройте диалог SC_HANDLE hSCManager,
свойств сервиса и на вкладке «Общие» выберите нуж- LPCTSTR lpServiceName,
LPCTSTR lpDisplayName,
ный режим. На данной вкладке также показано имя и DWORD dwDesiredAccess,
описание сервиса (их можно изменить), а также путь к DWORD dwServiceType,
DWORD dwStartType,
исполняемому файлу сервиса. Кроме того, можно из- DWORD dwErrorControl,
менять и другие параметры сервиса. Для этого смотри- LPCTSTR lpBinaryPathName,
LPCTSTR lpLoadOrderGroup,
те встроенную справку Windows. При любых манипуля- LPDWORD lpdwTagId,
циях с сервисами учтите, что изменение базы данных LPCTSTR lpDependencies,
LPCTSTR lpServiceStartName,
сервисов доступно только администраторам. Кроме это- LPCTSTR lpPassword
го, возможна модификация сервисов на членах NT до-
);
мена через MMC (Microsoft Management Console —
mmc.exe) пользователем, имеющим права администра-
тора домена. Итак, поподробнее о параметрах, так как я не думаю,
Создание сервиса в ОС Windows — задача нетриви- что их названия говорят сами за себя.
альная. Например, создание демона в ОС Unix намно- SC_HANDLE hSCManager — дескриптор базы дан-
го проще, хотя, я думаю, система сервисов лучше про- ных сервисов, полученный функцией OpenSCManager.
думана и более централизована. Для создания нового LPCTSTR lpServiceName — строка (до 256 символов),
сервиса, прежде всего, его нужно зарегистрировать в реальное имя сервиса в базе данных.
базе данных сервисов. Для этого открывается база дан- LPCTSTR lpDisplayName — данный параметр — имя
ных сервисов функцией OpenSCManager для записи-со- сервиса, которое показывается в инструменте управ-
здания, далее добавляется сам сервис функцией ления сервисами.
CreateService. После этого система может запустить DWORD dwDesiredAccess — флаг доступа к серви-
приложение, зарегистрировавшее себя сервисом, но за- су. Может принимать любые значения доступа.
пуск идёт несколько необычным образом: запускается Обычно используются константы GENERIC_READ,
не WinMain, а ServiceMain, которая определяет обра- GENERIC_WRITE, GENERIC_EXECUTE, а также значе-
ботчики событий сервиса (таких как запуск, пауза, ос- ние SERVICE_ALL_ACCESS для предоставления полно-
тановка) функцией RegisterServiceCtrlHandler, устанав- го доступа к сервису.
ливает текущее состояние сервиса SetServiceStatus и DWORD dwServiceType — флаг типа сервиса, то есть
выполняет функцию StartService для каждого зарегист- то, как он будет выполняться системой.
рированного сервиса (их может быть несколько). Не- Возможные значения:
много сложновато, не так ли? Но реально создать сер- SERVICE_WIN32_OWN_PROCESS — сервис суще-
вис ещё сложнее, так как данные функции принимают ствует в виде отдельного процесса;
очень много параметров, например функция SERVICE_WIN32_SHARE_PROCESS — сервис раз-
CreateProcess принимает аж 13 (!) параметров. Но я всё деляет процесс под названием services с другими сер-
же попытаюсь вкратце рассказать о каждой функции и висами;
приведу конкретный пример сервиса. Итак, начнём с SERVICE_WIN32_KERNEL_DRIVER — сервис явля-

№1, октябрь 2002 63


программирование
ется драйвером ядра (то есть сам является частью тается, что пароль — пустая строка. Для профиля локаль-
ядра); ной системы LocalSystem пароль всегда должен быть NULL.
SERVICE_WIN32_FILE_SYSTEM_DRIVER — сервис пред- Итак, если вы дочитали, до этого момента, то всё
ставляет собой драйвер файловой системы. Практически остальное должно показаться Вам детскими играми.
все сервисы, не являющиеся системными, создаются как Кстати, сообщу информацию, которая многим пока-
собственные процессы SERVICE_WIN32_OWN_PROCESS. жется полезной: после регистрации сервиса его пара-
В этом случае, если необходимо, чтобы сервис взаи- метры записываются в системный реестр Windows в улей
модействовал с рабочим столом, можно указать через HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services
битовое “или” флаг SERVICE_INTERACTIVE_PROCESS. \имя_сервиса. Для людей, любящих копаться в настрой-
DWORD dwStartType — флаг способа запуска сер- ках ОС сообщаю: большинство системных драйверов
виса, может принимать следующие значения: являются сервисами и их настройки можно поменять в
SERVICE_BOOT_START — служба запускается заг- реестре. Например, очень интересным является ключ
рузчиком, NT то есть при загрузке ядра (допустимо толь- Tcpip — можно менять очень много “скрытых” настроек
ко при создании драйвера ядра); сети (включая настройки сетевых карт!). Для поклон-
SERVICE_SYSTEM_START — сервис запускается ников безопасной сети ес ть ещё один ключик:
после загрузки ядра при инициализации драйверов (это Lanmanserver — позволяет прикрыть некоторые дыры
также допустимо только для драйверов); безопасности, например NetBios NULL session. Но вер-
SERVICE_AUTO_START — служба запускается при нёмся к реальности — к созданию сервисов. Чтобы еще
входе в систему; больше отпугнуть читателя, приведу пример создания
SERVICE_DEMAND_START — сервис запускается функции CreateService:
другим приложением (в том числе инструментом управ-
ления службами); CreateService (hsSManager,lpServiceName, lpDisplayName,
dwDesiredAccess, dwServiceType, dwStartControl,
SERVICE_DISABLED — сервис не может быть запу- lpBinaryPathName, lpLoadOrderGroup, lpdwTagId, lpDependencies,
щен. Для стандартных служб обычно применяются фла- lpServiceStartName, lpPassword);
ги SERVICE_AUTO_START и SERVICE_DEMAND_START
для, соответственно, автоматической или ручной заг- После создания сервиса считайте, что ваше дело за-
рузки. вершено – смело пишите CloseServiceHandle
DWORD dwErrorControl — флаг поведения при ошибке. (SC_HANDLE sh) и выходите из программы. Тут я сде-
Для большинства сервисов (кроме критичных и системных лаю небольшое лирическое отступление. Как извест-
драйверов) разумно писать SERVICE_ERROR_IGNORE для но, в ОС Windows после загрузки программы в память
игнорирования ошибки или SERVICE_ERROR_NORMAL вызывается специализированная функция main для кон-
для вывода сообщения о невозможности запуска (также сольных приложений и WinMain для приложений GUI
добавляется запись в системный журнал). Для драйверов Windows.
при невозможности их загрузки обычно происходит откат Программа, работающая как служба, ведёт себя не-
системы и (или) перезагрузка. сколько по-другому: вызов сервиса происходит функ-
LPCTSTR lpBinaryPathName — полный путь к испол- цией StartService(SC_HANDLE sh, DWORD param_count,
няемому файлу сервиса. Учтите, что если путь содер- char **parameters), после чего он загружается в память
жит пробелы или является очень длинным, то писать (если ещё не был загружен) и вызывается функция
его нужно в кавычках внутри строки, то есть “\”d:\\my ServiceMain(DWORD param_count, char **parameters), ко-
folder\\my_super service.exe\». торая также ведёт себя очень хитро, но об этом чуть
LPCTSTR lpLoadOrderGroup — имя группы сервиса, позднее.
обычное значение — NULL, для указания, что сервис Если для регистрации и для самого сервиса исполь-
не принадлежит никакой группе. зуется одна и та же программа, то обычно считывают
LPDWORD lpdwTagId — этот параметр содержит тег аргументы командной строки. При этом используется
группы, которые применяются драйверами для иденти- следующая схема:
фикации внутри группы, обычное значение — NULL.
LPCTSTR lpDependencies — массив строк-зависимо-
#include <windows.h>
стей сервиса. Если службы, от которых зависит дан-
ный сервис не запустились, то не произойдёт и запуска int main(int argc, char **argv){
данного сервиса. Данный параметр помогает выстро- SC_HANDLE sh;
ить иерархическую структуру служб. SC_HANDLE svdb; // Äåñêðèïòîð áàçû ñåðâèñîâ
SERVICE_TABLE_ENTRY DispatchTable[] =
LPCTSTR lpServiceStartName — имя пользователя {
для запуска сервиса. Имя вводится в формате Имя_до- { «MyService», MyServiceStart },
{ NULL, NULL }
мена\имя_пользователя (или .\имя_пользователя для };
локального домена). Если данный параметр NULL, то
if(argc < 2){ // Çàïóñê áåç àðãóìåíòîâ
используется системный профиль LocalSystem, что под- // Çàïóñê ñåðâèñà
ходит для большинства сервисов.
if (!StartServiceCtrlDispatcher( DispatchTable))
LPCTSTR lpPassword — параметр, определяющий пароль {
для выбранного профиля. Если параметр равен NULL, то счи-

64
программирование

write_to_log(«Can`t execute service»); DWORD dwControlsAccepted; — áèòîâàÿ ìàñêà, ñîäåðæàùàÿ äîïó-


} ñòèìûå ñîñòîÿíèÿ ñåðâèñà(÷åðåç ïîáèòíîå «èëè»).

Äîïóñòèìûå êîíñòàíòû:
}
if(argc == 2){ // Ïåðåäàí îäèí ïàðàìåòð SERVICE_ACCEPT_STOP - ñåðâèñ ìîæåò áûòü îñòàíîâëåí;
if(strcmp(argv[1], “-i”))
install_service(); // Óñòàíîâêà ñåðâèñà åñëè àð- SERVICE_ACCEPT_PAUSE_CONTINUE - ñåðâèñ ìîæåò áûòü ïîñòàâëåí
ãóìåíò -i è ñíÿò ñ ïàóçû;
if(strcmp(argv[1], “-u”)) SERVICE_ACCEPT_SHUTDOWN - ñåðâèñ áóäåò îïîâåù¸í ïðè âûõîäå
uninstall_service(); // Óäàëåíèå ñåðâèñà åñëè àð- èç ñèñòåìû.
ãóìåíò -u
} DWORD dwWin32ExitCode; — ýòîò ïàðàìåòð ñîîáùàåò ñèñòåìå
else{ çíà÷åíèå, êîòîðîå âîçâðàùàåò ñåðâèñ ïðè îøèáêå, ïîäðîáíåå îøèáêà
write_to_log(“Bad usage”); îïðåäåëÿåòñÿ ñëåäóþùèì ïàðàìåòðîì.
return -1; DWORD dwServiceSpecificExitCode; — êîíêðåòíàÿ îøèáêà, ïðî-
} èçîøåäøàÿ â ñåðâèñå.
return 0; DWORD dwCheckPoint; — à ýòî ïîëîæåíèå ïðîãðåññ-áàðà ïðè
çàïóñêå-îñòàíîâêå ñåðâèñà, èñïîëüçóåòñÿ äëÿ âèçóàëèçàöèè ïðî-
} öåññà çàïóñêà ñåðâèñà.
DWORD dwWaitHint; — âðåìÿ â ìèëëèñåêóíäàõ, êîòîðîå æä¸ò
âûçûâàþùàÿ ïðîãðàììà äî èçìåíåíèÿ ëèáî òåêóùåãî ñòàòóñà, ëèáî
dwCheckPoint. Åñëè ýòîãî íå ñëó÷èëîñü, òî ñ÷èòàåòñÿ, ÷òî çà-
В принципе, всё не так уж и сложно, хотя я кое-чего ïóñê ñåðâèñà áûë íåóäà÷åí. Åñëè äàííîå çíà÷åíèå íîëü, òî óáè-
не рассказал. Во-первых, функция OpenService служит åíèÿ íå ïðîèñõîäèò.
для загрузки сервиса в память. Функция принимает три
параметра: дескриптор базы сервисов, имя сервиса и тип };
доступа – тут всё понятно. Для удаления сервиса исполь-
зуется функция DeleteService(SC_HANDLE sh), которая Итак, обычно вначале устанавливается значение ста-
возвращает ноль в случае ошибки. При использовании туса SERVICE_START_PENDING, затем устанавливает-
данной функции учтите, что при открытии сервиса фун- ся обработчик сообщений, инициализация сервиса и
кцией OpenService, вы должны указать в правах досту- только после этого статус сервиса устанавливается в
па единственный флаг DELETE для удаления его из базы. SERVICE_RUNNING. При данных действиях не забудьте
Для запуска самого сервиса используется функция корректно установить dwWaitHint, иначе система сочтёт,
StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY что ваш сервис не успел запуститься, и замочит его без
lpServiceStartTable). Функция принимает единственный сожаления. Для регистрации обработчика событий ис-
аргумент — массив строк, содержащий две строки — имя пользуется функция SERVICE_STATUS_HANDLE
сервиса и имя функции-обработчика, заканчивается спи- RegisterServiceCtrlHandler(LPCTSTR service_name,
сок двумя пустыми строками. После этого начинается LPHANDLER_FUNCTION handler_function) — первый па-
обработка функции сервиса ServiceMain. раметр — имя сервиса, второй — указатель на функ-
Итак, начало самого интересного — написание фун- цию-обработчик, данная функция возвращает дескрип-
кции ServiceMain. Данная функция служит для запуска, тор состояния сервиса для функции SetServiceStatus или
инициализации и изменения статуса сервиса. Поведе- NULL в случае ошибки. Для установки текущего состоя-
ние данной функции строго регламентировано: во-пер- ния сервиса используется функция SetServiceStatus
вых, она должна заполнить поля струк т уры (SERVICE_STATUS_HANDLE ssh, LPSERVICE_STATUS
SERVICE_STATUS значениями параметров данного сер- status) — первый параметр — дескриптор состояния, а
виса; во-вторых — зарегистрировать обработчик собы- второй — указатель на структуру статуса. После этого
тий сервиса функцией RegisterServiceCtrlHandler; и, в- приведу простенький пример главной функции:
третьих — выполнить действия по инициализации сер-
виса и ус тановить сос тояние сервиса функцией void MyServiceStart (DWORD argc, LPTSTR *argv)
{
SetServiceStatus. Итак, обо всём по порядку. Думаю, от- DWORD status;
дельного описания заслу живает струк т ура DWORD specificError;
SERVICE_STATUS MyServiceStatus; //Ýòà òà ñàìàÿ
SERVICE_STATUS: ñòðóêòóðà ñòàòóñà
// À âîò ìû å¸ çàïîëíÿåì
MyServiceStatus.dwServiceType =
struct SERVICE_STATUS { SERVICE_WIN32_OWN_PROCESS;
DWORD dwServiceType; — ýòî ïîëå îçíà÷àåò òî æå, ÷òî è â MyServiceStatus.dwCurrentState =
ôóíêöèè CreateService, ò.å. òèï ïðèëîæåíèÿ ñåðâèñà(îòäåëüíûé, SERVICE_START_PENDING;
äðàéâåð ÿäðà, äðàéâåð ÔÑ). MyServiceStatus.dwControlsAccepted =
DWORD dwCurrentState; — à âîò ýòî ñïåöèôè÷åñêîå ïîëå - SERVICE_ACCEPT_STOP;
ñîäåðæèò òåêóùåå ñîñòîÿíèå ñåðâèñà, èìåííî åãî äîëæíà óñòà- MyServiceStatus.dwWin32ExitCode = 0;
íàâëèâàòü ServiceMain. MyServiceStatus.dwServiceSpecificExitCode = 0;
MyServiceStatus.dwCheckPoint = 0;
Äîïóñòèìûå çíà÷åíèÿ: MyServiceStatus.dwWaitHint = 5000;
SERVICE_STOPPED - ñåðâèñ îñòàíîâëåí; //Ðåãèñòðèðóåì îáðàáîò÷èê ñîáûòèé
SERVICE_START_PENDING - ñåðâèñ çàïóñêàåòñÿ; MyServiceStatusHandle =
SERVICE_STOP_PENDING - ñåðâèñ îñòàíàâëèâàåòñÿ; RegisterServiceCtrlHandler(«MyService»,
SERVICE_RUNNING - ñåðâèñ óæå çàïóùåí;
SERVICE_CONTINUE_PENDING - ñåðâèñ ïðîäîëæàåò ðàáîòó; MyServiceCtrlHandler);
SERVICE_PAUSE_PENDING - ñåðâèñ ïåðåõîäèò â ðåæèì ïàóçû;
SERVICE_PAUSED - ñåðâèñ íàõîäèòñÿ â ðåæèìå ïàóçû. if (MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0)

№1, октябрь 2002 65


программирование

{ write_to_log(«Yeah, this works!»,0);


write_to_log(«Can not register handler function»);
return; return;
} }
//À òåïåðü âûïîëíÿåì íåêîòîðûå èíèöèàëèçàöèîííûå äåé- Прекрасная функция, не так ли? Ну и для последне-
ñòâèÿ.
го штриха расскажу ещё про обработчик сообщений.
status = MyServiceInitialization(argc,argv, Если Вы когда-нибудь писали GUI программу, то, на-
&specificError);
верное, знаете, что обработчики событий — это длин-
// Ïðîâåðÿåì, êàê ïðîøëà èíèöèàëèçàöèÿ ная функция, которая получает номер события и выпол-
if (status != NO_ERROR)
няет в соответствии с этим некие действия. Так, в сер-
{ висах наблюдается нечто подобное. Формат функции
//Îøèáêà - îñòàíàâëèâàåì ñåðâèñ è âûõîäèì
MyServiceStatus.dwCurrentState = должен быть такой:
SERVICE_STOPPED; void WINAPI Handler(DWORD fdwControl); параметр
MyServiceStatus.dwCheckPoint = 0;
MyServiceStatus.dwWaitHint = 5000; fdwControl содержит текущее сообщение от системы.
MyServiceStatus.dwWin32ExitCode = status; Возможные значения этого параметра:
MyServiceStatus.dwServiceSpecificExitCode =
specificError; SERVICE_CONTROL_STOP — cервис должен оста-
новиться.
SetServiceStatus (MyServiceStatusHandle,
&MyServiceStatus); StartServiceCtrlDispatcher (LPSERVICE_TABLE_ENTRY
return; lpServiceStartTable) SERVICE_CONTROL_PAUSE —
}
cервис должен перейти в режим паузы.
// Âñ¸ ïðîøëî óñïåøíî :) èä¸ì äàëüøå. SERVICE_CONTROL_CONTINUE — cервис должен
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
MyServiceStatus.dwCheckPoint = 0; выйти из режима паузы.
MyServiceStatus.dwWaitHint = 0; SERVICE_CONTROL_INTERROGATE — cервис дол-
if (!SetServiceStatus (MyServiceStatusHandle,
&MyServiceStatus)) жен сообщить о себе информацию.
SERVICE_CONTROL_SHUTDOWN — cистема соби-
{
status = GetLastError(); рается выключаться.
write_to_log(«Can not set service status 8-(«); Ну вот, в принципе, и всё. Для дальнейшей инфор-
} мации Вы можете обратиться к MSDN. Но для большин-
ства сервисов этой информации оказывается достаточ-
//Ñåðâèñ íà÷àë ðàáîòàòü.
но. Итак, дерзайте!

66
СЕТИ
сети

АНАЛИЗАТОР
СЕТЕВОГО
ТРАФИКА
Введение На уровне протокола Ethernet маршру- ся сокет. Обычно используются значения
Если Вы - системный администратор, тизация отсутствует. Другими словами, кадр, PF_UNIX для соединений, ограниченных
специалист по безопасности, или Вам про- отправленный хостом-отправителем, не локальной машиной, и PF_INET для соеди-
сто интересно, что происходит в Вашей ло- попадает напрямую хосту-получателю, а бу- нений, базирующихся на протоколе IPv4.
кальной сети, то перехват и анализ несколь- дет доступен для всех хостов, объединен- Аргумент type определяет тип создаваемо-
ких сетевых пакетов может быть полезным ных в сеть. Каждая сетевая карта принима- го сокета и имеет несколько значений. Зна-
упражнением. При помощи небольшой про- ет кадр и считывает из него первые 6 байт. чение SOCK_STREAM указывается при со-
граммы на языке С и базовых знаний сете- Эти байты содержат MAC-адрес хоста-по- здании сокета для работы в режиме вирту-
вых технологий Вы сможете перехватить лучателя, но только одна карта в сети опре- альных соединений (протокол TCP), а зна-
данные сетевого траффика, даже если они делит его как свой собственный и передаст чение SOCK_DGRAM - для работы в режи-
адресованы не Вам. В данной статье мы кадр для дальнейшей обработки сетевому ме пересылки дейтаграмм (протокол UDP).
рассмотрим, как это можно сделать в сети драйверу. Сетевой драйвер проверит поле Последний параметр protocol определяет
Ethernet - наиболее распространенной на «Тип протокола» заголовка кадра и, осно- используемый протокол (в соответствии с
данный момент технологии построения ло- вываясь на этом значении, направит инкап- IEEE 802.3).
кальных компьютерных сетей. сулированный пакет соответствующей при- В версиях LINUX, начиная с 2.2, появил-
емной функции данного протокола. В боль- ся новый тип сокетов - пакетные сокеты.
Обзор технологии шинстве случаев это протокол IP. Прием- Пакетные сокеты используются для отправ-
Ethernet ная функция изымает IP заголовок из при- ления и приема пакетов на уровне драйве-
Для начала вспомним, как функциони- нятой дейтаграммы и передает инкапсули- ров устройств. Сокеты данного типа созда-
рует сеть Ethernet (те из вас, кто уже знаком рованное сообщение соответствующему ются вызовом socket (SOCK_PACKET, int
с данным вопросом, могут пропустить этот модулю протокола транспортного уровня type, int protocol). Параметр type равен или
параграф). IP-пакеты (дейтаграммы), источ- (например, TCP или UDP). Эти протоколы, SOCK_RAW, или SOCK_DGRAM.
ником которых является приложение в свою очередь, обрабатывают свои заго- Пакеты типа SOCK_RAW передаются
пользователя, инкапсулируются в Ethernet- ловки и передают данные протоколам при- драйверу устройства и принимаются от него
кадры (пакеты Ethernet-протокола, переда- кладного уровня. В течение этой «экскур- без всяких изменений данных пакета.
ваемые в сети). Каждый кадр содержит ис- сии» по различным уровням сетевого стека SOCK_DGRAM работает на более вы-
ходный IP-пакет и другую информацию, не- исходный пакет теряет все служебные поля соком уровне. Физический заголовок (MAC-
обходимую для доставки его адресату, в протоколов и, в конце концов, данные, пе- адрес) удаляется перед тем, как пакет от-
частности, 6-ти байтовый Ethernet-адрес редаваемые в пакете, принимаются пользо- правляется на обработку пользователю.
(MAC-адрес) назначения, который при по- вательским приложением.
мощи протокола ARP ставится в соответ- Пример реализации
ствие IP-адресу назначения. Таким образом, Пакетные сокеты Итак, приступим непосредственно к со-
сформированный кадр, содержащий пакет, При создании сокета стандартным вы- зданию анализатора. Для этого нам необ-
начинает свое путешествие от хоста-отпра- зовом socket (int domain, int type, int protocol) ходимо:
вителя к хосту-получателю через кабельное параметр domain определяет коммуникаци- - определить необходимые переменные;
соединение. онный домен, в котором будет использовать- - получить параметры сетевого интер-

68
сети
фейса (IP-адрес, маску подсети, номер под- Функция получения Получим маску подсети:
сети, размер MTU, индекс (номер) интер- параметров сетевого
фейса); интерфейса if (ioctl (fd, SIOCGIFNETMASK, ifr) <0 )
{
- создать пакетный сокет и привязать perror («ioctl»);
его к определенному интерфейсу; #include «ip.h» return (-1);
#include <sys/ioctl.h> }
- принять сетевой пакет и проанализи- int getifconfig (struct ifreq *ifr, char memset(&s, 0, sizeof (struct
ровать его структуру. Этим будет занимать- *intf, struct ifparam *ifp) sockaddr_in));
{ memcpy(&s, &ifr->ifr_netmask, sizeof
ся главная функция программы. (struct sockaddr));
Для удобства каждый из пунктов офор- int fd; - дескриптор сокета. memcpy(&ifp-mask, &to.sin_addr.s_addr,
sizeof (u_long));
мим в виде отдельной функции или заголо- Создадим сокет:
вочного файла. Получим номер подсети:
if (( fd= socket (AF_INET, SOCK_DGRAM,
0)) <0 ) {
Переменные perror ( «socket» );
ifp->sunbet = check_subnet(ifp->mask,
ifp->ip);
Необходимые заголовочные файлы: return ( - 1 );
}
#include <stdio.h> Получим размер MTU:
#include <sys/types.h> Скопируем имя интерфейса в поле
#include <sys/socket.h> if (ioctl (fd, SIOCGIFMTU, ifr) <0 ) {
#include <errno.h> ifr_name структуры ifr: perror («ioctl»);
#include <linux/if.h> return (-1);
#include <linux/if_ether.h> }
#include <linux/in.h> sprintf (ifr->ifr_name, «%s»,
intf); ifp -> mtu = ifr -> ifr_mtu;
#include <linux/ip.h>
Структура для хранения принятого IP - Получим IP адрес интерфейса: Получим индекс (номер) интерфейса:
пакета:
if (ioctl (fd, SIOCGIFADDR, ifr) <0 ) {
perror («ioctl»); if ( ioctl (fd, SIOCGIFINDEX, ifr) <0 )
struct ip_packet { return (-1); {
struct iphdr ip; } perror («ioctl»);
char *ip_data; memset(&s, 0, sizeof (struct return (-1);
} ip_pack; sockaddr_in)); }
memcpy(&s, &ifr->ifr_addr, sizeof (struct ifp -> index = ifr -> ifr_ifindex;
Cтруктура для хранения параметров sockaddr));
сетевого интерфейса: memcpy(&ifp-ip, &to.sin_addr.s_addr, Переведем интерфейс в неразборчи-
sizeof (u_long));
вый режим. Для этого получим значение
struct ifreq ifr;

Структура для получения адресной ин-


формации:

struct sockaddr_in s;

Структура для хранения заголовка IP-


пакета:

struct iphdr *ip;

Структура для хранения заголовка


Ethernet-кадра:

struct ethhdr eth;

Вспомогательная структура, содержа-


щая параметры интерфейса:

struct ifparam {
u_long ip;
u_long mask;
u_long subnet;
int mtu;
int index;
} ifp;

int e0_r, - дескриптор сокета;


rec; - размер принятого пакета в байтах;
char *buff; - буфер для приема пакетов.
Переменные разместим в файле ip.h.

№1, октябрь 2002 69


сети
флагов интерфейса: Функция check_subnet сканирует пер- return (fd);
}
if ( ioctl (fd, SIOCGIFFLAGS, ifr)
вый переданный параметр (маску подсе-
<0 ) { ти) до первого появления 1 и запоминает Функция getsock_recv принимает в ка-
perror («ioctl»); эту позицию. Эта позиция соответствует честве параметра индекс интерфейса и
close (fd);
return (-1); числу разрядов, отведенных в адресе на возвращает дескриптор пакетного сокета.
} сетевую составляющую. Далее, во втором Значение поля protocol в системном вызо-
Установим флаг неразборчивого ре- переданном параметре (адресе) происхо- ве socket равно htons (ETH_P_ALL). Это
жима: дит логический сдвиг на число разрядов, означает, что мы будем принимать пакеты
отведенных под адрес хоста. Таким обра- всех протоколов. Все входящие пакеты
ifr -> ifr_flags |= IFF_PROMISC; зом, у нас остается только сетевая состав- будут передаваться пакетному сокету до
ляющая, которая и является адресом под- того, как они будут переданы протоколам,
Установим новое значение флагов ин- сети. реализованным в ядре. Список возможных
терфейса: протоколов приведен в файле <linux/
Функция для создания if_ether.h>.
if ( ioctl (fd, SIOCSIFFLAGS, ifr) пакетного сокета Для получения пакетов только с опре-
<0 ) {
perror («ioctl»); Заголовочные файлы: деленного интерфейса используется фун-
close (fd); кция bind: таким образом мы соединяем
return (-1);
} #include <sys/types.h> пакетный сокет с интерфейсом, адрес ко-
return 1; #include <sys/socket.h>
#include <errno.h > торого указан в структуре struct sockaddr_ll.
}
#include <linux/if_packet.h> Если этого не сделать, мы будем получать
#include <linux/if_ether.h>
Параметрами функции getifconfig явля- int getsock_recv (int index) пакеты со всех сетевых интерфейсов, ко-
ются структура struct ifreq *ifr, имя интер- { торые в данный момент активны.
int fd;
фейса char *intf и структура struct ifparam
*ifp. Номер подсети определяется при по- Структура для хранения адресной ин- Главная функция
мощи вызова функции check_subnet, при- формации об интерфейсе (см. файл <linux/ #include «ip.h»
веденной ниже. if_packet.h>): int main ()
{
Установкой флага интерфейса
IFF_PROMISC мы добиваемся приема всех struct sockaddr_ll s_ll; Получим параметры сетевого интер-
пакетов, даже если они не адресованы фейса:
нашему хосту. Создадим пакетный сокет:
memset (&ifr, 0, sizeof (struct ifreq));
if (( fd= socket (SOCK_PACKET, SOCK_RAW, if (getifconfig (&ifr, «eth0», &ifp)
Функция определения htons (ETH_P_ALL) )) <0 ) { <0 ) {
номера подсети perror («getifconfig»);
perror ( «socket» ); exit (1);
return ( - 1 );
BITS 32 } }
GLOBAL check_subnet

SECTION .text
Выделим память для структуры struct Выделим память:
check_subnet: sockaddr_ll s_ll:
push ebp - ñîõðàíèì buff = (char *) malloc (ifp.mtu + 18);
àäðåñ âîçâðàòà èç ôóíêöèè memset(&ip, 0, sizeof (struct
mov ebp, esp memset (&s_ll, 0, sizeof (struct ip_packet));
mov edx, [ebp+8] - ïåðâûé ïà- sockaddr_ll)); ip_pack.ip_data = (char *) malloc (
ðàìåòð - ìàñêà ïîäñåòè ifp.mtu - sizeof (struct iphdr));
mov eax, [ebp+12] - âòîðîé ïà- ip=(struct iphdr *)&ip_pack.ip;
ðàìåòð - IP àäðåñ Заполним поля структуры s_ll необхо-
mov cx, 32 - ÷èñëî ðàç-
ðÿäîâ â IP àäðåñå â ôîðìàòå IPv4 димыми значениями: Получим дескриптор пакетного сокета:
push cx - ñîõðàíèì çíà÷åíèå
â ñòåêå
xor esi, esi - îáíóëèì s_ll.sll_family = PF_PACKET; if ((e0_r = getsock_recv (ifp.index)) <0
ñ÷åò÷èê - òèï ñîêåòà ) {
.label s_ll.sll_protocol = htons (ETH_P_ALL); perror («getsock_recv»);
bt edx, esi - ñêàíèðóåì - òèï ïðèíèìàåìîãî ïðîòîêîëà exit(1);
ìàñêó â ïîèñêàõ 1 s_ll.sll_ifindex = index; }
jnc .msk - âûõîä èç - íîìåð èíòåðôåéñà
öèêëà ïðè ñîâïàäåíèè s_ll.sll_pkttype = PACKET_HOST; Цикл приема пакетов:
inc esi - èíêðåìåíò - òèï ïàêåòà (äëÿ ëîêàëüíîé ìàøèíû)
ñ÷åò÷èêà
loop .label - ïðîäîëæèòü Привяжем сокет к интерфейсу: for (;;) {
ïîèñê
.mask Îáíóëèì áóôåð:
pop cx - èçâëå÷ü if ((bind (fd, (struct sockaddr bzero (buff, ifp.mtu+18);
ðàíåå ñîõðàíåííîå çíà÷åíèå èç ñòåêà *) &s_ll, sizeof (struct sockaddr_ll)) rec = 0;
sub cx, si - ÷èñëî ðàçðÿäîâ â <0 ) { Принять пакет:
àäðåñå, îòâåäåííûõ ïîä õîñòîâóþ ÷àñòü perror («bind»);
close (fd); rec=recvfrom (e0_r, (char
shl eax, cl - ëîãè÷åñêèé ñäâèã *)buff, ifp.mtu+18, 0, NULL, NULL);
íà ýòî çíà÷åíèå return (-1);
} if (rec<0) {
mov esp, ebp perror («recvfrom»);
pop ebp - âîññòàíîâèì ñòåê Возвратим дескриптор сокета в вызы- exit(1);
ret - âîçâðàò èç ôóíê- }
öèè вающую функцию:

70
сети
Число принятых байт (длина принято- inet_ntoa (ip -> saddr)); - à ä ð å ñ # Êîìïèëÿòîð Ñ
го пакета): èñòî÷íèêà CC = gcc
printf («%s \n», inet_ntoa # Êîìïèëÿòîð àññåìáëåðà
(ip -> daddr)); - àäðåñ NASM = nasm
íàçíà÷åíèÿ # Èìÿ èñïîëíÿåìîãî ìîäóëÿ
printf («\nrec = %d\n», rec);
name = ip
} IP = ip.o check_snet.o
Первые 12 байт в принятом буфере return (1); getsock_recv.o getifconf.o
} $(name): $(IP)
содержат MAC - адреса отправителя и по- $(CC) -g -o $(name) $(IP)
лучателя. Заполним структуру struct ethhdr ip.o: ip.c
$(CC) -c ip.c
eth адресной информацией: Прием пакетов осуществляется с по- check_snet.o: check_snet.asm
мощью функции recvfrom. Эта функция $(NASM) -f elf
check_snet.asm
memcpy ((char *) &eth, buff, 12); принимает данные через дескриптор e0_r. getsock_recv.o: getsock_recv.c
Принятое сообщение копируется в струк- $(CC) -c getsock_recv.c
getifconf.o: getifconf.c
По смещению 14 в данном буфере рас- туру ip_pack. $(CC) -c getifconf.c
положены данные Ethernet-кадра - IP-па- В принятом пакете первым следует за- clean:
rm -f *.o
кет: головок Ethernet-кадра. По смещению 14
расположен IP-пакет. Поле «Версия» ука- В файле ip.c находится главная функция
memcpy ((char *)&ip.pack, (buff + 14), зывает тип данного пакета. Для IPv4-па- программы. Командой make мы получим ис-
ifp.mtu );
кета данное поле содержит значение 4 в полняемый модуль ip. Команда make clean
Проведем анализ принятого Ethernet- двоичной форме. Значение длины заголов- удалит все объектные файлы. Результаты
кадра. ка лежит в диапазоне между 20 и 60 байта- работы программы будут отображаться на
ми и находится в поле «Длина заголовка». консоли. Иногда это не совсем удобно, по-
if ((ip -> version) !=4)
continue; Поле «Протокол» содержит идентифика- этому, немного модифицировав программу,
цию протокола следующего, более высо- результаты можно сохранять в файле.
версия IP-протокола MAC-адрес отправи- кого уровня, содержащегося в разделе дан- Компиляцию производим с ключем -g для
теля: ных (т.е. в теле сообщения) данного IP- возможности последующей отладки. Наде-
пакета. В документе RFC 1700 перечисле- юсь, что читателю это не понадобится, но
printf (« %.2x: %.2x: %.2x: %.2x: %.2x: ны все значения, которые могут содержать- все-таки хочу показать простой прием поис-
%.2x \t -> \t «,
eth.h_source[0], eth.h_source[1], ся в поле «Протокол» в заголовке IP-паке- ка неисправностей в программе (не только в
eth.h_source[2], та. Поле «Адрес источника» и поле «Адрес этой). Иногда программа, хотя и компилиру-
eth.h_source[3], eth.h_source[4],
eth.h_source[5]); назначения» содержат соответственно IP- ется без ошибок, при запуске выдает сооб-
адрес отправителя пакета и IP-адрес пред- щение Segmentation fault и завершается. Для
MAC-адрес получателя: полагаемого получателя. нашего примера, если программа работает
Дальнейшая обработка принятого па- некорректно, запустите исполняемый файл
printf (« %.2x: %.2x: %.2x: %.2x: %.2x:
%.2x», кета зависит от полей «Длина заголовка» ip на отладку командой gdb ip. В командной
eth.h_dest[0], eth.h_ dest[1], eth.h_ и «Протокол». В принятом буфере по сме- строке отладчика наберите run и изучите
dest[2],
eth.h_ dest[3], eth.h_ dest[4], eth.h_ щению, указанном в поле «Длина заго- информацию, которую выдаст отладчик. Он
dest[5]); ловка» (с учетом заголовка кадра Ethernet) укажет место, где программа аварийно за-
printf («%d \n», ip -> ihl);
- äëèíà çàãîëîâêà IP-ïàêåòà будет расположен заголовок протокола вершилась, и Вам останется только устра-
printf («%d \n», ntohs (ip следующего уровня. Его анализ аналоги- нить неисправность. Если все в порядке, то
-> tot_len));
- äëèíà âñåãî ïàêåòà чен вышеприведенному анализу заголов- перекомпилируйте программу с ключем -s.
printf («%d \n», ip -> ка IP. В следующей статье мы рассмотрим, как
protocol); - ïðîòîêîë
âåðõíåãî óðîâíÿ Для получения исполняемого модуля можно передать принятый пакет, создав, тем
printf («%s \t -> \t», создадим Makefile следующего содержания: самым, простейший шлюз.
сети

СКАНЕР ПОРТОВ:

ПРИМЕР
РЕАЛИЗАЦИИ

ВЛАДИМИР МЕШКОВ
Введение Процесс, получающий или отправ- ходится в состоянии LISTEN (прослу-
Подавляющее большинство сете- ляющий данные, идентифицируется шиваться). Быстро определить состо-
вых служб использует при работе про- на этом уровне номером, который на- яние порта позволяет специальное
токол TCP. Согласно модели OSI, ТСР зывается номером порта, или просто программное обеспечение - сканер
является протоколом транспортного портом. Другими словами, порт опре- портов.
уровня. Он обеспечивает надежное деляет сетевую службу, которой пред- Сканирование портов выполняет-
двунаправленное соединение между назначен пакет. ся, как правило, для того, чтобы най-
двумя процессами; данные передают- Для возможности установления ти на узле службу, уязвимую с точки
ся в обоих направлениях без ошибок, соединения с какой-либо сетевой зрения безопасности сети. Это свое-
пакеты не теряются и не дублируют- службой соответствующий ей порт го рода разведка, которая может осу-
ся, последовательность передачи дан- должен быть открыт, или, выражаясь ществляться как администратором
ных не нарушается. терминологией TCP-соединения, на- сети, так и злоумышленником.

72
сети
Целью данной статьи является читать в статье «NESSUS - современ- добятся следующие header-файлы:
описание принципов функционирова- ный анализ безопасности, методы
ния и внутреннего устройства просто- сканирования»(http://www.hack #include <stdio.h>
#include <stdlib.h>
го сканера портов TCP протокола. zone.ru/articles/nessus.htm#scan), а #include <errno.h>
также в документации на сканер #include <sys/types.h>
#include <sys/socket.h>
Порядок установления Nmap (http://www.insecure.org/nmap). #include <sys/ioctl.h>
TCP соединения Мы рассмотрим один из методов - #include <linux/in.h>
#include <linux/ip.h>
SYN-сканирование. Этот метод часто #include <linux/tcp.h>
Для понимания принципа работы называют half-open (полуоткрытым) #include <linux/if.h>
#include <linux/if_ether.h>
сканера необходимо знать, каким об- сканированием, т.к. полное TCP-со-
разом устанавливается TCP соедине- единение с портом сканируемой ма- структуры:
ние. шины не устанавливается. Суть дан- struct ifreq *ifr - ñòðóêòóðà äëÿ
При установлении соединения за- ного метода заключается в следую- õðàíåíèÿ ïàðàìåòðîâ ñåòåâîãî èíòåðôåéñà
действуются поля «Порядковый но- щем. Вы посылаете TCP-пакет с ус- struct iphdr *ih - ñòðóêòóðà, ñîäåð-
æàùàÿ çàãîëîâîê IP-ïàêåòà
мер» (SEQ), «Номер подтверждения» тановленным флагом SYN, как если sturct tcphdr *th - ñòðóêòóðà, ñîäåð-
(ACK-SEQ), флаги SYN, ACK и RST бы собирались установить реальное æàùàÿ çàãîëîâîê TCP-ïàêåòà
struct sockaddr_in local - ñòðóêòóðà,
заголовка TCP-пакета. Флаг SYN яв- соединение с выбранным портом, и ñîäåðæàùàÿ àäðåñíóþ èíôîðìàöèþ î ëîêàëü-
ляется флагом синхронизации. Он ожидаете ответ. Принятый пакет с ус- íîé ñèñòåìå
struct sockaddr_in dest - ñòðóêòóðà,
используется при установлении со- тановленными флагами SYN и ACK ñîäåðæàùàÿ àäðåñíóþ èíôîðìàöèþ îá óäà-
единения и устанавливает начальный указывает на то, что выбранный порт ëåííîé ñèñòåìå
struct p_header {
порядковый номер, используемый для открыт и ожидает соединения. Флаг u_long s_addr;
последующей передачи данных. Флаг RST означает обратное. Если получен u_long d_addr;
u_char zer0;
ACK указывает на то, что поле номе- SYN|ACK-пакет, следует немедленно u_char protocol;
ра подтверждения содержит досто- отправить пакет с флагом RST для u_int lenght;
}
верные данные. Флаг RST использу- сброса соединения, хотя реально за *pseudo - ïñåâäîçàãîëîâîê. Íåîáõî-
ется для сброса соединения. вас это сделает ядро. Преимуществом äèì ïðè ðàñ÷åòå êîíòðîëüíîé ñóììû TCP-
ïàêåòà (âçÿò èç èñõîäíûõ òåêñòîâ ñêàíå-
Соединение устанавливается в 3 данной технологии является отсут- ðà Nmap)
этапа: ствие в log-файлах сканируемой ма-
n инициатор соединения (клиент) шины записей о попытках установле- и переменные:
формирует SYN-пакет (TCP-пакет ния соединения с ней. Недостаток -
с установленным флагом SYN), за- необходимость наличия прав root для int fd - äåñêðèïòîð âñïîìîãàòåëüíîãî
ñîêåòà
полняет поле SEQ и передает па- формирования SYN-пакета. e0_s- äåñêðèïòîð ñîêåòà äëÿ ïåðåäà÷è
кет серверу; e0_r- äåñêðèïòîð ñîêåòà äëÿ ïðèåìà
sent- ÷èñëî ïåðåäàííûõ áàéò
n если порт, на который пришел зап- Пример реализации rec- ÷èñëî ïðèíÿòûõ áàéò
рос на соединение, открыт, сервер сканера port- íîìåð ñêàíèðóåìîãî ïîðòà
index- èíäåêñ èíòåðôåéñà, ÷åðåç êîòî
формирует SYN|ACK-пакет, запол- ðûé îñóùåñòâëÿåòñÿ ñêàíèðîâàíèå.
няет поля SEQ, ACK-SEQ и пере- Приведенный ниже код был разра- u_char *packet- ïàêåò, ïåðåäàâàåìûé â
ñåòü.
дает пакет клиенту. Значение поля ботан и протестирован в ОС GNU/
ACK-SEQ равно значению поля Linux, дистрибутив Slackware 7.1, ком-
SEQ из пакета клиента, увеличен- пилятор gcc-2.95.2. Сокет для передачи
ному на 1 (т.е. ACK-SEQ-сервера Алгоритм реализации следующий:
= SEQ-клиента + 1). Если порт зак- n определить необходимые пере- Дескриптор сокета для передачи
рыт, клиенту отправляется RST- менные и заголовочные файлы; получим при помощи функции
пакет; n создать сокеты для приема и пе- getsock_send, приведенной ниже.
n получив SYN|ACK-пакет, клиент редачи пакетов;
проверяет поле ACK-SEQ и высы- n сформировать SYN-пакет и отпра- Необходимые заголовочные файлы:
лает серверу ACK-пакет. После вить его сканируемому хосту; #include <errno.h>
этого соединение считается уста- n принять ответный пакет и проана- #include <sys/types.h>
#include <sys/socket.h>
новленным и переходит в фазу лизировать состояние флагов #include <linux/in.h>
обмена данными между клиентом SYN, ACK, RST; #include <linux/if.h>
#include <linux/if_ether.h>
и сервером. n сделать вывод о статусе проверя- int getsock_send ( char *intf)
емого порта. - âûçîâ ôóíêöèè
Методы сканирования
Заголовочные файлы и Функция getsock_send принимает
Существует достаточно большое переменные строковое значение (имя интерфейса)
число методов сканирования, каждый и возвращает дескриптор сокета в
из которых имеет свои преимущества Заголовочные файлы и перемен- случае положительного завершения
и, соответственно, недостатки. Под- ные разместим в файле, который на- или -1, если произошла ошибка.
робнее о каждом из них можно про- зовем scan.h. Для работы нам пона-

№1, октябрь 2002 73


сети
Переменные: if ( setsockopt ( fd, SOL_SOCKET, return ( - 1 );
SO_BINDTODEVICE, (void *)&ifr, sizeof }
int fd - äåñêðèïòîð ñîêåòà (ifr)) <0 ) {
const int on=1 - ôëàã âêëþ÷åíèÿ çàãî- perror ( « SO_BINDTODEVICE» Пакетный сокет имеет тип
ëîâêà (ñì. íèæå) );
struct ifreq ifr - ñòðóêòóðà äëÿ õðà- close ( fd ); SOCK_DGRAM. Это означает, что за-
íåíèÿ ïàðàìåòðîâ ñåòåâîãî èíòåðôåéñà. return ( -1 ); головок физического уровня (MAC-
}
адрес в случае Ethernet) будет отбро-
Сокет создадим следующим сис- Опция SO_BINDTODEVICE являет- шен при приеме.
темным вызовом: ся опцией сокета, параметр level вы- Выделим память для структуры
зова setsockopt принимает значение struct sockaddr_ll s_ll:
if (( fd = socket ( AF_INET, SOL_SOCKET.
SOCK_RAW, htons(ETH_P_IP) )) < 0 ) {
perror ( «socket» ); Опция SO_BINDTODEVICE исполь- memset (&s_ll, 0, sizeof (struct
return (-1); sockaddr_ll));
}
зует экземпляр структуры ifreq. Вызов
setsockopt считывает из буфера дан- Заполним поля структуры s_ll не-
В данном и последующих вызовах ной структуры имя интерфейса, при обходимыми значениями:
мы будем включать код, обрабатыва- помощи которого будут обслуживать-
ющий возможные ошибки. Комменти- ся все доступы к рассматриваемому s_ll.sll_family = PF_PACKET;
- òèï ñîêåòà
ровать его особого смысла нет, и так сокету. Поэтому вначале мы заполня- s_ll.sll_protocol = htons
все понятно, надеюсь. ем соответствующее поле структуры (ETH_P_ALL); - òèï ïðèíèìàåìîãî ïðî-
òîêîëà
Сокеты типа SOCK_RAW (RAW- ifreq именем интерфейса, который s_ll.sll_ifindex = index;
сокеты) домена AF_INET удобны тем, был передан как параметр функции - íîìåð èíòåðôåéñà
s_ll.sll_pkttype = PACKET_HOST;
что позволяют получить непосред- getsock_send. - òèï ïàêåòà (äëÿ ëîêàëüíîé ìàøèíû)
ственный доступ к служебным полям Если все успешно, возвращаем в
протокола TCP/IP, в отличии от типов главную функцию дескриптор сокета: Для получения пакетов только с
SOCK_STREAM и SOCK_DGRAM (для return ( fd ). определенного интерфейса использу-
TCP и UDP соединений, соответствен- ется функция bind: таким образом мы
но). Существуют также пакетные со- Сокет для приема соединяем пакетный сокет с интер-
кеты, о которых я рассказывал в ста- фейсом, номер которого указан в
тье «Анализатор сетевого траффи- Дескриптор сокета для приема структуре struct sockaddr_ll s_ll.
ка», на них мы сейчас останавливать- получим при помощи функции Привяжем сокет к интерфейсу:
ся не будем. getsock_recv (пример данной функции
if ((bind (fd, (struct sockaddr *)
Так как формировать пакеты мы был рассмотрен ранее в статье «Ана- &s_ll, sizeof (struct sockaddr_ll)) <0
будем вручную, то необходимо в оп- лизатор сетевого траффика»). ) {
perror («bind»);
циях сокета указать данный факт. Заголовочные файлы: close (fd);
Делается это при помощи системно- return (-1);
#include <sys/types.h> }
го вызова setsockopt следующим об- #include <sys/socket.h>
разом: #include <errno.h >
#include <linux/if_packet.h>
#include <linux/if_ether.h> Возвратим дескриптор сокета в
if ( setsockopt ( fd, IPPROTO_IP, вызывающую функцию:
IP_HDRINCL, ( const void *) &on, sizeof int getsock_recv (int index)
( on ) ) < 0 ) { - âûçîâ ôóíêöèè
perror ( «setsockopt return (fd)
IP_HDRINCL» );
close ( fd ); Функция getsock_recv принимает в
return ( -1 ); качестве параметра индекс интер-
}
фейса и возвращает дескриптор со- Главная функция
Опция IP_HDRINCL является опци- кета.
ей протокола IP, поэтому параметр Дескриптор сокета: #include «scan.h» - ôàéë ñ ïåðåìåííû-
level вызова setsockopt равен ìè è header-ôàéëàìè
int main ( int argc, char *argv [ ] )
IPPROTO_IP. Если опция IP_HDRINCL int fd; - âûçîâ ãëàâíîé ôóíêöèè
установлена, приложение строит и
вставляет в исходящий пакет полный Структура для хранения адресной Главной функции мы передаем два
IP заголовок. Данная опция включа- информации об интерфейсе (см. файл параметра: IP-адрес сканируемого
ется ненулевым значением (const int <linux/if_packet.h>): хоста (аргумент argv[1]) и номер пор-
on=1). та (аргумент argv[2]). Обработкой
Поскольку мы собираемся рабо- struct sockaddr_lls_ll; ошибочного ввода параметров будет
тать через определенный интерфейс, заниматься функция usage ():
необходимо осуществить привязку Создадим пакетный сокет: void usage () {
сокета к выбранному интерфейсу. Для printf ( « \n scan [ dest_IP ] [
if (( fd= socket (SOCK_PACKET, dest_port ] \n» );
этой цели также используется систем- return;
SOCK_DGRAM, htons (ETH_P_ALL) )) <0 )
ный вызов setsockopt: { }
sprintf ( ifr.ifr_name, «%s», intf ); perror ( «socket» ); Первое, что необходимо сделать

74
сети
при запуске программы, это прове- p_header )); - îáíóëèì ñòðóêòó- printf ( «IP-àäðåñ íàçíà÷åíèÿ \t -\t
рить, все ли необходимые параметры ðó %s \n «, inet_ntoa ( ih -> daddr ));
pseudo -> s_addr = printf ( «IP-àäðåñ èñòî÷íèêà \t -
указаны: local.sin_addr.s_addr; - \t %s \n «, inet_ntoa ( ih -> saddr ));
àäðåñ ëîêàëüíîãî õîñòà printf ( «Ïîðò íàçíà÷åíèÿ \t \t -
if ( argc != 3 ) { pseudo -> d_addr = \t %d \n «, ntohs ( th -> dest ));
usage (); dest.sin_addr.s_addr; - printf ( «Ïîðò èñòî÷íèêà \t\t - \t
exit ( 1 ); àäðåñ óäàëåííîãî õîñòà %d \n «, ntohs ( th -> source ));
} pseudo -> protocol = 6;
- ïðîòîêîë (TCP)
Преобразуем введенные строко- pseudo -> lenght = htons ( sizeof Создадим сокеты для передачи и
( struct tcphdr )); - äëèíà ïñåâäîçàãî-
вые значения адреса и порта в сете- ëîâêà приема пакетов.
вой формат и заполним адресную
структуру удаленной системы: Сформируем TCP заголовок. if (( e0_s = getsock_send ( «eth0» ))
< 0 ) {
perror ( «getsock_send» );
port = atoi ( argv [2] ); memset ( th, 0, sizeof ( struct tcphdr exit ( 1 );
memset ( &dest, 0, sizeof ( struct )); - îáíóëèì ñòðóêòóðó }
sockaddr_in )); th -> source = local.sin_port; -
dest.sin_addr.s_addr = inet_ntoa ( argv ëîêàëüíûé ïîðò if (( e0_r = getsock_recv ( index
[1] ); th -> dest = dest.sin_port; )) < 0 ) {
dest.sin_port = htons ( port ); - óäàëåííûé ïîðò perror ( «getsock_recv» );
th -> seq = htonl ( 1156270349 ); exit ( 1 );
- íà÷àëüíûé ïîðÿäêîâûé íîìåð }
Тоже самое проделаем для ло- th -> ack_seq = 0; -
кального хоста. íîìåð ïîäòâåðæäåíèÿ Передадим сформированный
th -> doff = 5; -
äëèíà çàãîëîâêà ( â 32-õ ðàçðÿäíûõ ñëî- SYN-пакет хосту назначения.
memset ( &local, 0, sizeof ( struct âàõ )
sockaddr_in )); th -> syn = 1; - dest.sin_family = AF_INET;
óñòàíîâèòü ôëàã SYN sent = sendto ( e0_s, (char *)
th -> window = htons ( 3072 ); - packet, ntohs ( ih -> tot_len), 0, (
ðàçìåð îêíà struct sockaddr *)&dest,
Получим IP-адрес интерфейса и th -> check = 0; - sizeof (struct
занесем его в адресную структуру îáíóëèòü ïîëå êîíòðîëüíîé ñóììû sockaddr_in));
th -> check = in_cksum (( u_short if ( sent <= 0 ) {
local: *)pseudo, sizeof ( struct tcphdr) + sizeof perror ( «sendto» );
( struct p_header)); exit ( 1 );
fd = socket ( AF_INET, SOCK_DGRAM, 0 }
); printf ( «\n Ïåðåäàíî %d áàéò \n»,
sprintf ( ifr -> ifr_name, «%s», Алгоритм расчета контрольной sent );
«eth0»); суммы для заголовков TCP и IP оди-
octl ( fd, SIOCGIFADDR, ifr );
memcpy (( char *) &local, ( char *)&( наковый и будет изложен ниже. Примем ответ на наш запрос. При-
ifr -> ifr_addr ), sizeof ( struct Сформируем IP заголовок. ем будем осуществлять в бесконеч-
sockaddr ));
local.sin_port = htons (53); ном цикле, каждый раз обнуляя при-
memset ( ih, 0, sizeof ( struct iphdr емный буфер.
)); - îáíóëèì ñòðóêòóðó
Получим индекс интерфейса: ih -> version = 4;
- âåðñèÿ ïðîòîêîëà for ( ; ; ) {
ioctl ( fd, SIOCGIFINDEX, ifr ); ih -> ihl = 5; -
index = ifr -> ifindex; äëèíà çàãîëîâêà (÷èñëî 32-õ áèòíûõ ñëîâ) bzero ( packet, sizeof (packet));
ih -> tot_len = htons (sizeof rec = 0;
(struct iphdr)+sizeof(struct tcphdr));
Выделим память для хранения - äëèíà ïàêåòà rec = recvfrom ( e0_r, (char *)
данных, передаваемых и принимае- ih -> id = 3290; packet, sizeof ( struct iphdr ) + sizeof
- ïîðÿäêîâûé íîìåð ïàêåòà (èäåíòèôèêà- ( struct tcphdr ),
мых из сети. Пакет будет состоять öèÿ) 0, NULL,
только из заголовков IP и TCP прото- ih -> ttl = 42; NULL );
- âðåìÿ æèçíè if ( rec <0 || rec > 1500 ) {
колов. ih -> protocol = 6; perror ( «recvfrom» );
- òðàíñïîðòíûé ïðîòîêîë (TCP) exit ( 1 );
packet = ( u_char * )malloc( sizeof ( ih -> saddr = }
struct iphdr ) + sizeof ( struct tcphdr local.sin_addr.s_addr; - ëîêàëüíûé
)); àäðåñ Число 1500 определяет макси-
ih -> daddr = dest.sin_addr.s_addr;
- óäàëåííûé àäðåñ мальный размер MTU для сети
Внутри общего пакета разместим ih -> check = in_cksum (( u_short Ethernet. Больше этого значения в
*) ih, sizeof (struct iphdr )); -
служебные заголовки IP и TCP прото- êîíòðîëüíàÿ ñóììà одном пакете принять мы не можем,
колов, а также псевдозаголовок. и любое превышение данного преде-
Некоторые поля заголовков (на- ла трактуется как ошибка.
in = ( struct iphdr * ) packet; пример, поле «Начальный порядко- Теперь займемся анализом приня-
th = ( struct tcphdr * ) (packet +
sizeof ( struct iphdr )); вый номер» TCP заголовка и поле того пакета.
pseudo = (struct p_header *) ( packet «Идентификация» заголовка IP) были Для начала проверим соответ-
+ sizeof ( struct iphdr) - sizeof ( struct
p_header )); выбраны совершенно произвольно, ствие версии протокола IP. Поле
т.к. в данном случае эти значения ни «Версия» должно содержать 4. Если
Заполним поля псевдозаголовка на что не влияют. это не так (к нам мог поступить ARP-
необходимыми значениями. Отобразим для контроля имеющу- запрос, который мы не собираемся
memset ( pseudo, 0, sizeof ( struct юся адресную информацию: обрабатывать), то принятый пакет от-

№1, октябрь 2002 75


сети
брасывается и продолжается ожида- Контрольная сумма Makefile
ние:
Расчет контрольной суммы выпол- Для сборки выполняемого модуля
if (( ih -> version ) != 4 ) continue; няет следующая функция: создадим Makefile следующего содер-
жания:
Также мы не будем обрабатывать #include < linux/types.h >
__u16 in_cksum ( __u16 *ptr, int CC = gcc
IP-пакеты, отправителем которых не nbytes ) name = scan
является сканируемый хост: { SCAN = scan.o checksum.o
register __u32 sum; getsock_send.o getsock_recv.o
__u16 oddbyte; $( name ) : $( SCAN )
if (( ih -> saddr != register __u16 answer; $( CC ) -g -o $( name ) $(
dest.sin_addr.s_addr ) continue; NAME )
sum = 0; scan.o : scan.c
и если транспортный протокол не есть while ( nbytes > 1 ) { $( CC ) -c scan.c
sum += *ptr ++; checksum.o : checksum.c
TCP: nbytes -= 2; $( CC ) -c checksum.c
} getsock_send.o : getsock_send.c
$( CC ) -c getsock_send.c
if (( ih -> protocol != 6 ) continue; if ( nbytes == 1 ) { getsock_recv.o : getsock_recv.c
oddbytes = 0; $( CC ) -c getsock_recv.c
* (( unsigned char clean:
Если принятый пакет соответству- *) &oddbyte ) = * (unsigned char *) ptr; rm -f *.o
ет всем условиям, то нам останется sum += oddbyte;
}
только отобразить результаты: Для получения исполняемого мо-
sum = ( sum >> 16 ) + ( sum дуля достаточно ввести команду
printf ( «Ïðèíÿòî %d áàéò \n \n «, rec ); & 0xFFFF);
printf ( «%s \t -> \t « , inet_ntoa ( ih sum += (sum >> 16 ); make. После этого в каталоге, где раз-
->saddr )); answer=~sum; мещены файлы программы, появить-
printf ( «%s \t \n \n «, inet_ntoa ( ih - return (answer);
> daddr )); } ся файл scan. При запуске в команд-
printf ( «Âåðñèÿ \t \t \t = %d \n», ih - ной строке модуля необходимо ука-
> version );
printf ( «Äëèíà çàãîëîâêà \t \t = %d \n», Код для расчета контрольной сум- зать IP-адрес сканируемого хоста и
ih -> ihl ); мы взят из исходных текстов сканера номер проверяемого порта.
printf ( «Äëèíà ïàêåòà \t \t= %d \n»,
ntohs (ih -> tot_len )); Nmap, поэтому приводится без ком- Вот в принципе и все.
printf ( «Èäåíòèôèêàòîð \t \t = %d \n», ментариев. Порядок расчета конт- Обо всех замечаниях и пожелани-
ih -> id );
printf ( «Âðåìÿ æèçíè \t \t = %d \n», ih рольной суммы изложен в RFC 1071. ях пишите на ubob@mail.ru.
-> ttl );
printf ( «Ïðîòîêîë \t \t = %d \n», ih ->
protocol );
printf ( «Êîíòðîëüíàÿ ñóììà IP \t= %d
\n», ih -> check );
printf ( «Ïîðò èñòî÷íèê \t \t = %d \n»,
ntohs ( th -> source ));
printf ( «Ïîðò íàçíà÷åíèÿ \t \t = %d \n»,
ntohs ( th -> dest ));
printf ( «Êîíòðîëüíàÿ ñóììà TCP \t = %d
\n», th->check);
printf ( «SEQ \t \t \t = %lu \n», ntohl (
th -> seq ));
printf ( «ACK-SEQ \t \t \t = %lu \n»,
ntohl ( th -> ack_seq ));
if ( th -> syn == 1 ) printf ( «Ôëàã SYN
óñòàíîâëåí \n» );
if ( th -> ack == 1 ) printf ( «Ôëàã ACK
óñòàíîâëåí \n» );
if ( th -> fin == 1 ) printf ( «Ôëàã FIN
óñòàíîâëåí \n» );
if ( th -> rst == 1 ) printf ( «Ôëàã RST
óñòàíîâëåí \n» );
if ( th -> psh == 1 ) printf ( «Ôëàã PUSH
óñòàíîâëåí \n» );
if ( th -> urg == 1 ) printf ( «Ôëàã URG
óñòàíîâëåí \n» );
if (( th -> syn == 1 )&&(th->ack==1))
printf («Ïîðò %d îòêðûò \n», ntohs (th -
> source ));

Здесь все предельно ясно. После


этого мы прерываем цикл приема па-
кетов и выходим из программы.
break;
}
return (1); }

Сброс соединения возложим на


ядро.

76
FAQ PERL

Можно ли Как можно Как удалить дерево


скомпилировать из Perl стандартизировать каталогов?
исполняемый файл? (оформить в виде
процедуры) получение В «Perl Cookbook» by Tom
Вы можете воспользоваться про- выборки из БД, чтобы Christiansen and Nathan Torkington,
граммой Perl2Exe. Это утилита для получать набор записей O’Reilly («Библиотека программис-
преобразования Perl сценариев в с именованными та: Perl», издательство «Питер»),
выполняемые файлы, не требую- полями? приводится два примера рекурсив-
щие присутствия интерпретатора ного удаления каталога вместе с
языка Perl. Это можно сделать так: его содержимым. В одном исполь-
Perl2Exe может сгенерировать зуется функция finddepth из моду-
модули для Win32 и многих клонов sub QueryArrayOfHashes ля File::Find, во втором - функция
Unix. my ($DB, $query) = @_; rmtree из File::Path.
Perl2Exe также позволяет Вам my ($result,$data_hash,@items,$key, Вот еще один способ:
$val,%hash);
создавать не консольные програм-
мы, с использованием Tk. $result = $DB->prepare($query); # в качестве параметров скрипт
$result->execute or return;
ht tp://www.indigostar.com/ while ($data_hash=$result- принимает
perl2exe.htm — разработчик — >fetchrow_hashref) # список директорий для удале-
IndigoSTAR Software. %hash=%$data_hash; ния
Еще один продукт IndigoSTAR push @items,{%hash};
} die «usage: $0 <dir1> [<dir2> ...
Software — SendMail for $result->finish;
@items; <dirN>]\n» unless @ARGV;
Windows(TM) — Windows версия по-
} foreach $path (@ARGV) {
пулярной программы Unix Sendmail.
del_folder($path);
Она позволяет отправлять сообще- Комментарии: }
ний из командной строки, CGI сце-
$data_hash - ññûëêà íà õýø sub del_folder {
нария или BAT-файла. my $dir=shift;
%$data_hash == %{$data_hash} - ïîëó- return 0 unless $dir;
÷åíèå ñàìîãî õýøà èç ññûëêè my (@dirs,@files,$filename,
{%hash} = ðàçèìåíîâàííûé õýø - ÷òîá
ïîëó÷èëñÿ ìàññèâ õýøåé, à íå ïðîñòî îäèí $newdir,$list);
ìàññèâ opendir(DIR,$dir) or (warn «Can’t
@items â äàííîì ñëó÷àå == return @items
rmdir $dir: $!» and return 0);
Proc::Background — @dirs=grep {!(/^\./) && -d «$dir/
Ïðèìåð èñïîëüçîâàíèÿ: $_»} readdir(DIR);
Общий ли для Unix и use DBI;
... rewinddir(DIR);
Win32 интерфейс @files=grep {!(/^\.(\.)?$/) && -f
$dbh=DBI->connect(“DBI:mysql:mysql: «$dir/$_»}
управление фоновыми localhost”, $user, $password,
{RaiseError => 1}) readdir(DIR);
процессами? closedir (DIR);
or die «connecting : $DBI::errstr\n»;
@res = QueryArrayOfHashes($dbh, «select for $list(0..$#dirs) {
Это общий интерфейс для уп- user, password from user»);
for ($i=0; $i<=$#res; $i++) { $newdir=$dir.»/».$dirs[$list];
равления фоновыми процессами del_folder($newdir);
print «\n[Record #$i]::\n»; }
как на Unix, так и на Win32 плат- foreach $key (sort keys
%{$res[$i]}) { for $list(0..$#files) {
формах. Модуль позволяет Вам за- $filename=$dir.»/».$files[$list];
# çàïèñü âèäà $a[1]{b} ýêâèâàëåí- unlink $filename or (warn «Can’t
пускать и завершать фоновые про- òíà $a[1]->{b}
print $key, «\t», $res[$i]{$key}, unlink $filename: $!» and next);
цессы, получать выходные данные }
«\n»; rmdir $dir or (warn «Can’t rmdir $dir:
и отслеживать состояние фоновых }
} $!» and return 0);
процессов. return 1;
P.S. Рекомендую при использо- }
$dbh->disconnect;
вании под Win32 брать архив со
CPAN и посмотреть прилагаемые
примеры и скрипты.
Proc::Background — http://
search.cpan.org/search?dist=Proc-
Background

по материалам www.xpoint.ru
составил Дмитрий Горяинов

№1, октябрь 2002 77


ПРОГРАММИРОВАНИЕ
СОКЕТОВ ВСЕВОЛОД СТАХОВ
сети
Таким образом, сетевые сокеты представляют собой
парные структуры, жёстко между собой синхронизиро-
ванные. Для создания сокетов в любой операционной
системе, поддерживающей их, используется функция
socket (к счастью, сокеты достаточно стандартизиро-
ваны, поэтому их можно использовать для передачи
данных между приложениями, работающими на разных
платформах). Формат функции таков:

int socket(int domain, int type, int protocol);

Параметр domain задаёт тип транспортного прото-


кола, т.е. протокола доставки пакетов в сети. В настоя-
щее время поддерживаются следующие протоколы (но
учтите, что для разных типов протокола тип адресной
структуры будет разный):

PF_UNIX èëè PF_LOCAL Ëîêàëüíàÿ êîììóíèêàöèÿ äëÿ ÎÑ


Unix(è ïîäîáíûõ)
PF_INET IPv4, ip ïðîòîêîë Internet, íàè-
áîëåå ðàñïðîñòðàí¸í ñåé÷àñ(32-õ
áèòíûé àäðåñ)
Подавляющее большинство сетевых PF_INET6 IPv6, ñëåäóþùåå ïîêîëåíèå ïðîòî-
êîëà ip(IPng) - 128 áèòíûé àäðåñ
серверных программ организовано с PF_IPX IPX - ïðîòîêîëû Novell
использованием сокетов. По сути дела,
сокеты аналогичны файловым дескрипторам Поддерживаются и другие протоколы, но эти 4 явля-
с одним очень важным отличием - сокеты ются самыми популярными.
служат для общения между приложениями Параметр type означает тип сокета, т.е. то, как бу-
либо в сети, либо на локальной машине. дут передаваться данные: обычно применяется констан-
Таким образом, для программиста нет та SOCK_STREAM, её использование означает безопас-
проблемы с доставкой данных, за него это ную передачу данных двунаправленным потоком с кон-
делают сокеты. Необходимо только тролем ошибок. При таком способе передачи данных
позаботиться о том, чтобы параметры программисту не приходится заботиться об обработке
сокетов у двух приложений совпадали. ошибок сети, хотя это не уберегает от логических оши-
бок, что актуально для сетевого сервера.
Параметр protocol определяет конкретный тип про-
токола для данного domain, например IPPROTO_TCP
или IPPROTO_UDP (параметр type должен в данном
случае быть SOCK_DGRAM).
Функция socket просто создаёт конечную точку и воз-
вращает дескриптор сокета; до того, как сокет не со-
единён с удалённым адресом функцией connect, дан-
ные через него пересылать нельзя! Если пакеты теря-
ются в сети, т.е. произошло нарушение связи, то при-
ложению, создавшему сокет, посылается сигнал Broken
Pipe — SIGPIPE, поэтому целесообразно присвоить об-
работчик данному сигналу функцией signal. После того,
как сокет соединён с другим функцией connect, по нему
можно пересылать данные либо стандартными функ-
циями read — write, либо специализированными recv —
send. После окончания работы сокет надо закрыть фун-
кцией close. Для создания клиентского приложения до-
статочно связать локальный сокет с удалённым (сер-
верным) функцией connect. Формат этой функции та-
кой:

int connect(int sock_fd, const struct *sockaddr serv_addr,


socketlen_t addr_len);

При ошибке функция возвращает -1, статус ошибки

79
сети
можно получить средствами операционной системы. (172.16.163.89) либо в символьном формате
При успешной работе возвращается 0. Сокет, однажды (myhost.com). Для преобразования первого служит фун-
связанный, чаще всего не может быть связан снова, так, кция inet_addr (const char *ip_addr), а для второго — фун-
например, происходит в протоколе ip. Параметр sock_fd кция gethostbyname (const char *host). Рассмотрим обе
задаёт дескриптор сокета, структура serv_addr назна- из них:
чает удалённый адрес конечной точки, addr_len содер-
жит длину serv_addr (тип socketlen_t имеет историчес- u_int32_t inet_addr(const char *ip_addr)
кое происхождение, обычно он совпадает с типом int).
Самый важный параметр в этой функции — адрес уда- — возвращает сразу же целое, пригодное для исполь-
лённого сокета. Он, естественно, неодинаков для раз- зования в структуре sockaddr_in по ip адресу, передан-
ных протоколов, поэтому я опишу здесь структуру ад- ному ей в формате x.x.x.x. При возникновении ошибки
реса только для ip(v4) протокола. Для этого использу- возвращается значение INADDR_NONE.
ется специализированная структура sockaddr_in (её не-
обходимо прямо приводить к типу sockaddr при вызове struct HOSTENT* gethostbyname(const char *host_name)
connect). Поля данной структуры выглядят следующим
образом: — возвращает структуру информации о хосте, исходя
из его имени. В случае неудачи возвращает NULL. По-
struct sockaddr_in{ иск имени происходит вначале в файле hosts, а затем в
sa_family_t sin_family; DNS. Структура HOSTENT предоставляет информацию
— îïðåäåëÿåò ñåìåéñòâî àäðåñîâ, âñåãäà äîëæíî áûòü AF_INET
u_int16_t sin_port; о требуемом хосте. Из всех её полей наиболее значи-
— ïîðò ñîêåòà â ñåòåâîì ïîðÿäêå áàéò тельным является поле char **h_addr_list, представля-
struct in_addr sin_addr;
— ñòðóêòóðà, ñîäåðæàùàÿ ip àäðåñ ющее список ip адресов данного хоста. Обычно исполь-
}; зуется h_addr_list[0], представляющая первый ip адрес
Ñòðóêòóðà, îïèñûâàþùàÿ ip-àäðåñ:
struct in_addr{ хоста, для этого можно также использовать выражение
u_int32_t s_addr; h_addr. После выполнения функции gethostbyname в
— ip àäðåñ “ñîêåòà â ñåòåâîì ïîðÿäêå áàéò
}; списке h_addr_list структуры HOSTENT оказываются
простые символические ip адреса, поэтому необходи-
Обратите внимание на особый порядок байт во всех мо воспользоваться дополнительно функцией inet_addr
целых полях. Для перевода номера порта в сетевой по- для преобразования в формат sockaddr_in.
рядок байт можно воспользоваться макросом htons Итак, мы связали клиентский сокет с серверным фун-
(unsigned short port). Очень важно использовать имен- кцией connect. Далее можно использовать функции пе-
но этот тип целого — беззнаковое короткое целое. редачи данных. Для этого можно использовать либо
Адреса IPv4 делятся на одиночные, широковеща- стандартные функции низкоуровневого ввода/вывода
тельные (broadcast) и групповые (multicast). Каждый для файлов, так как сокет — это, по сути дела файло-
одиночный адрес указывает на один интерфейс хоста, вый дескриптор. К сожалению, для разных операцион-
широковещательные адреса указывают на все хосты в ных систем функции низкоуровневой работы с файла-
сети, а групповые адреса соответствуют всем хостам ми могут различаться, поэтому надо посмотреть руко-
в группе (multicast group). В структуре in_addr можно водство к своей операционной системе. Учтите, что пе-
назначать любой из этих адресов. Но для сокетных кли- редача данных по сети может закончиться сигналом
ентов в подавляющем большинстве случаев присваи- SIGPIPE, и функции чтения/записи вернут ошибку. Все-
вают одиночный адрес. Исключением является тот слу- гда нужно помнить о проверке ошибок, кроме того,
чай, когда необходимо просканировать всю локальную нельзя забывать о том, что передача данных по сети
сеть в поисках сервера, тогда можно использовать в может быть очень медленной, а функции ввода/вывода
качестве адреса широковещательный. Затем, скорее являются синхронными, и это может вызвать существен-
всего, сервер должен сообщить свой реальный ip ад- ные задержки в работе программы.
рес и сокет для дальнейшей передачи данных должен Для передачи данных между сокетами существуют
присоединяться именно к нему. Передача данных че- специальные функции, единые для всех ОС — это фун-
рез широковещательные адреса не есть хорошая идея, кции семейства recv и send. Формат их очень похож:
так как неизвестно, какой именно сервер обрабатыва-
ет запрос. Поэтому в настоящее время сокеты, ориен- int send(int sockfd, void *data, size_t len, int flags);
тированные на соединение, могут использовать лишь — îòïðàâëÿåò áóôåð data
одиночные адреса. Для сокетных серверов, ориентиро- int recv(int sockfd, void *data, size_t len, int flags);
ванных на прослушивание адреса, наблюдается другая — ïðèíèìàåò áóôåð data
ситуация: здесь разрешено использовать широковеща-
тельные адреса, чтобы сразу же ответить клиенту на Первый аргумент — дескриптор сокета, второй —
запрос о местоположении сервера. Но обо всём по по- указатель на данные для передачи, третий — длина
рядку. Как вы заметили, в структуре sockaddr_in поле буфера и четвёртый — флаги. В случае успеха возвра-
ip адреса представлено как беззнаковое длинное це- щается число переданных байт, в случае неудачи — от-
лое, а мы привыкли к адресам либо в формате x.x.x.x рицательный код ошибки. Флаги позволяют изменить

80
сети
параметры передачи (например, включить асинхронный сокет, затем ему присваивается локальный адрес фун-
режим работы), но для большинства задач достаточно кцией bind, при этом можно присвоить сокету широко-
оставить поле флагов нулевым для обычного режима вещательный адрес. Затем начинается прослушивание
передачи. При отсылке или приёме данных функции адреса функцией listen, запросы на соединение поме-
блокируют выполнение программы до того, как будет щаются в очередь. То есть функция listen выполняет
отослан весь буфер. А при использовании протокола инициализацию сокета для приёма сообщений. После
tcp/ip от удалённого сокета должен прийти ответ об ус- этого нужно применить функцию accept, которая воз-
пешной отправке или приёме данных, иначе пакет пе- вращает новый, уже связанный с клиентом сокет. Обыч-
ресылается ещё раз. При пересылке данных учитывай- но для серверов характерно принимать много соедине-
те MTU сети (максимальный размер передаваемого за ний через небольшие промежутки времени. Поэтому
один раз кадра). Для разных сетей он может быть раз- нужно постоянно проверять очередь входящих соеди-
ным, например, для сети Ethernet он равен 1500. нений функцией accept. Для организации такого пове-
Итак, для полноты изложения приведу самый про- дения чаще всего прибегают к возможностям операци-
стенький пример программы на Си, реализующей со- онной системы. Для ОС Windows чаще используется
кетного клиента: многопоточный вариант работы сервера (multi-threaded),
после принятия соединения происходит создание ново-
#include <sys/socket.h> го потока в программе, который и обрабатывает сокет.
/* Ñòàíäàðòíûå áèáëèîòåêè ñîêåòîâ äëÿ Linux */ В *nix системах чаще используется порождение дочер-
#include <net/netinet.h>
/* Äëÿ ÎÑ Windows èñïîëüçóéòå #include<winsock.h> */ него процесса функцией fork. При этом накладные рас-
#include <stdio.h> ходы уменьшены за счёт того, что фактически проис-
int main(){ ходит копия процесса в файловой системе proc. При
int sockfd = -1; этом все переменные дочернего процесса совпадают с
/* Äåñêðèïòîð ñîêåòà */
char buf[128]; родителем. И дочерний процесс может сразу же обра-
/* Óêàçàòåëü íà áóôåð äëÿ ïðè¸ìà */ батывать входящее соединение. Родительский же про-
char s[] = "Client ready\n";
/* Ñòðîêà äëÿ ïåðåäà÷è ñåðâåðó */ цесс продолжает прослушивание. Учтите, что порты с
HOSTENT *h = NULL; номерами от 1 до 1024 являются привилегированными
/* Ñòðóêòóðà äëÿ ïîëó÷åíèÿ ip àäðåñà */
sockaddr_in addr; и их прослушивание не всегда возможно. Ещё один мо-
/* Còðóêòóðà tcp/ip ïðîòîêîëà */ мент: нельзя, чтобы два разных сокета прослушивали
unsigned short port = 80;
/* Çàïîëíÿåì ïîëÿ ñòðóêòóðû: */ один и тот же порт по одному и тому же адресу! Для
addr.sin_family = AF_INET; начала рассмотрим форматы вышеописанных функций
addr.sin_port = htons(port);
для создания серверного сокета:
sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
/* Ñîçäà¸ì ñîêåò */ int bind(int sockfd, const struct *sockaddr, socklen_t
if(sockfd == -1) addr_len);
/* Ñîçäàí ëè ñîêåò */
return -1;
h = gethostbyname("www.myhost.com");
— присваивает сокету локальный адрес для обеспече-
/* Ïîëó÷àåì àäðåñ õîñòà */ ния возможности принимать входящие соединения. Для
if(h == NULL) адреса можно использовать константу INADDR_ANY, ко-
/* À åñòü ëè òàêîé àäðåñ? */
return -1; торая позволяет принимать входящие соединения со
addr.sin_addr.s_addr = inet_addr(h->h_addr_list[0]); всех адресов в данной подсети. Формат функции ана-
/* Ïåðåâîäèì ip àäðåñ â ÷èñëî */
логичен connect. В случае ошибки возвращает отрица-
if(connect(sockfd, (sockaddr*) &addr, sizeof(addr))) тельное значение.
/* Ïûòàåìñÿ ñîåäèíèòñÿ ñ óäàë¸ííûì ñîêåòîì */
return -1;
/* Ñîåäèíåíèå ïðîøëî óñïåøíî - ïðîäîëæàåì */ int listen(int sockfd, int backlog);
if(send(sockfd, s, sizeof(s), 0) < 0)
/* Ïîñûëàåì óäàë¸ííîìó ñîêåòó ñòðîêó s */ — функция создаёт очередь входящих сокетов (коли-
return -1;
чество подключений определяется параметром backlog,
if(recv(sockfd, buf, sizeof(buf), 0) < 0) оно не должно превышать числа SOMAXCONN, кото-
/* Ïîëó÷àåì îòâåò îò óäàë¸ííîãî ñåðâåðà */
return -1; рое зависит от ОС). После создания очереди можно
printf("Recieved string was: %s", buf);
ожидать соединения функцией accept. Сокеты обычно
/* Âûâîä áóôåðà íà ñòàíäàðòíûé âûâîä */ являются блокирующими, поэтому выполнение програм-
close(sockfd); мы приостанавливается, пока соединение не будет при-
/* Çàêðûâàåì ñîêåò */
нято. В случае ошибки возвращается -1.
/* Äëÿ Windows ïðèìåíÿåòñÿ ôóíêöèÿ closesocket(s) */
return 0;
} int accept(int sockfd, struct *sockaddr, socklen_t addr_len)

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

№1, октябрь 2002 81


сети
этом исходный сокет sockfd остается в неизменном со- передать в качестве аргумента указатель на сокет
стоянии. Структура sockaddr заполняется значениями (обычно в функцию потока можно передавать данные
из удалённого сокета. В случае ошибки возвращается любого типа в формате void *, что требует использова-
-1. ния приведения типов).
Итак, приведу пример простого сокетного сервера, Важное замечание для систем Windows. Мною было
использующего функцию fork для создания дочернего замечено, что система сокетов не работает без приме-
процесса, обрабатывающего соединение: нения функции WSAStartup для инициализации библио-
теки сокетов. Программа с сокетами в ОС Windows дол-
int main(){ жна начинаться так:
pid_t pid;
/* Èäåíòèôèêàòîð äî÷åðíåãî ïðîöåññà */ WSADATA wsaData;
int sockfd = -1; WSAStartup(0x0101, &wsaData);
/* Äåñêðèïòîð ñîêåòà äëÿ ïðîñëóøèâàíèÿ */
int s = -1;
/* Äåñêðèïòîð ñîêåòà äëÿ ïðè¸ìà */
char buf[128]; И при выходе из программы пропишите следующее:
/* Óêàçàòåëü íà áóôåð äëÿ ïðè¸ìà */
char str[] = «Server ready\n»;
/* Ñòðîêà äëÿ ïåðåäà÷è ñåðâåðó */ WSACleanup();
HOSTENT *h = NULL;
/* Ñòðóêòóðà äëÿ ïîëó÷åíèÿ ip àäðåñà */
sockaddr_in addr; Так как в основном операции с сокетами являются
/* Còðóêòóðà tcp/ip ïðîòîêîëà */ блокирующими, приходится часто прерывать исполне-
sockaddr_in raddr;
unsigned short port = 80; ние задачи ожиданием синхронизации. Поэтому часто
/* Çàïîëíÿåì ïîëÿ ñòðóêòóðû: */ в *nix подобных системах избегают блокировки консо-
addr.sin_family = AF_INET;
addr.sin_port = htons(port); ли созданием особого типа программы — демона. Де-
мон не принадлежит виртуальным консолям и возника-
sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); ет, когда дочерний процесс вызывает fork, а родитель-
/* Ñîçäà¸ì ñîêåò */ ский процесс завершается раньше, чем 2-й дочерний
if(sockfd == -1)
/* Ñîçäàí ëè ñîêåò */ (а это всегда бывает именно таким образом). После
return -1; этого 2-й дочерний процесс становится основным и не
addr.sin_addr.s_addr = INADDR_ANY; блокирует консоль. Приведу пример такого поведения
/* Ñëóøàåì íà âñåõ àäðåñàõ */ программы:
if(bind(sockfd, (sockaddr*) &addr, sizeof(addr)))
/* Ïðèñâàèâàåì ñîêåòó ëîêàëüíûé àäðåñ */ pid = fork();
return -1; /* Ñîçäàíèå ïåðâîãî äî÷åðíåãî ïðîöåññà */
if(listen(sockfd, 1)) if (pid <0){
/* Íà÷èíàåì ïðîñëóøèâàíèå */ /* Îøèáêà âûçîâà fork */
return -1; printf(«Forking Error : )\n»);
exit(-1);
s = accept(sockfd, (sockaddr *) &raddr, sizeof(raddr)); }else if (pid !=0 ){
/* Ïðèíèìàåì ñîåäèíåíèå */ /* Ýòî ïåðâûé ðîäèòåëü! */
printf(«\nThis is a Father 1\n»);
pid = fork(); }else{
/* ïîðîæäàåì äî÷åðíèé ïðîöåññ */ pid = fork();
if(pid == 0){ /* Ðàáîòà 1-ãî ðîäèòåëÿ çàâåðøàåòñÿ */
/* Ýòî äî÷åðíèé ïðîöåññ */ /* È ìû âûçûâàåì åù¸ îäèí äî÷åðíèé ïðîöåññ */
if (pid <0){
if(recv(s, buf, sizeof(buf), 0) < 0)
/* Ïîñûëàåì óäàë¸ííîìó ñîêåòó ñòðîêó s */ printf(«Forking error : )\n»);
return -1 exit(-1);
if(send(s, str, sizeof(str), 0) < 0) }else if (pid !=0 ){
/* Ïîëó÷àåì îòâåò îò óäàë¸ííîãî ñåðâåðà */ /* Ýòî âòîðîé ðîäèòåëü */
return -1; printf(«\nThis is a father 2\n»);
}else{
printf(«Recieved string was: %s», buf);
/* Âûâîä áóôåðà íà ñòàíäàðòíûé âûâîä */ /* À âîò ýòî òîò ñàìûé 2-é äî÷åðíèé ïðîöåññ */
close(s);
/* Çàêðûâàåì ñîêåò */ /* Ïåðåõîä â «ñòàíäàðòíûé» ðåæèì äåìîíà */
return 0; setsid();
/* Âûõîäèì èç äî÷åðíåãî ïðîöåññà */ /* Äàííûé ïðîöåññ ñòàíîâèòñÿ ãëàâíûì â ãðóïïå */
} umask(0);
/* Ñòàíäàðòíàÿ ìàñêà ôàéëîâ */
close(sockfd); chdir(«/»);
/* Çàêðûâàåì ñîêåò äëÿ ïðîñëóøèâàíèÿ */ /* Ïåðåõîä â êîðíåâîé êàòàëîã */
return 0; daemoncode();
} /* Ñîáñòâåííî ñàì êîä äåìîíà */
/* Ïðè âûçîâå fork äåìîíà ïîÿâëÿåòñÿ ïîòîìîê-äåìîí */
}
При создании потока (thread) для обработки сокета
}
смотрите руководство к ОС, так как для разных систем
вызов функции создания потока может существенно
различаться. Но принципы обработки для потока оста- Вот и всё. Я думаю, для создания простенького со-
ются теми же. Функции обработки необходимо только кетного сервера этого достаточно.

82
ОБРАЗОВАНИЕ
образование

ВЗАИМНЫЕ
ФУНКЦИОНАЛЬНЫЕ
ЗАВИСИМОСТИ АНДРЕЙ ФИЛИППОВИЧ

84
образование
Обычно к системному администратору Однако ни в одной из них не говорится об ограниченности
обращаются по самым разным вопросам, их использования. Рассмотрим пример использования пра-
при возникновении любой проблемы: будь вила объединения:
это забытый пароль, неработающая Если A→B, A→С то A→BС.
программа, нечитающаяся дискета или Пусть имеется два простейших отношения ЗАРПЛАТА(-
пропавшие неизвестно куда данные. Этот Сотрудник, Зарплата) и СЧЕТА(Сотрудник, N_Кредитки). Во
список можно продолжать бесконечно. второй таблице задаются номера кредитных карточек со-
Мне хотелось бы остановиться на трудников. Предположим, что у Невезинского нет кредитки.
ошибках, которые возникают при работе с Специалист по БД решил оптимизировать структуру и уст-
СУБД. Статья посвящена тем, кому ранить избыточность, пользуясь вышеупомянутым правилом.
приходится заниматься вопросами Если Сотрудник → Зарплата, Сотрудник → N_Кредитки
проектирования или реструктурирования то Сотрудник → {Зарплата, N_Кредитки}.
реляционных баз данных.
·ÔØÖÙÊÓÎÐ ­ÆÖÕÑÆØÆ ·ÔØÖÙÊÓÎÐ 1B°ÖËÊÎØÐÎ
³ËÈËÍÎÓ×ÐÎÏ  µÔÝØÔÈ 
В настоящее время базы данных являются частью практи-
µÔÝØÔÈ  ·ÆÖÐÎ×âåÓ 
чески любой информационной системы. Современные БД ·ÆÖÐÎ×âåÓ  ¸ÖÎÚÔÓÔÈ 
характеризуются большой размерностью и сложной струк- ¸ÖÎÚÔÓÔÈ  
турой, поэтому для их разработки используются специаль- 
·ÔØÖÙÊÓÎÐ ­ÆÖÕÑÆØÆ 1B°ÖËÊÎØÐÎ
ные программные средства. Они позволяют не только упро-
³ËÈËÍÎÓ×ÐÎÏ  1XOO
стить и ускорить процесс проектирования, но и выполнять µÔÝØÔÈ  
некоторые функции по оптимизации БД. Наиболее популяр- ·ÆÖÐÎ×âåÓ  
ным является подход, в котором осуществляется нормали- ¸ÖÎÚÔÓÔÈ  

зация.
Теория нормализации (зависимостей) появилась одно- Сразу после «оптимизации» в результирующем отноше-
временно с теорией реляционной алгебры. Коддом были нии появится кортеж, который имеет пустое (неопределен-
предложены первые нормальные формы. Впоследствии, ное) значение. Реляционное отношение, да и ФЗ (A→BС)
третья нормальная форма была уточнена и названа нормаль- таких кортежей не поддерживают, поэтому такая запись дол-
ной формой Бойса-Кодда (НФБК). Позже Фэйджином (Fagin) жна автоматически удалиться1.
были предложены 4 и 5 нормальная формы, а также альтер- Большинство современных СУБД поддерживают трех-
нативная доменно-ключевая нормальная форма [3]. Суще- значную логику (с использованием Null значений), поэтому
ствуют и другие нормальные формы, но наиболее популяр- запись может не пропасть. Однако наличие неопределен-
ной является 3НФ и НФБК. ных значений приводит к появлению неоднозначности в зап-
В основе теории нормализации лежат различные поня- росах и программах.
тия зависимостей между атрибутами БД. В теории Кодда Таким образом, свободно применять правила Армстрон-
используется понятие функциональной зависимости (ФЗ), га можно только для проектируемой БД, не имеющей дан-
реляционный аналог математической функции. Рассмотре- ных. При реструктуризации необходимо использовать Null-
ние основных вопросов статьи требует четкого определе- значения.
ния понятия ФЗ, поэтому приведем формальное определе- Это замечание существенно для баз данных с динами-
ние ФЗ из 6-ого издания книги Дейта [4]. Отметим сразу, что чески изменяемой структурой, к которым можно отнести
статья ориентирована на читателя, знакомого с основными современные объектно-реляционные разработки. Функцио-
понятиями реляционной алгебры и теории нормализации. нальные зависимости являются одними из простейших се-
Пусть R — это отношение, а X и Y — произвольные под- мантических ограничений, поэтому можно смело утверждать,
множества множества атрибутов отношения R. Тогда Y фун- что все рассматриваемые вопросы будут актуальны и для
кционально зависимо от X (X→Y), тогда и только тогда, ког- объектных СУБД.
да каждое значение множества X отношения R связано в
точности с одним значением множества Y отношения R. Проблемы 3НФ и НФБК
Иначе говоря, если два кортежа отношения совпадают по X Приведение отношений в 3НФ и НФБК направлено на
[t1(X) = t2(X)], то они также совпадают и по Y [t1(Y) = t2(Y)]. избавление от транзитивных зависимостей (A→B, B→C =>
X→Y o [t1(X) = t2(X)] => [t1(Y) = t2(Y)] A→C). 3НФ позволяет удалить транзитивные зависимости
неключевого атрибута (C) от ключевого (A) через другой
Возможность применения правил неключевой (B). В НФБК запрещаются все зависимости сре-
Армстронга ди ключевых атрибутов и любой неключевой атрибут дол-
Проектирование схемы базы данных начинается с опре- жен напрямую (нетранзитивно) зависеть от ключа, т.е. от всех
деления универсального отношения, в которое входят все ключевых атрибутов.
атрибуты. Для предметной области задается множество ог- Рассмотрим пример приведения отношения в третью
раничений с помощью функциональных, многозначных и нормальную форму, предложенный Ульманом[18] и пробле-
других зависимостей. Для вывода новых ФЗ или сокраще- мы, которые при этом возникают.
ния их числа используются правила Армстронга. Эти прави- Пусть задана схема отношений R и множество функцио-
ла общеизвестны и приводятся почти в каждой книге по БД. нальных зависимостей F. Звездочкой (*) помечены функци-

№1, октябрь 2002 85


образование
ональные зависимости, которые отсутствуют в указанном молетов с номером рейса из B. По условиям, заданным на-
примере. бором ФЗ такая ситуация невозможна. И тем не менее, в
R = (A, B, C, D, E, T), где БД, находящейся в НФБК с «сохранением» зависимостей
A – читаемый курс, содержится неутешительный приговор двум самолетам.
B – преподаватель, Рассмотрим другой случай. Пусть D — это сумма в раз-
C – час начала занятий, мере 120000$ и ее нужно перечислить только на счет Ива-
D – аудитория, нова. Как видно из рис.1. эту сумму может также получить и
E – студент, Петров, а также десятки других сотрудников и это не будет
T – оценка по курсу. противоречить структуре БД.
Из примера видно, что приведение отношения в 3НФ или
F = {CD→B, CD→A, AC→D, CE→A, A→B, CB→D, AE→T, даже в НФБК с помощью декомпозиции не решает пробле-
CE→D} мы противоречивости хранимых данных. Конечно, если про-
A→B — каждый курс ведет один преподаватель; извести естественное соединение всех отношений, то мы
CD→A — в аудитории в один и тот же момент времени избавимся от всех противоречий. Обеспечение целостнос-
может читаться только один курс; ти по взаимосвязанности требует использования специаль-
CB→D — преподаватель в один и тот же момент време- ных средств: внешних ключей, представлений, ограничений
ни может находиться только в одной аудитории; на целостность, триггеров и т.д2.
CE→D — студент в один и тот же момент времени может
находиться только в одной аудитории; Взаимные функциональные зависимости
AE→T — по каждому курсу каждый студент имеет толь- Оставшаяся часть статьи посвящена вопросу обнаруже-
ко одну оценку; ния и устранения ошибок, описанных выше. Рассматривае-
AC→D* — каждый курс в один и тот же момент времени мая противоречивость данных связана с тем, что во множе-
может читаться только в одной аудитории; стве ФЗ имеются взаимные функциональные зависимости
CD→B*— в аудитории в один и тот же момент времени (ВФЗ).
может быть один преподаватель; Взаимной функциональной зависимостью атрибутов A и
CE→A* — каждый студент в один и тот же момент вре- B называется пара функциональных зависимостей вида
мени может слушать только один курс. B→A, A→B и обозначается как A↔B.

Для приведения схемы отношения в 3НФ необходимо Рассмотрим основное свойство ВФЗ. Из функциональ-
найти минимальное покрытие множества функциональных ной зависимости A→B вытекает утверждение 1. Кроме того,
зависимостей. В описываемом примере оно будет представ- может существовать множество кортежей с разными значе-
лено следующим образом[18]: ниями в атрибуте А и одинаковыми значениями в атрибуте
F = {A→B, CD→A, CB→D, AE→T, CE→D} B (утверждение 2):
Далее, осуществим декомпозицию схемы отношения: (1) A→B o [ti(A) = tj(A)] => [ti(B) = tj(B)]
r={AB, CDA, CBD, AET, CED} (2) [∀ ti(B)] ∃ {t(A)} , |{t(A)}|?1

Данная схема находится в НФБК и декомпозируется из Аналогично определим соотношения для ФЗ B→A:
исходной схемы с сохранением зависимостей. Недостатком (3) B→A o [ti(B) = tj(B)] => [ti(A) = tj(A)]
этой декомпозиции является зависимость проекций (по тер- (4) [∀ti(A)] ∃ {t(B)} , |{t(B)}|?1
минологии Риссанена). Отсюда следует, что схема может
обладать аномалиями. Проиллюстрируем это на примере: Соединяя условия (1), (4) и (2), (3) получаем следующие ут-
На рис.1. представлены три таблицы из полученной схе- верждения:
мы отношений. Звездочками отмечены ключевые поля. (5) [∀ ti(A)] ∃ {t(B)} , |{t(B)}|=1
Пусть в БД хранится информация, что в 10 часов в 12 и 13 (6) [∀ ti(B)] ∃ {t(A)} , |{t(A)}|=1
аудиториях должны читаться кур-
·  '  $
сы «Базы данных» и «Операци- Основным свойством ВФЗ является взаимная однознач-
онные системы» (CDA). Извест- ность значений для атрибутов левой и правой части, т.е. каж-
  §ÆÍáªÆÓÓáÛ
но, что эти курсы читают соответ- дый кортеж должен иметь уникальные значения полей, вхо-
  ´ÕËÖ×Î×Ø ственно преподаватели Иванов и дящих во ВФЗ.
 Петров (AB). При этом оба пре- Для задания ВФЗ в СУБД необходимо атрибуты A и B
$  % подавателя должны проводить объединить в одном отношении (таблице) и задать уникаль-
§ÆÍáªÆÓÓáÛ ®ÈÆÓÔÈ
занятия одновременно в одной ность каждого атрибута. Если части ВФЗ более одного ат-
аудитории (CBD). Данная инфор- рибута, то можно использовать первичный и альтернатив-
´ÕËÖ×Î×Ø µËØÖÔÈ
мация является противоречивой, ный ключ (с обязательным заданием уникальности).

·  %  '
несмотря на то, что схема отно- Надо отметить, что алгоритмы нахождения минимально-
шений находится в НФБК и все го покрытия Мейера [11] и Бернштейна [19] учитывают ВФЗ,
 ®ÈÆÓÔÈ 
условия ФЗ соблюдены. но только в рамках сокращения размера покрытия.
 µËØÖÔÈ  Представим теперь, что D — Для дальнейшего рассмотрения материала введем по-
Рис.1. Противоречивость БД. это номер посадочной полосы са- нятие условной ВФЗ (УВФЗ).

86
образование
Условной взаимной функциональной зависимостью ат- При наборе условных атрибутов (C1,.. Cn) конструкция
рибутов A и B называется пара функциональных зависимо- Where изменится:
стей вида СB→A, СA→B, где С — набор атрибутов (усло-
вие) такой, что CCA=?, CCB=?. WHERE (Pse1.C1= Pse2.C1) &(Pse1.C2= Pse2.C2) &…& (Pse1.Cn=
Будем обозначать УВФЗ как С|A↔B. Pse2.Cn)
& (Pse1.A<> Pse2.A) & (Pse1.B<> Pse2.B)

Смысл УВФЗ заключается в том, что при определенных


условиях атрибуты находятся во взаимной функциональной Альтернативой может послужить общее ограничения на
зависимости. Из функциональной зависимости CA→B вы- целостность БД.
текают утверждения 7 и 8:
(7) CA→B o [ti(C) = tj(C)]&[ti(A) = tj(A)] => [ti(B) = tj(B)] CREATE ASSERTION UslFD CHECK (
SELECT Pse1.*
(8) [∀ ti(B)] ∃ {t(СA)} , |{t(СA)}|?1 FROM table1 AS Pse1, table1 AS Pse2
WHERE (Pse1.C= Pse2.C) & (Pse1.A<> Pse2.A) & (Pse1.B<> Pse2.B)
)
Аналогично определим соотношения для ФЗ СB→A:
(9) СB→A o [ti(C) = tj(C)]&[ti(B) = tj(B)] => [ti(A) = tj(A)] Вторым способом задания ограничений является соот-
(10) [∀ ti(A)] ∃ {t(СB)} , |{t(СB)}|?1 ветствующая организация структуры БД (схемы). Реализо-
вать ограничение в одном отношении можно путем задания
Соединяя условия (7-10) и фиксируя значение атрибута С, двух ключей (первичного и альтернативного) со свойством
получаем следующие утверждения: уникальности. Можно также осуществить декомпозицию с
(11) [∀ ti(A)] ∃ {t(B)} , |{t(B)}|=1 использованием внешних ключей.
(12) [∀ ti(B)] ∃ {t(A)} , |{t(A)}|=1
Выявление ВФЗ
Основным свойством EВФЗ является взаимная однознач- Рассмотрим теперь причины возникновения взаимных
ность значений для атрибутов левой и правой части при со- функциональных зависимостей и методы их выявления, а
впадении значений в атрибутах условия, т.е. каждый для также некоторые важные следствия и их применимость на
каждого значения атрибутов условия кортеж должен иметь этапе проектирования структуры базы данных.
уникальные значения полей, входящих во ВФЗ.
Существует несколько способов задать УВФЗ. Можно Следствие 1
ввести понятие условного ключа или условной уникальнос- Во всех функциональных зависимостях, в которых при-
ти. Проиллюстрируем эти понятия. Пусть имеется условная сутствует набор условных атрибутов (С) и условная взаим-
взаимная зависимость (УВФЗ) такая, что условием являет- ная ФЗ (CA→B, CB→A), можно воспользоваться правилом
ся набор атрибутов C, атрибуты B и D входят во ВФЗ. Тогда A~B для нахождения минимального (канонического) покры-
при фиксированном значении атрибутов из С значения ат- тия.
рибутов B и D являются уникальными. Для пояснения при- Пример:
ведем пример. F = {CD→B, AC→D, CE→A, A→B, CD→A, CB→D, AE→T,
В настоящее время возможность введения таких клю- CE→D}
чей в СУБД отсутствует, поэтому данное ограничение мож- CD→B & CB→D => B→D & D→B => C| D↔B
но реализовать либо программным способом, либо деком-
позицией отношений и дополнительных изменений схемы. Выберем все зависимости, в которых присутствует C:
К первому способу можно отнести возможность работы CD→B, AC→D, CE→A, CD→A, CB→D, CE→D
с представлениями (запросами на отображение). Если реа-
лизовать ввод данных только через представление, тогда Заменим B на D и получим:
оно должно содержать выборку по одному из значений ус- CD→D, AC→D, CE→A, CD→A, CD→D, CE→D
ловных атрибутов (например С1), а взаимозависимые атри-
буты должны иметь уникальный ключ. Сократим одинаковые зависимости (CD→D).

Полученная зависимость CD→D является рекурсивной3.


CREATE VIEW UslFD AS В исходных данных такой зависимости нет, поэтому можно
SELECT Pse1.*
FROM table1 AS Pse1, table1 AS Pse2 проверить предметную область на ее наличие. В случае ее
WHERE (Pse1.C= Pse2.C) & (Pse1.A<> Pse2.A) & (Pse1.B<> Pse2.B) обнаружения необходимо разбить ее на нерекурсивные за-
висимости4. В общем (данном) случае рекурсии нет, поэто-
· ½Æ×ÓÆÝÆÑÆÍÆÓåØÎÏ  % µÖËÕÔÊÆÈÆØËÑâ  ' ¦ÙÊÎØÔÖÎå  му эту функциональную зависимость исключаем из списка
· % µÖËÕÔÊÆÈÆØËÑâ  ' ¦ÙÊÎØÔÖÎå  как тривиальную.
·   µËØÖÔÈ  AC→D, CE→A, CD→A, CE→D, C| D↔B
·   ®ÈÆÓÔÈ  Аналогично поступаем и AC→D и CD→A
·   ·ÔÐÔÑÔÈ 
CE→A, C| A↔D↔B
· % µÖËÕÔÊÆÈÆØËÑâ  ' ¦ÙÊÎØÔÖÎå 
Итоговое множество функциональных зависимостей будет
·   µËØÖÔÈ 
выглядеть следующим образом:
·   ·ÔÐÔÑÔÈ 
F = { A→B, AE→T, CE→A, C| A↔D↔B}

№1, октябрь 2002 87


образование
Следствие 2 лучения 3НФ, а некоторые ВФЗ могут быть исходно нео-
Более простые УВФЗ включают в себя более сложные. чевидны и для их выявления требуется ввести специаль-
Если имеется две УВФЗ такие, что множество условных ный алгоритм. Единственное, что можно посоветовать,
атрибутов одной зависимости входит во множество услов- это проверка на ВФЗ каждой ФЗ, которую можно исклю-
ных атрибутов другой зависимости, то такая УВФЗ являет- чить из начального списка ФЗ.
ся более простой: Ниже приводится алгоритм нахождения ВФЗ для ми-
C|A↔B и D|A↔B, и CID, то C|A↔B => D|A↔B нимального покрытия. Данный алгоритм является схе-
матичным и неоптимизированным, т.к. при нахождении
Следствие 3 ВФЗ не осуществляется изменение (уменьшение) мно-
Нахождение минимального покрытия ФЗ не убирает жества исходных ФЗ. Можно также заранее исключить
и не изменяет ВФЗ, но значительно осложняет их поиск. зависимости, которые точно не образуют ВФЗ. Это так
В качестве примера можно сравнить очевидность ВФЗ называемые неприводимые слева ФЗ, т.е. те зависимо-
в исходном и нормализированном наборе ФЗ (см. выше). сти, в левой части которых содержатся атрибуты, не
Одним из способов обнаружения ВФЗ является графи- встречающиеся в правой части других ФЗ.
ческое отображение ФЗ и поиск кольцевых структур5. Алгоритм можно также применять для произвольно-
CA→B, B→A го множества ФЗ, но в случае минимального покрытия
На рис.2а показаны функциональные зависимости в ВФЗ будут находится только один раз. Существуют мно-
графической форме. Можно заметить, что функциональ- жества ФЗ, которые являются взаимно-независимые.
ные зависимости пересекаются, и, кроме того, образу- Правила определения таких множеств можно найти в
ют циклическую структуру. Воспользовавшись правилом [4,20].
дополнения Армстронга, получаем схему, представлен-
ную на рис.2б. Функциональная зависимость B→A пред- Пусть задана схема отношений R и множество ФЗ S
ставлена отдельно и атрибут B в ней не зависит от CA, такие что:
т.к. зависимость была разделена на B→A при наличии C S ={ X[1]→Y[1]… X[i]→Y[i]… X[n]→Y[n]}, где n — коли-
и на B→A при отсутствии C. На рис.2в представлено вы- чество ФЗ.
деление взаимной зависимости A и B при условии С. Y[i]∈ R, X[i]⊆ R , т.е. Y является атрибутом, а X — на-
На рисунке 3 рассматривается пример нахождения бором атрибутов из R.
УВФЗ для минимального покрытия ФЗ из примера, при- X[i]= {X[i][1]… X[i][j]… X[i][m]}, где m — Количество ат-
веденного выше. В результате нахождения минимально- рибутов в левой части ФЗ. Для разных ФЗ m может из-
го покрытия ВФЗ становятся менее очевидными из-за меняться.
увеличения элементов в цикле. Взаимные зависимости
становятся транзитивными. Алгоритм
Можно сделать вывод, что нахождение минимально-
го покрытия ФЗ усложняет процесс нахождения ВФЗ за for i:=1 to n do // öèêë ïî âñåì ÔÇ
{ m:=m(X[i]); // ïîäñ÷åò êîëè÷åñòâà àòðèáóòîâ â ëåâîé
счет уменьшения числа зависимостей. Тем не менее, на- ÷àñòè
хождение минимального покрытия необходимо для по- for j:=1 to m do // öèêë ïî âñåì àòðèáóòàì
{Left_Part:= X[i][j]; // âûáîð îäíîãî èç
àòðèáóòîâ
for k:=1 to n do // öèêë ïî âñåì ÔÇ
// åñëè óñëîâèå âûïîëíÿåòñÿ, òî âîçìîæíî íàëè÷èå êîëüöåâîé
  ñòðóêòóðû
    {if Left_Part == Y[k] then
   
  //ôóíêöèÿ ïðîâåðÿåò ìíîæåñòâî S íà íàëè÷èå ÂÔÇ
  //ìåæäó X[i][j] è Y[i] ïðè óñëîâèè X[i]- X[i][j]
 call function_1(Left_Part, Y[i], X[i]- X[i][j]);
Restore S, n; //âîññòàíîâëåíèå ïîëíîãî ñïèñêà ÔÇ
à á â }
Рисунок 2. }
}
//ôóíêöèÿ ïðîâåðÿåò ìíîæåñòâî S íà íàëè÷èå ÂÔÇ
$ % //ìåæäó Left_Part è Right_Part ïðè óñëîâèè Usl
function_1(Left_Part, Right_Part, Usl)
for i:=1 to n do // öèêë ïî âñåì ÔÇ
{ if (Right_Part I X[i]) then //åñëè ïðàâûé àòðèáóò âõî
& & $ & äèò
// â ëåâóþ ÷àñòü êàêîé-ëèáî ÔÇ
' $ 7 ' {
% ' ( ( Usl:=Usl+ X[i] - Right_Part; // òî óñëîâèå äîïîë
íÿåòñÿ äðóãèìè
//àòðèáóòàìè èç ëå
âîé ÷àñòè
if (Left_Part == Y[i]) then //åñëè ïðîèçîøëî
çàêîëüöåâàíèå
& { Add «Usl | Left_Part ~ Right_Part» //äîáàâëÿåò
' & ' ñÿ íîâàÿ ÓÂÔÇ
delete X[i]®Y[i]; / /
% äàííàÿ ÔÇ óäàëÿåòñÿ èç S
$ % n:=n-1; //êîëè÷åñòâî ÔÇ óìåíüøàåòñÿ
$ }
else //èíà÷å îñóùåñòâëÿåòñÿ ðåêóðñèâíûé âûçîâ
Рисунок 3. äëÿ äàëüíåéøåãî ïîèñêà

88
образование
4
{ Программа ERWIN позволяет использовать на ста-
Right_Part := Y[i]; дии проектирования рекурсивные неидентифицирую-
delete X[i]®Y[i]; //äàííàÿ ÔÇ óäàëÿåòñÿ èç S
n:=n-1; //êîëè÷åñòâî ÔÇ óìåíüøàåòñÿ щие связи.
function_1(Left_Part, Right_Part, Usl);
} 5
Здесь очень хорошо бы подошло слово замыкание
} или рекурсивная связь, но они уже используются и не-
}
сут несколько другую смысловую нагрузку. См. также
циклические БД.
Выводы
1. Нахождение ВФЗ позволяет избавиться от проти-
воречивости хранимой информации в БД. Ëèòåðàòóðà:
2. Одним из способов учета ВФЗ является наложе- 1. Àðñåíüåâ Á.Ï., ßêîâëåâ Ñ.À. Èíòåãðàöèÿ ðàñïðåäåëåííûõ
ние ограничений на целостность БД. Для этого введем ÁÄ, ÑÏá, Ëàíü, 2001, - 464 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 37-41).
2. Âåðáîâåöêèé À.À. Îñíîâû ïðîåêòèðîâàíèÿ áàç äàííûõ., Ðà-
понятие «целостности по взаимозависимости». äèî è ñâÿçü, 2000, - 88 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 25-28).
3. Вторым способом учета ВФЗ является нормали- 3. Ãîëîñîâ À.Î. Àíîìàëèè â ðåëÿöèîííûõ áàçàõ äàííûõ. Æóðíàë
ÑÓÁÄ, âûïóñê 1.06.1996.
зация отношений, т.е. приведение БД к соответствую- 4. Äåéò Ê.Äæ. Ââåäåíèå â ñèñòåìû áàç äàííûõ, 6-å èçäàíèå.
щей структуре (схеме). Введем понятие взаимно-неза- Ê., Ì., ÑÏá.: Èçäàòåëüñêèé äîì «Âèëüÿìñ», 2000. - 848 ñ. Òåîðèÿ
íîðìàëèçàöèè (ñòð. 269-347). Îäíà èç íàèáîëåå ïîëíûõ êíèã íà
висимой нормальной формы, если схема отношений не ðóññêîì ÿçûêå. Çàòðàãèâàþòñÿ âîïðîñû àòîìàðíûõ îòíîøåíèé è
имеет ВФЗ. ВННФ можно рассматривать как синоним ÄÊÍÔ. Â 7-îì èçäàíèè áîëåå ïîäðîáíî ðàññìàòðèâàþòñÿ âîïðîñû 4
è 5ÍÔ.
понятия ациклической БД. 5. Äóíàåâ Ñ. Äîñòóï ê ÁÄ è òåõíèêà ðàáîòû â ñåòè. Ì., Äèà-
4. Приведение отношения к ВННФ можно осуществ- ëîã-ÌÈÔÈ, 2000, - 416 ñ.
6. Êàëèíè÷åíêî Ë.À. Ìåòîäû è ñðåäñòâà èíòåãðàöèè íåîäíîðîä-
лять независимо от приведения отношения в 1НФ, 2НФ, íûõ áàç äàííûõ. Ì.: Íàóêà, 1983 - 424 ñ.
3НФ, НФБК. Рекомендуется выявлять ВФЗ на этапе на- 7. Êàðïîâà Ò. Áàçû äàííûõ, ìîäåëè, ðàçðàáîòêà, ðåàëèçàöèÿ,
ÑÏá., Ïèòåð, 2001, - 304 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 110-120).
хождения минимального покрытия. 1-5ÍÔ, î÷åíü êðàòêî.
5. В последующих нормальных формах используют- 8. Êîííîëëè Ò., Áåãã Ê., Ñòðà÷àí À. Áàçû äàííûõ. Ïðîåêòèðî-
âàíèå, ðåàëèçàöèÿ è ñîïðîâîæäåíèå. Òåîðèÿ è ïðàêòèêà, 2-å èçä.,
ся несколько другие зависимости. В данной работе эти Ì.:»Âèëüÿìñ», 2000, - 1120 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 222-
вопросы не проработаны. 258). 1-5ÍÔ, î÷åíü êðàòêî, â îñíîâíîì, ïðèìåðû.
9. Êîðíååâ Â.Â., Ãàðååâ À.Ô., Âàñþòèí Ñ.Â., Ðàéõ Â.Â. Áàçû
6. Автор не считает, что вопросы, затронутые в этой äàííûõ. Èíòåëëåêòóàëüíàÿ îáðàáîòêà èíôîðìàöèè. Ì.: «Íîëèäæ». -
работе являются научной новизной. Более того, автор 352 ñ.
10. Êóëüáà Â.Â., Êîâàëåâñêèé Ñ.Ñ., Êàÿ÷åíêî Ñ.À., Ñèðîòþê
уверен, что за такой продолжительный срок существо- Â.Î. Òåîðåòè÷åñêèå îñíîâû ïðîåêòèðîâàíèÿ îïòèìàëüíûõ ñòðóêòóð
вания теории баз данных и теории нормализации, про- ðàñïðåäåëåííûõ ÁÄ., Ì., Ñèíòåã, 1999, 660 ñ. Òåîðèÿ íîðìàëèçà-
öèè (ñòð. 116-124). 1-3ÍÔ, Ôîðìàëüíîå îïèñàíèå îñóùåñòâëåíî â
блемы ВФЗ были затронуты, а может, и решены. К со- òåðìèíàõ êíèãè.
жалению, из всех современных книг по БД, только в кни- 11. Ìåéåð Ä. Òåîðèÿ ðåëÿöèîííûõ áàç äàííûõ. Ì.: Ìèð, 1987. -
608 ñ. Òåîðèÿ íîðìàëèçàöèè ðàñêðûâàåòñÿ â ýòîé êíèãå íàèáîëåå
ге Дейта и Мейера отдаленно затрагивается этот воп- ïîëíî. Î÷åíü ìíîãî ðåçóëüòàòîâ, ñâÿçàííûõ ñ ìíîãîçíà÷íûìè è
рос, несмотря на его первостепенную важность. Автор ñîåäèíèòåëüíûìè çàâèñèìîñòÿìè. Ðàññìàòðèâàþòñÿ êîëüöåâûå è
òàáëè÷íûå çàâèñèìîñòè, àëãîðèòìû ñèíòåçà, äåêîìïîçèöèè è ìíî-
осуществлял поиск и просмотрел большое количество ãèå äðóãèå âîïðîñû. Êíèãà äîñòàòî÷íà ñëîæíàÿ, ò.ê. ââîäèòñÿ
литературы. На поиск аналогичных разработок было по- ìíîæåñòâî äîïîëíèòåëüíûõ ïîíÿòèé, è ïåðåîïðåäåëÿþòñÿ íåêîòîðûå
ïðèâû÷íûå îïðåäåëåíèÿ.
трачено время, в 7-8 раз превышающее теоретическую 12. Ìàðòèí Äæ. Îðãàíèçàöèÿ áàç äàííûõ â âû÷èñëèòåëüíûõ ñèñ-
разработку проблемы ВФЗ. «Если теорему проще до- òåìàõ. 2-å èçä., Ì.: Ìèð, 1980.
13. Ðåâóíêîâ Ã.È., ×åòâåðèêîâ Â.Í., Ñàìîõâàëîâ Ý.Í. Áàçû è
казать, чем найти описание доказательства, то почему áàíêè äàííûõ. Ì.: Âûñøàÿ øêîëà, 1987. - 248 ñ.
это не сделать?» 14. Ôðîëîâ. À., Ôðîëîâ. Ã. Áàçû äàííûõ â Èíòåðåíåòå: ïðàêòè-
÷åñêîå ðóêîâîäñòâî ïî ñîçäàíèþ WEB-ïðèëîæåíèé ñ ÁÄ., èçä. 2-
Автор пытается акцентировать внимание на вопро- å., «Ðóññêàÿ Ðåäàêöèÿ», 2000, - 448 ñ.
сах ВФЗ. Если читатель имеет какую-либо информа- 15. Õàíñåí Ã., Õàíñåí Äæ. Áàçû äàííûõ. Ðàçðàáîòêà è óïðàâëå-
íèå, Ì., Áèíîì, 2000, - 704 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 200-
цию по данному вопросу, а также возражения, замеча- 210). 1-4ÍÔ, î÷åíü êðàòêî.
ния или дополнения, присылайте их по адресу 16. Öàëåíêî Ì.Ø. Ìîäåëèðîâàíèå ñåìàíòèêè â ÁÄ. Ì.: Íàóêà.
Ãë. ðåä. ôèç-ìàò.ëèò., 1989. - 288 ñ. - (Ïðîáëåìû èñêóññòâåí-
fil@ics.bmstu.ru. íîãî èíòåëëåêòà).
17. Óëüìàí Äæ., Óèäîì Äæ. Ââåäåíèå â ñèñòåìû ÁÄ. Ì.: Ëîðè,
1
2000, - 420 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 94-138). 1-5ÍÔ, Ïåðå-
Например, если использовать SQL-инструкцию èçäàíèå êíèãè 80 ãîäà ñ íåáîëüøèìè èçìåíåíèÿìè. Ïðèâîäÿòñÿ
Select Сотрудник, Зарплата, N_Кредитки àëãîðèòìû ïðèâåäåíèÿ â ÍÔ.
18. Óëüìàí Äæ. Îñíîâû ñèñòåì áàç äàííûõ. - Ì.: Ôèíàíñû è
From ЗАРПЛАТА AS R1, СЧЕТА AS R2 ñòàòèñòèêà, 1983. - 334 ñ. Òåîðèÿ íîðìàëèçàöèè (ñòð. 152-189).
Where R1. Сотрудник = R2. Сотрудник 1-4ÍÔ, Îäíà èç íåìíîãèõ êíèã, ñîäåðæàùàÿ ìíîæåñòâî àëãîðèòìîâ,
òåîðåì, àêñèîì äëÿ ôóíêöèîíàëüíûõ è ìíîãîçíà÷íûõ çàâèñèìîñòåé.
19. Bernstein P.A. Synthesising Third Normal Form Relations
2
Попытка создать описанную БД с заданием связей from Functional Dependencies // ACM Trans. on Database Systems.
- 1976. V. 1, ¹ 4. - Ð. 277-298.
в среде ERWin приведет к неудаче. 20. Rissanen J. Independent Components of Relations// ACM
Trans. on Database Systems. - 1977. - V. 2, ¹4. - Ð. 317-325.
3
21.Zaniolo C., Melkanoff M.A. A Formal Approach to the
Рекурсивные зависимости невозможны в реляци- Definition and the Design of Conceptual Schemata for Database
онной алгебре, называются тривиальными и исключа- Systems // ACM Trans. on Database Systems. - 1982. - V. 7, ¹.1,
- P. 24-59.
ются из множества ФЗ как избыточные. Следует раз-
личать рекурсивные и взаимные зависимости.

№1, октябрь 2002 89


CommerceML
стандарт
стандарт обмена
обмена
коммерческой
коммерческой информацией
информацией вв формате
формате XML
XML
РТИЩЕВА ЕЛЕНА
ВАЛЕРЬЕВНА
Для обмена информацией в элект- Однако для конкретного бизнес-при- предполагает совместную работу спе-
ронной коммерции необходим общий ложения сам по себе XML еще не от – циалистов фирм «1С», «Port.ru»,
язык, с помощью которого компании вет – он лишь основа, на которой этот «Price.Ru», «Extra.RU» и компании
могли бы обмениваться структуриро- ответ можно построить. Microsoft над совершенствованием
ванными данными между своими раз- В соответствии с планами, объяв- стандарта, а также предоставление
нотипными компьютерами. Язык ленными в июле 2000 года, специалис- всей необходимой информации органи-
Internet первого поколения, HTML, не тами фирм «1С» и «Extra.RU» при под- зациям, которые в дальнейшем захотят
подходит для этой цели - он описывает держке технических специалистов поддерживать предлагаемый стандарт.
форматирование информации, но не ее представительства Microsoft в России Соглашение имеет некоммерческий
смысл. И вот появился XML - Extensible разработаны стандарты обмена ком- характер, ставит целью развитие Ин-
Markup Language (расширяемый язык мерческой информацией в формате тернет-технологий и технологий обме-
разметки). Как и HTML, он содержит XML для торговых организаций. на коммерческой информацией и явля-
текст, размеченный тегами. Но теги в Между компанией Microsoft®, фир- ется открытым для всех заинтересован-
XML описывают уже и смысл и струк- мой «1С» и ведущими отечественными ных организаций, готовых к конструк-
туру информации, позволяя напрямую Интернет-компаниями «Port.ru», тивному сотрудничеству.
обрабатывать ее программными сред- «Price.Ru» и «Extra.RU», а также мос-
ствами. Например, Международный ковским представительством компании Использование
совет по прессе и телекоммуникациям Intel достигнуто соглашение о поддер- CommerceML
недавно утвердил NewsML как основ- жке единого стандарта обмена коммер- Сегодня рынок Интернет-коммер-
ную систему разметки новостной ин- ческой информацией в формате XML. ции в нашей стране находится на эта-
формации, также был создан MathML Соглашение о дальнейшем разви- пе становления. Пока что Интернет-
для математических документов и др. тии и поддержке единого стандарта торговлей занимаются в основном

90
образование
компании, созданные непосредствен- широким кругом производителей лог. Каталог может быть «внутрен-
но для продажи услуг через Сеть, а экономического программного обес- ним», т.е. вложенным в тот же доку-
также фирмы, работающие в сфере печения и Интернет-компаний. Для мент, что и пакет предложений, и со-
информационных технологий. Есте- этого разработчики изначально со- ставленным непосредственно авто-
ственным путем, позволяющим вов- здавали стандарт независимо от осо- ром пакета предложений. Он также
лечь в Интернет–коммерцию широкий бенностей собственного программ- может быть «внешним» – составлен-
круг традиционных (off-line) торговых ного обеспечения или структур ин- ным одной из известных фирм. В этом
фирм, расширить сферу применения формационных баз и исходили из случае в пакете предложений огова-
Интернет–технологий и продемонст- общих принципов организации тор- ривается, на какой каталог (класси-
рировать преимущества этих техноло- говой деятельности. В то же время в фикатор) он ориентирован. Для одно-
гий продавцам и покупателям, явля- стандарте учтены различные особен- значного определения товара в пос-
ется публикация каталогов фирм на ности работы как Интернет-компа- леднем случае достаточно ссылки
специализированных Интернет-сай- ний, так и торгующих организаций. (идентификатора товара во внешнем
тах (Web-витринах). Поэтому количе- Например, решена проблема органи- каталоге), т.е. в тот же документ, что
ство Web-витрин растет, и Интернет- зации обмена информацией при не- и пакет предложений, каталог товаров
компании заинтересованы в привле- зависимой классификации товаров у можно вообще не включать. Таким об-
чении как можно большего числа тор- каждого участника обмена. разом, каталог товаров можно рас-
говых организаций. Однако этот про- Предлагаемый стандарт суще- сматривать как некий классификатор.
цесс сдерживается отсутствием стан- ственно отличается от иностранных Следовательно, в каталоге должен
дартов и готовых программно-аппа- аналогов, так как учитывает отече- быть оговорен список Свойств (по
ратных решений, а также большой ственную специфику и включает не- каким критериям производится клас-
трудоемкостью организации взаимо- сколько универсальных решений, не- сификация). Устойчивые сочетания
действия торговых организаций с обходимых для российских Интер- свойств удобно фиксировать в Набо-
Web-витринами. нет–компаний и торговых организа- ры свойств (например, «свойства ви-
ций. Вместе с тем, новый стандарт деомагнитофона», «свойства телеви-
Общее описание имеет много общего с решениями, зора»). Для указания, какие свойства
стандарта используемыми сейчас в наиболее (или наборы свойств) доступны (мо-
Разработанный стандарт позво- популярных отечественных системах гут быть определены, обязательно
ляет существенно снизить затраты на Интернет-торговли. должны быть указаны) для всего ка-
организацию информационного вза- талога, для его группы или для от-
имодействия за счет унификации об- Описание схемы дельного товара, используются Ссыл-
мена коммерческой информацией CommerceML ки на свойства (Ссылки на наборы
между различными организациями: Предусматривается использова- Свойств). Каталог (классификатор)
как выступающими на рынке Интер- ние данной схемы, в частности, для обычно создается многоуровневым
нет-коммерции, так и работающими обмена: (т.е. имеющим разветвленное дерево
в сфере традиционной (off-line) тор- n каталогами товаров; категорий (Групп), к которым можно
говли. n коммерческими предложениями; отнести товар). Иногда однозначная
Использование торговыми орга- n документами. классификация может вызвать зат-
низациями программного обеспече- руднения, поэтому для удобства раз-
ния, поддерживающего данный стан- Формирование решается включать товары сразу в
дарт, позволит им с минимальными коммерческих несколько категорий. Но при этом
усилиями и без привлечения про- предложений по одна из них должна быть выбрана в
граммистов организовать публика- каталогу качестве «основной». Например, ра-
цию своих предложений на любых Предложение практически совпа- дио-будильник можно отнести как к
поддерживающих этот стандарт Web- дает с одной строкой «обычного» категории «Радиоприемники», так и к
витринах, а также реализовать обмен прайс-листа. Предлагается такой-то категории «Будильники», но в первую
информацией между собой без спе- товар по такой-то цене, имеющийся в очередь, радио-будильник является
циальной доработки программ. На- наличии в таком-то количестве. Напри- радиоприемником. При разработке
пример, при оприходовании товаров мер, гречневая крупа по цене 200 руб- классификаторов принято для каждой
у покупателя информация о хозяй- лей за мешок, на складе имеется 125 позиции указывать Аналоги (напри-
ственной операции может быть авто- мешков. Предложения группируются в мер, для лекарства это – другие ле-
матически загружена из данных, по- Пакет предложений, в котором зада- карства аналогичного действия, для
лученных от продавца. ется общая часть всех предложений запчастей – запчасти, которые мож-
Разработчики стремились обес- (аналог «шапки» прайс-листа). но поставить вместо данной).
печить максимальную открытость Для того чтобы получатели пред- Указание, какими собственно
стандарта с тем, чтобы он в дальней- ложений могли понять, какой товар свойствами из заданных в каталоге
шем мог развиваться на основании предлагается, последний должен быть может обладать товар (или группа),
объективных потребностей рынка и описан. Описание товара и его клас- достигается с помощью Ссылки на
поддерживаться как можно более сификация «складываются» в Ката- свойство (при этом еще можно задать

№1, октябрь 2002 91


образование
обязательность заполнения данного n Заказ товара XML-документе, описывающем «От-
свойства). Аналогичный тип элемен- n Cчет на оплату пуск товара» роль «собственного пред-
та создан и для набора (Ссылка на на- n Отпуск товара приятия» обозначена как «Покупа-
бор свойств). n Счет-фактура тель», то это означает, что XML-доку-
Для хранения значений свойств, в n Возврат товара мент описывает расходную накладную
том числе и дополнительной, не предус- n Передача товара на реализацию поставщика, и ее следует импортиро-
мотренной классификатором информа- n Возврат товара с реализации вать в учетную систему как «наклад-
ции, служит специальный тип элемен- n Отчет о продажах комиссионного ную на поступление товара».
та ЗначениеСвойства. товара Примеры соглашений при исполь-
В итоге, для опубликования своего n Выплата наличных денег зовании данной схемы.
прайс-листа (составления своего паке- n Возврат наличных денег
та предложений) надо сделать следую- n Выплата безналичных денег Обозначения
щее. n Возврат безналичных денег «0-1» - атрибут или элемент не
Причем для предприятий (фирм) – обязателен. Может принимать только
1. Классифицировать отправителя и получателя XML-доку- одно значение;
свой товар мента – указанные хозяйственные опе- «1-1» - атрибут или элемент обя-
Это можно сделать: рации представляются разными доку- зателен. Может принимать только
n путем составления собственного ментами. Например «Отпуск товара» одно значение;
классификатора, для чего нужно: для отправителя сопровождается «0-*» - атрибут или элемент не обя-
1. составить список свойств, по оформлением «расходной накладной» зателен. Может содержать список
которым будет производится классифи- («накладной на отпуск товара»), а для значений;
кация; получателя – оформлением «приход- «1-*» - атрибут или элемент обя-
2. объединить устойчивые сочета- ной накладной». Программа автомати- зателен. Может содержать список
ния свойств в наборы свойств; зации учета может, исходя из вида хо- значений.
3. составить иерархический список зяйственной операции и роли, которая По умолчанию – все атрибуты и
категорий (групп); указана для данного предприятия, «по- элементы являются не обязательны-
4. отнести каждый товар к одной нять», является ли «собственное пред- ми и имеют тип «строка», если специ-
или нескольким категориям; приятие» (от лица которого автомати- ально не оговорено другое.
5. определить для каждого товара зируется учет в программе) получате-
его аналоги. лем данного документа. Роли предус- Коммерческая
n путем нахождения своих товаров во мотрены следующие: Информация
внешнем классификаторе. n Продавец (CommerceInfo)
1. если некоторые товары не най- n Покупатель Описание: Корневой элемент XML-
дены во внешнем классификаторе, то n Плательщик документа, описывающего каталог (ка-
для них (и только для них!) придется n Получатель талоги) товаров, список (списки) пред-
составлять внутренний классификатор. Например, если в обрабатываемом ложений. Содержит один или несколь-
Òàáëèöà ¹1
2. Отправить пакет
предложений ³ÆÎÒËÓÔÈÆÓÎË ¸ÎÕ´ÕÎ×ÆÓÎË °ÔÒÒËÓØÆÖÎÏ
°ÔÒÒËÓØÆÖÎÏ  µÖËÊÓÆÍÓÆÝËÓÊÑåÕËÖËÊÆÝÎ 
1. Если при составлении пакета
 &RPPHQW  ×ÔÕÖÔÈÔÊÎØËÑâÓÔÏÍÆÕÎ×ÐÎÈÈÎÊË
предложений оказалось достаточно ÕÖÔÎÍÈÔÑâÓÔÏØËÐ×ØÔÈÔÏÎÓÚÔÖÒÆÜÎÎÕÔ
внешнего классификатора, то отправ- ÊÔÐÙÒËÓØÙ
ленный файл будет содержать только
пакет предложений. Òàáëèöà ¹2
2. Если для составления пакета ³ÆÎÒËÓÔÈÆÓÎË ¸ÎÕ ´ÕÎ×ÆÓÎË °ÔÒÒËÓØÆÖÎÏ
(всего или его части) понадобился внут- ³ÔÒËÖ·ÝËØÆ  ³ÔÒËÖ×ÝËØÆ 
ренний классификатор, то в отправля-  $FFRXQW1XPEHU 
емый файл придется включить внутрен- §ÆÓÐ %DQN  ,GUHI§ÆÓÐÈÐÔØÔÖÔÒÔØÐÖáØ×ÝËØ 
§ÆÓаÔÖÖË×ÕÔÓÊËÓØ ,GUHI§ÆÓÐÐÔÖÖË×ÕÔÓÊËÓØÈ×ÑÙÝÆËÓË 
ний классификатор.  &RUUHVSRQGHQW%DQN  ÕÖåÒáÛÖÆ×ÝËØÔÈ
°ÔÒÒËÓØÆÖÎÏ &RPPHQW   µÖÔÎÍÈÔÑâÓáÏ×ÔÕÖÔÈÔÊÎØËÑâÓáÏ 
Обмен документами ØËÐ×Ø
В задачу, решаемую с помощью
данной схемы, не входит обмен произ- Òàáëèöà ¹3
вольными документами. Также не вхо- ³ÆÎÒËÓÔÈÆÓÎË ¸ÎÕ´ÕÎ×ÆÓÎË °ÔÒÒËÓØÆÖÎÏ
дят задачи поддержки распределенной ®ÊËÓØÎÚÎÐÆØÔÖ ,G  ,G ¹ÓÎÐÆÑËÓÈÖÆÒÐÆÛÈ×ËÉÔÊÔÐÙÒËÓØÆ 
базы данных. Схема описывает доку- ³ÆÎÒËÓÔÈÆÓÎË  ³ÆÎÒËÓÔÈÆÓÎË ÓÆÕÖÎÒËÖªÔÒÆÞÓÎÏ 
менты, сопровождающие наиболее рас-  1DPH  ØËÑËÚÔÓÊÎÖËÐØÔÖÆ 
°ÔÒÒËÓØÆÖÎÏ  µÖÔÎÍÈÔÑâÓáÏ×ÔÕÖÔÈÔÊÎØËÑâÓáÏØËÐ×Ø 
пространенные торговые (хозяйствен-  &RPPHQW   ÓÆÕÖÎÒËÖÐÆÐÊÔËÛÆØâÓÆÆÈØÔÒÔÇÎÑË 
ные) операции:

92
образование
ко каталогов товаров, включая список °ÔÒÒËÖÝË×ÐÆå®ÓÚÔÖÒÆÜÎå 
возможных свойств товаров в катало- >°ÔÒÒËÓØÆÖÎÏ VWULQJ@ 
ге, один или несколько пакетов пред- !
ложений.  °ÔÓØÖÆÉËÓØ!@ 
Атрибуты: Таблица №1 > ·ÐÑÆÊ!@ 
×ÎÓØÆÐ×Î×
> §ÆÓÐ!@ 
Содержит: Каталог (0-*), ПакетП-  VHT 
> °ÆØÆÑÔÉ!@ 
редложений (0-*), Контрагент (0-*), > µÆÐËصÖËÊÑÔÌËÓÎÏ!@ 
Документ (0-*), Банк (0-*), Склад (0-*). > ªÔÐÙÒËÓØ!@ 
°ÔÒÒËÖÝË×ÐÆå®ÓÚÔÖÒÆÜÎå!
РасчетныйСчет ×Ô×ØÆÈ HOW2QO\
(BankAccount) ÕÔÖåÊÔÐ VHT
Описание: Расчетный счет описы- ÈáÞË×ØÔåßÎË1RSDUHQWVIRXQG7KLVLVSUREDEO\WKHGRFXPHQWHOHPHQW
ÊÔÝËÖÓÎË §ÆÓЪÔÐÙÒËÓØ°ÆØÆÑÔÉ°ÔÓØÖÆÉËÓصÆÐËصÖËÊÑÔÌËÓÎÏ·ÐÑÆÊ
вает банковский счет контрагента в
ÆØÖÎÇÙØá °ÔÒÒËÓØÆÖÎÏ
объеме, необходимом для оформле- ÒÔÊËÑâ RSHQ ÕÔÙÒÔÑÝÆÓÎä 
ния (и передачи) документов.
Атрибуты: Таблица №2 <ElementType name=»Êîììåð÷åñêàÿÈíôîðìàöèÿ» content=»eltOnly» order=»seq»>
Содержит: ДополнительныйРек- <description> Ñîáèðàòåëüíûé ýëåìåíò äëÿ âñåãî, ÷òî ìîæåò áûòü óïîìÿíóòî â ïðîöåññå
îáìåíà</description>
визит (0-*). <AttributeType name=»Êîììåíòàðèé» dt:type=»string» required=»no»>
description>Ïðåäíàçíà÷åí äëÿ ïåðåäà÷è «ñîïðîâîäèòåëüíîé çàïèñêè» â âèäå ïðîèçâîëü-
íîé òåêñòîâîé èíôîðìàöèè ïî äîêóìåíòó.</description>
Контакт (Contact) </AttributeType>
Описание: Контакт предназначен <attribute type=»Êîììåíòàðèé»/>
<element type=»Êîíòðàãåíò» minOccurs=»0" maxOccurs=»*»/>
для ответа на вопросы: «Где найти?» <element type=»Ñêëàä» minOccurs=»0" maxOccurs=»*»/>
и «Кого спросить?». <element type=»Áàíê» minOccurs=»0" maxOccurs=»*»/>
<element type=»Êàòàëîã» minOccurs=»0" maxOccurs=»*»/>
Содержит список ФИО контактных <element type=»ÏàêåòÏðåäëîæåíèé» minOccurs=»0" maxOccurs=»*»/>
лиц (например, список сотрудников <element type=»Äîêóìåíò» minOccurs=»0" maxOccurs=»*»/>
</ElementType>
отдела продаж), список телефонов,
факсов, адресов электронной почты
контакта, ICQ. жет быть упомянуто в процессе обме- те. В стандарте учтены различные
Атрибуты: Таблица №3 на. аспекты работ как интернет-компа-
Содержит: КонтактноеЛицо (0-*), <description> - предназначен для пе- ний, так и обычных предприятий.
Телефон (0-*), Факс (0-*), Почта (0- редачи «сопроводительной записки» Стандарт предлагает подробную схе-
*), ICQ (0-*). в виде произвольной текстовой ин- му обмена с описанием атрибутов и
формации по документу. представлением её в виде кода
Пример структуры XML- Представленный стандарт обме- HTML. Это соглашение является важ-
схемы в формате HTML. на коммерческой информацией дает ным шагом, позволяющим расши-
<КоммерческаяИнформация> соби- возможность организациям обмени- рить сферу применения Интернет-
рательный элемент для всего, что мо- ваться информацией в одном форма- технологий на российском рынке.
женщина и компьютер

Уступите
место
женщине!
С древних времен отношения меж- Светлана через свое агентство нашла Для себя с Мариной мы отметили
ду полами складывались на основании нам несколько серьезных работодате- следующее: в этих фирмах никто не по-
физического превосходства. Такие от- лей и оформила нас на собеседования. интересовался нашим уровнем профес-
ношения остались и сегодня. До сих пор Вакансии были выбраны такие: систем- сионализма.
«положено», что мужчина – сильный ный администратор, тестировщик, про- Еще скажу пару слов о размерах
пол – занимается тяжелой работой и граммист. заработной платы: у женщин оплата
добыванием еды, а женщина – слабое Мы ожидали услышать байки про труда, в основном, все-таки ниже, чем
создание – должна рожать детей и дер- глупеньких девушек, которые готовы у мужчин. По данным все того же кад-
жать в порядке дом. Мужчина – силь- упасть в обморок, когда их просят дать рового агентства, средний уровень за-
ный, а женщина – мудрая. Только се- «подмышку», долго ищут ту кнопку, работной платы для мужчины-програм-
годня все немного перепуталось: для которая все выключает, или путает мо- миста Oracle составляет $600 - $800 в
добывания еды мужчине не надо боль- нитор с телевизором. Однако ответы месяц (в отдельных местах, до $1200),
ше ходить на охоту, а достаточно весь были совершенно неожиданными. а женщинам платят всего $200 - $400.
день просидеть у экрана монитора, на- Первый ответ был больше похож на Причем, малейшая профессиональная
жимая кнопочки. И чем эта работа та- отмазку: в объявлении говорилось, что ошибка, допущенная женщиной-про-
кая тяжелая и трудоемкая? Сегодня фирма не против взять студента стар- граммистом, рассматривается коллега-
такие профессии, как системный адми- ших курсов технического ВУЗа с базо- ми с пристальным вниманием.
нистратор или программист приобрели выми знаниями MS SQL 7.0/2000. В Наша собственная статистика тако-
метку сугубо мужского способа зара- фирме же мне сказали, что брать сту- ва: из 16 фирм, в которые были направ-
батывания кучи денег. Сегодня эти спе- дентку они не намерены, при этом не лены наши резюме, только восемь не
циальности очень престижны и высо- объясняя конкретной причины. С Мари- остались без ответа (учитывая, что кад-
кооплачиваемы в России (не совсем ной же была проведена беседа о «лич- ровое агентство уже проводило первич-
так, глав.ред.), а тем более за рубежом. ном», где подробно допытывались ин- ное собеседование), а из восьми ока-
А почему бы женщине не заняться та- формации о ее семейном положении. залось 6 отказов.
кой простой работой – и мудрость мож- В итоге ответ был весьма суров: «Де- Так что же теперь? Неужели нам,
но применить, и физической нагрузки вушку на работу не возьму… Девушка женщинам, так и не суждено найти ра-
практически никакой. Так почему же становится мамой, а программирова- боту по специальности? Напротив!
женщине так тяжело найти работу в ние – это…», и далее - длинный моно- Женщина своего добьется! В различ-
этих отраслях? Почему даму неохотно лог о непрерывности и качественности ного рода компаниях, занимающихся
берут на работу даже тестером, не го- интеллектуального процесса. разработками бухгалтерского про-
воря уже о программистах? В другом, исключительно мужс- граммного обеспечения, в торговых
Столкнувшись с такой дискримина- ком коллективе, честно признались, центрах, в банках, да и во многих дру-
цией по половому признаку, мы с под- что если в отделе будет постоянно гих местах успешно работают женщи-
ругой решили провести небольшое ис- находиться женщина (особенно мо- ны: WEB-дизайнеры, системные адми-
следование в этой области (а заодно и лодая), то работать будет тяжелее: нистраторы, координаторы техническо-
устроиться на работу), прибегнув к по- придется каждый день бриться, сле- го отдела, системные аналитики, да и
мощи приятельницы, которая работает дить за чистотой рубашек, контроли- программисты-разработчики тоже. Не-
в агентстве по трудоустройству. ровать свою речь и вообще соответ- смотря ни на что, самые настойчивые
Резюме выглядели следующим об- ствовать «стандарту». Вывод был и упрямые все-таки умудряются добить-
разом: я - студентка шестого курса однозначен – присутствие барышни, ся потрясающих результатов. И мы с
МГТУ им. Баумана, знания СУБД: безусловно, нарушит гармонию в Мариной тоже добились-таки того, к
Access, MS SQL 7.0, администрирова- этом коллективе, и нас здесь не чему стремились! Она стала работать
ние 1С-Предприятие 7.7; базовые зна- ждут… А Светлана со своей стороны в одной из ведущих компаний по раз-
ния бухучета, опыт Web-разработок, узнала от руководителя отдела, авто- работке программного обеспечения для
знание английского языка. Марина – в ра многочисленных научных публика- компаний сотовой связи, я же устрои-
этом году закончила МГТУ с красным ций с педагогическим образованием, лась системным администратором в
дипломом, имела разовый опыт разра- что русского литературного языка не торговый центр. Так что вывод один:
ботки программы для небольшой фир- хватает для постановки задачи, поэто- при правильном подходе выживет силь-
мы, знания: Delphi, MS SQL 7.0, базо- му он не сможет корректно поставить нейший — будь это мужчина или жен-
вые знания 1С, технический английский задачу женщине-программисту, т.е. щина, надо только быть настойчивым!
+ разговорный, начала изучение C++. без нормативной лексики… Евгения Саблина

94
женщина и компьютер

Почему их
мало
в компьютерных
компаниях
Бурно развивающийся бизнес в состав гормонов чувства и гормонов туации реагируют на ситуацию, а жен-
сфере компьютерных технологий инте- действия у мужчин и женщин, то карти- щины – на раздражитель. Мужчины ста-
ресует многих людей. Привлекатель- на будет выглядеть примерно так: на раются найти деловое решение, а жен-
ность работы в крупной компьютерной одну часть гормонов чувства у мужчин щины – выйти из ситуации с минималь-
фирме состоит из нескольких момен- приходится около тысячи частей гормо- ными потерями для себя.
тов. Один из них – реальная возмож- нов действия, а у женщин – с точнос- Ещё одна особенность таких фирм
ность заработать деньги. Второй – пер- тью до наоборот: на одну часть гормо- в том, что по роду деятельности про-
спектива достаточно быстрого продви- нов действия приходится в тысячу раз граммистам часто приходится рабо-
жения по служебной лестнице, третий - больше гормонов чувства. Получается, тать вдвоём, в «тандеме». И от взаи-
шанс показать себя, проявить свои спо- что основная деятельность женщин – моотношений друг с другом часто за-
собности. Именно так или подобным об- чувства, а основная деятельность муж- висит результат такой работы. Здесь
разом отвечают молодые люди в воз- чин – действие. И когда мужчина начи- тоже есть несколько моментов. При
расте 22-28 лет на вопрос о причинах, нает много чувствовать, а женщина – работе в паре один из специалистов
побудивших их к поиску работы в ком- много действовать, нарушается гормо- становится ведущим, другой – ведо-
пьютерной компании. Основной состав нальный баланс в организме. Ничего мым. При этом роли часто меняются.
персонала бурно развивающихся ком- хорошего из этого, как Вы сами пони- Мужская психика довольно спокойно
пьютерных фирм – именно такие моло- маете, выйти не может. Да и при рас- реагирует на смену ролей, так как
дые ребята, со свежими мозгами, оп- смотрении ситуации взаимоотношений дело важнее. А вот женская – нет.
тимизмом и желанием преобразовать в группе видно различное отношение Выше я уже писал, что для женщины
окружающий мир. Есть, правда, одна женского коллектива к мужчинам, при- важно отношение к ней. И по этой при-
особенность: женщин в такие компании сутствующих в нем, или мужского – к чине смена ролей для женщины не
берут на работу достаточно неохотно. 1-2 женщинам в том же положении. всегда происходит безболезненно.
Анализ состава компаний говорит о Если женщины в такой ситуации будут Часто эти перемены роли восприни-
том, что компьютерный бизнес преиму- чувствовать себя комфортно, то муж- маются как покушение на безопас-
щественно мужской. И причин для это- чины просто заработают невроз, пото- ность, что, соответственно, может
го достаточно много. Прежде всего, му что женщине важно отношение к вызвать совершенно непредсказуе-
различна мотивация. Для мужчин важ- ней, а мужчине – отношение к тому, что мую реакцию. По этой причине не-
но достижение поставленной цели, они, он делает. большие компании, состоящие из 5-7
собственно, для этого и предназначе- Специфика работы в компьютерной человек, являются достаточно замк-
ны – преобразовывать окружающий фирме такова, что часто персоналу при- нутыми сообществами мужчин, а в
мир. Для женщин же характерно жела- ходится работать в стрессовой ситуа- крупных компьютерных компаниях
ние сохранения жизни. И довольно ча- ции (например, сдача проекта, который женщины занимают должности, свя-
сто представительницы прекрасного по каким - либо причинам «не идёт»). А занные с административной работой.
пола идут на работу в такие компании восприятие стресса у мужчин и женщин Да простят меня воинствующие фе-
не от хорошей жизни. Медицинская ста- различно - мозг реагирует по-разному. министки, но для женщин есть много ра-
тистика, в свою очередь, показывает, У женщин в момент опасности, пусть бочих мест, где востребованы замеча-
что, как бы компьютеры не были совер- даже гипотетической, в работу включа- тельные женские качества: сострада-
шенны, они больше предназначены для ются все мозговые центры и отключа- ние, чувствительность, женская интуи-
мужчин, чем для женщин. В психологи- ются только после того, как опасность ция, умение эффективно работать в мо-
ческом плане, компьютерный бизнес перестаёт существовать. У мужчин в нотонном режиме (от которого мужчи-
предполагает больше работу в систе- момент опасности включается только ны сходят с ума), ассоциативное мыш-
ме «человек – знак» и требует именно один конкретный мозговой центр, дея- ление, стремление выжить, умение со-
логического мышления - правополу- тельность которого через 7-10 минут здать комфорт и уют. Без женщин не
шарного. Женщинам же больше свой- угасает, если опасность не подтверж- обходится ни одна сфера жизнедея-
ственно эмоциональное мышление - ле- дена другими центрами, отвечающими тельности, просто давайте заниматься
вополушарное. Даже гормональный со- за реагирование на конкретный вид тем, для чего мы предназначены.
став у мужчин и женщин сильно отли- раздражителя. И получается, что муж- Вячеслав Михалёв,
чается. Если сравнить количественный чины при возникновении стрессовой си- психолог.

№1, октябрь 2002 95


анонс

№1, Октябрь, 2002 год ЧИТАЙТЕ


УЧЕРЕДИТЕЛИ
Владимир Положевец
В СЛЕДУЮЩЕМ
Александр Михалев

РУКОВОДИТЕЛЬ ПРОЕКТА
НОМЕРЕ:
Петр Положевец

РЕДАКЦИЯ
Главный редактор Тема номера посторонними лицами для доступа к
Александр Михалев конфиденциальной информации и
chief@samag.ru БЕЗОПАСНОСТЬ нарушения работы системы, вплоть
Ответственный секретарь Интервью с начальником до полной потери данных и работос-
Наталья Хвостова «Управления Р» пособности.
sekretar@samag.ru Александром Слуцким Основными пользователями сис-
Был уже восьмой час вечера. В тем аудита безопасности являются
Художник кабинете начальника отдела по борь- профессионалы: сетевые админист-
Игорь Усков бе с компьютерными преступлениями раторы, специалисты по безопаснос-
igus@samag.ru Александра Слуцкого обсуждались ти и т.д. Простые пользователи тоже
Верстка детали завтрашнего «оперативного могут использовать сканеры, но ин-
Владимир Положевец мероприятия». Собирались брать ма- формация, выдаваемая такими про-
imposer@samag.ru терого хакера. Когда детали завтраш- граммами, как правило специфична,
Владимир Лукин него рейда намеченного между про- что ограничивает возможности ее ис-
maker_up@samag.ru чим на 6 утра были согласованы, пользования неподготовленным чело-
Александр Сергеевич извинился, до- веком.
РЕКЛАМНАЯ СЛУЖБА стал из сумки котлету и начал устало
тел./факс:(095) 298-0316 ее поглощать. «Вы меня извините, я МЫТАРСТВА
Наталья Хохлова еще не завтракал». Собственно я и БЛАЖЕННОГО
тел.:(095) 928-8253 (доб. 112) сам уже понял, что работа самого ин- СИСАДМИНА
Наталья Политыко теллектуального «хакерского» отдела Большинство системных админи-
reklama@samag.ru не сахар. страторов не выбирают эту карьеру,
она предназначена им судьбой.
103012, г. Москва, Ветошный Создание простейшей Меня судьба взяла за шкирку и по-
переулок, дом 13/15 статистики для Internet садила в сисадминское кресло со-
тел.: (095) 928-8253 (доб. 112) Service Providers вершенно случайно. Я работал про-
факс: (095) 928-8253 В большенстве случаев, при со- граммистом в одной государствен-
Е-mail: info@samag.ru здании ISP, самым главным вопро- ной конторе и однажды имел несча-
Internet: www.samag.ru сом становится — построение на пер- стье попасться на глаза шефа в не
вых порах простейшего биллинга: совсем трезвом состоянии. Дело
ИЗДАТЕЛЬ подсчет количества времени и денег, было в 10 часов утра — самое, что
ЗАО «Редакция «Учительской затраченных пользователем в ни на есть, рабочее время. Есте-
газеты» Internet’e. ственно, на следующий день меня
В этом нет ничего сверхсложно- вызвали на ковер и заявили, что
Отпечатано ЗАО «Холдинговая го, но попробуем разобраться, и по- быть мне отныне... ни за что не по-
компания «Блиц-информ». стараемся создать простейший бил- верите — «системным администра-
Образцовая типография «Блиц- линг. Добавлять что-либо Вы сможе- тором»!
принт» г. Киев, ул. Довженко, 3 те сами или наоборот — убирать: как
Тираж 5000 экз. Вам будет угодно. Продолжение статьи
про механизм отражений
Журнал зарегистрирован Cравнение сетевых в Java.
В Министерстве РФ по делам пе- сканеров безопасности «Я постараюсь рассказать о са-
чати, телерадиовещания и Сканер безопасности - это про- мом нетривиальном механизме мира
средств массовых коммуникаций граммное средство для удаленной или отражений — создании собственных
(свидетельство ПИ № 77-12542 локальной диагностики различных загрузчиков классов, а также о том,
от 24 апреля 2002 г.) элементов сети на предмет выявле- как компилировать новые классы из
ния в них различных уязвимостей, исходного Java-текста для передачи
которые могут быть использованы собственному загрузчику».

96