Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 26 (всего у книги 67 страниц)
Теперь, когда вы увидели, как проверять существующие блокировки файла, давайте посмотрим, что произойдет, когда две программы состязаются за получение блокировки для одного и того же участка файла. Вы воспользуетесь снова программой lock3 для блокировки файла и новой программой lock5 для попытки установить новую блокировку файла. В завершение вы добавите в программу lock5 несколько вызовов для снятия блокировки (упражнение 7.11).
Упражнение 7.11. Конкурирующие блокировки
Далее приведена программа lock5.с, которая пытается заблокировать уже заблокированные участки файла вместо того, чтобы проверить состояние блокировки других частей файла.
После директив #include и объявлений откройте дескриптор файла.
#include
#include
#include
#include
const char *test_file = «/tmp/test_lock»;
int main() {
int file_desc;
struct flock region_to_lock;
int res;
file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
if (!file_desc) {
fprintf(stderr, «Unable to open %s for read/writen», test_file);
exit(EXIT_FAILURE);
}
В оставшейся части программы задаются разные участки файла, и делается попытка установить для них блокировки разных типов:
region_to_lock.l_type = F_RDLCK;
region_to_lock.l_whence = SEEK_SET;
region_to_lock.l_start = 10;
region_to_lock.l_len = 5;
printf(«Process %d, trying F_RDLCK, region %d to %dn», getpid(), (int)region_to_lock.l_start,
(int)(region_to_lock.l_start + region_to_lock.l_len));
res = fcntl(file_desc, F_SETLK, ®ion_to_lock);
if (res == -1) {
printf(«Process %d – failed to lock regionn», getpid());
} else {
printf(«Process %d – obtained lock regionn», getpid());
}
region_to_lock.l_type = F_UNLCK;
region_to_lock.l_whence = SEEK_SET;
region_to_lock.l_start = 10;
region_to_lock.l_len = 5;
printf(«Process %d, trying F_UNLCK, region %d to %dn», getpid(), (int)region_to_lock.l_start,
(int)(region_to_lock.l_start + region_to_lock.l_len));
res = fcntl(file_desc, F_SETLK, ®ion_to_lock);
if (res == -1) {
printf(«Process %d – failed to unlock regionn», getpid());
} else {
printf(«Process %d – unlocked regionn», getpid());
}
region_to_lock.l_type = F_UNLCK;
region_to_lock.l_whence = SEEK_SET;
region_to_lock.l_start = 0;
region_to_lock.l_len = 50;
printf(«Process %d, trying F_UNLCK, region %d to %dn», getpid()", (int)region_to_lock.l_start,
(int)(region_to_lock.l_start + region_to_lock.l_len));
res = fcntl(file_desc, F_SETLK, ®ion_to_lock);
if (res == -1) {
printf(«Process %d – failed to unlock regionn», getpid());
} else {
printf(«Process %d – unlocked regionn», getpid());
}
region_to_lock.l_type = F_WRLCK;
region_to_lock.l_whence = SEEK_SET;
region_to_lock.lstart = 16;
region_to_lock.l_len = 5;
printf(«Process %d, trying F_WRLCK, region %d to %dn», getpid(), (int)region_to_lock.l_start,
(int)(region_to_lock.l_start + region_to_lock.l_len));
res = fcntl(file_desc, F_SETLK, ®ion_to_lock);
if (res == -1) {
printf(«Process %d – failed to lock regionn», getpid());
} else {
printf(«Process %d – obtained lock on regionn», getpid());
}
region_to_lock.l_type = F_RDLCK;
region_to_lock.l_whence = SEEK_SET;
region_to_lock.l_start = 40;
region_to_lock.l_len = 10;
printf(«Process %d, trying F_RDLCK, region %d to %dn», getpid(), (int)region_to_lock.l_start,
(int)(region_to_lock.l_start + region_to_lock.l_len));
res = fcntl(filedesc, F_SETLK, ®ion_to_lock);
if (res == -1) {
printf(«Process %d – failed to lock regionn», getpid());
} else {
printf(«Process %d – obtained lock on regionn», getpid());
}
region_to_lock.l_type = F_WRLCK;
region_to_lock.l_whence = SEEK_SET;
region_to_lock.l_start = 16;
region_to_lock. l_len = 5;
printf(«Process %d, trying F_WRLCK with wait, region %d to %dn», getpid(), (int)region_to_lock.l_start,
(int)(region_to_lock.l_start + region_to_lock.l_len));
res = fcntl(file_desc, F_SETLKW, ®ion_to_lock);
if (res == -1) {
printf(«Process %d – failed to lock regionn», getpid());
} else {
printf(«Process %d – obtained lock, on regionn», getpid());
}
printf («Process %d endingn», getpid());
close(file_desc);
exit(EXIT_SUCCESS);
}
Если вы сначала запустите программу lock3 в фоновом режиме, далее сразу запускайте новую программу:
$ ./lock3 &
$ process 227 locking file
$ ./lock5
Вы получите следующий вывод:
Process 227 locking file
Process 228, trying F_RDLCK, region 10 to 15
Process 228 – obtained lock on region
Process 228, trying F_UNLCK, region 10 to 15
Process 228 – unlocked region
Process 228, trying F_UNLCK, region 0 to 50
Process 228 – unlocked region
Process 228, trying F_WRLCK, region 16 to 21
Process 228 – failed to lock on region
Process 228, trying F_RDLCK, region 4 0 to 50
Process 228 – failed to lock on region
Process 228, trying F_WRLCK with wait, region 16 to 21
Process 227 closing file
Process 228 – obtained lock on region
Process 228 ending
Как это работает
Сначала программа пытается заблокировать участок с 10-го по 15-й байты с помощью разделяемой блокировки. Эта область уже заблокирована блокировкой того же типа, но одновременные разделяемые блокировки допустимы, и установка блокировки завершается успешно.
Затем программа снимает свою разделяемую блокировку с участка файла, и эта операция тоже завершается успешно. Далее она пытается разблокировать первые 50 байтов файла, даже если у них нет никакой блокировки. Это действие тоже завершается успешно, потому что, несмотря на то, что программа не установила блокировок на этот участок, конечный результат запроса на снятие блокировки заключается в констатации того, что для первых 50 байтов данная программа не поддерживает никаких блокировок.
Далее программа пытается заблокировать участок с 16-то по 21-й байты исключительной блокировкой. Эта область уже заблокирована разделяемой блокировкой, поэтому новая попытка блокировки завершается аварийно, т.к. не может быть создана исключительная блокировка.
После этого программа пробует установить разделяемую блокировку на участок с 40-го по 50-й байты. Эта область уже заблокирована исключительной блокировкой, поэтому данная операция снова завершается аварийно.
В заключение программа опять пытается получить исключительную блокировку для участка с 16-го по 21-й байты, но в этот раз она применяет команду F_SETLKW
, позволяющую ждать до тех пор, пока блокировка не будет установлена. В выводе наступает долгая пауза, длящаяся, пока программа lock3, заблокировавшая этот участок, завершает вызов sleep
и закрывает файл, тем самым снимая все установленные блокировки. Программа lock5 возобновляет выполнение, успешно блокирует участок файла и затем тоже завершается.
Есть второй метод блокировки файлов – функция lockf
. Она тоже действует, используя дескрипторы файлов.
У функции следующий прототип:
#include
int lockf(int fildes, int function, off_t size_to_lock);
Параметр function
может принимать следующие значения:
□ F_ULOCK
– разблокировать;
□ F_LOCK
– заблокировать монопольно;
□ F_TLOCK
– проверить и заблокировать монопольно;
□ F_TEST
– проверить наличие блокировок других процессов.
Параметр size_to_lock
содержит количество обрабатываемых байтов, отсчитываемых в файле от текущей величины смещения. У функции lockf
более простой интерфейс, чем у вызова fcntl
в основном потому, что у нее меньше функциональных возможностей и гибкости. Для применения функции вы должны найти начало участка, который хотите заблокировать, затем вызвать функцию, указав количество блокируемых байтов.
Как и в случае вызова fcntl
, все блокировки только рекомендательные; они на самом деле не могут помешать чтению из файла или записи в файл. За проверку имеющихся блокировок отвечают программы. Эффект от смешивания блокировок с помощью fcntl
и блокировок с помощью lockf
непредсказуем, поэтому вам следует решить, какой способ выбрать, и строго его придерживаться.
Обсуждение блокировок не было бы законченным без упоминания об опасности взаимоблокировок или тупиков. Предположим, что две программы хотят обновить один и тот же файл. Им обеим нужно обновить байт 1 и байт 2 одновременно. Программа А выбирает первым обновление байта 2, затем байта 1. Программа В пытается обновить сначала байт 1, затем байт 2.
Обе программы стартуют одновременно. Программа А блокирует байт 2, а программа В – байт 1. Программа А пытается установить блокировку для байта 1. Поскольку он уже заблокирован программой В, программа А ждет. Программа В пытается заблокировать байт 2. Поскольку он уже заблокирован программой А, программа В тоже ждет.
Ситуация, в которой ни одна программа не может выполняться, называется взаимоблокировкой или тупиковой ситуацией. Эта проблема очень распространена в работающих с базами данных приложениях, в которых многие пользователи часто пытаются получить доступ к одним и тем же данным. Многие коммерческие реляционные базы данных обнаруживают взаимоблокировки и устраняют их автоматически; ядро Linux этого не делает. Для устранения возникшего непорядка требуется внешнее вмешательство, возможно, принудительно завершающее выполнение одной из программ.
Программистам стоит опасаться подобных ситуаций. Если у вас есть несколько программ ждущих установки блокировок, нужно быть очень внимательным и рассмотреть возможность возникновения тупиковой ситуации. В данном примере этого легко избежать: обе программы просто должны блокировать нужные им байты в одном и том же порядке или использовать область большего размера для блокировки.
Примечание
В этой книге из-за ограниченности объема у нас нет возможности рассматривать трудности действующих одновременно программ. Если вы хотите почитать побольше об этом, попробуйте найти книгу: Ben-Ari М. Principles of Concurrent and Distributed Programming. – Prentice Hall, 1990 (Бен-Ари M. Принципы параллельного и распределенного программирования).
Базы данных
Вы научились использованию файлов для хранения данных, зачем применять для этого базы данных? Очень просто, в некоторых обстоятельствах средства баз данных предоставляют лучший способ решения проблем. Применение базы данных лучше, чем хранение файлов, по двум причинам:
□ вы можете хранить записи данных переменного размера, что довольно трудно реализовать с помощью простых неструктурированных файлов;
□ базы данных эффективнее хранят и извлекают данные, применяя индекс. Это большое преимущество, потому что этот индекс должен быть не просто номером записи, который легко было бы реализовать в обычном файле, а произвольной строкой.
База данных dbmВсе версии Linux и большая часть вариантов систем UNIX поставляются с базовым, но очень эффективным набором подпрограмм для хранения данных, называемым базой данных dbm. База данных dbm отлично подходит для хранения индексированных данных, которые относительно статичны. Некоторые консерваторы в области баз данных могут возразить, что dbm – вовсе не база данных, а просто система хранения индексных файлов. Стандарт X/Open, тем не менее, называет dbm базой данных, поэтому в книге мы будем продолжать называть ее так же.
Введение в базу данных dbm
Несмотря на взлет свободно распространяемых реляционных баз данных, таких как MySQL и PostgreSQL, база данных dbm продолжает играть важную роль в системе Linux. Дистрибутивы, использующие RPM, например, Red Hat и SUSE, применяют dbm как внутреннее хранилище для данных устанавливаемых пакетов. Реализация LDAP с открытым кодом, Open LDAP (Lightweight Directory Access Protocol, облегченный протокол доступа к каталогу), также может применять dbm как механизм хранения. Преимущества dbm по сравнению с более сложными базами данных, такими как MySQL, в ее «легковесности» и возможности более простого встраивания в распределенный двоичный код (distributed binary), поскольку не требуется установка отдельного сервера базы данных. Во время написания книги программы Sendmail и Apache использовали dbm.
База данных dbm позволяет хранить структуры данных переменного размера с помощью индекса и затем извлекать структуру либо используя индекс, либо просто последовательно просматривая базу данных. Базу данных dbm лучше всего применять для данных, к которым часто обращаются, но которые редко обновляются, поскольку она довольно медленно создает элементы, но быстро извлекает их.
В данный момент мы сталкиваемся с небольшой проблемой: в течение многих лет было сформировано несколько версий базы данных dbm с разными API и средствами. Существует исходный набор dbm, "новый" набор dbm, называемый ndbm, и реализация проекта GNU gdbm. Реализация GNU может эмулировать интерфейсы более старой версии dbm и версии ndbm, но ее собственный интерфейс существенно отличается от других реализаций. Различные дистрибутивы Linux поставляются с библиотеками разных версий dbm, но самый популярный вариант – поставка с библиотекой gdbm и установка ее с возможностью эмуляции интерфейсов двух других типов.
В книге мы собираемся сосредоточиться на интерфейсе ndbm, поскольку он стандартизован X/OPEN и его применять легче, чем непосредственно интерфейс реализации gdbm.
Получение dbm
Самые широко распространенные дистрибутивы Linux приходят с уже установленной версией gdbm, хотя в некоторых из них вам придется применить соответствующий диспетчер пакетов (package manager) для установки нужных библиотек разработки. Например, в дистрибутиве Ubuntu вам может понадобиться диспетчер пакетов Synaptic для установки пакета libgdbm-dev, если он не установлен по умолчанию.
Если вы хотите просмотреть исходный код или используете дистрибутив, в который не включен встроенный пакет разработки, реализацию GNU можно найти по адресу www.gnu.org/software/gdbm/gdbm.html.
Устранение неполадок и повторная установка dbm
Эта глава написана в расчете на то, что у вас установлена реализация GNU gdbm, укомплектованная библиотеками совместимости с ndbm. Это обычный вариант для дистрибутивов Linux, однако, как упоминалось ранее, возможно, вам придется явно устанавливать пакет библиотеки разработки для того, чтобы компилировать файлы с использованием подпрограмм ndbm.
К сожалению, требуемые библиотеки директив include
и компоновки слегка различаются в разных дистрибутивах, поэтому, несмотря на их установку, вам, возможно, придется поэкспериментировать немного, чтобы выяснить, как компилировать исходные файлы с использованием ndbm. Наиболее частый вариант – база данных gdbm установлена и поддерживает по умолчанию режим совместимости с версией ndbm. Дистрибутивы, например Red Hat, как правило, делают это. В этом случае вам нужно выполнить следующие шаги:
1. Включите в ваш файл на языке С файл ndbm.h.
2. Включите каталог заголовочного файла /usr/include/gdbm с помощью опции -I/usr/include/gdbm
.
3. Скомпонуйте программу с библиотекой gdbm, используя опцию -lgdbm
.
Если программа не работает, обычная альтернатива, принятая в новейших версиях дистрибутивов Ubuntu и SUSE, – устанавливается база данных gdbm, но при необходимости явно запрашивается совместимость с базой данных ndbm, и вы должны компоновать программу сначала с библиотекой совместимости, а затем с основной библиотекой. В этом случае надо выполнить следующие шаги:
1. Вместо файла ndbm.h включите в ваш файл на С файл gdbm-ndbrh.h.
2. Включите каталог заголовочного файла /usr/include/gdbm с помощью опции -I/usr/include/gdbm
.
3. Скомпонуйте программу с дополнительной библиотекой совместимости gdbm, используя опцию -lgdbm_compat -lgdbm
.
Загружаемый Makefile и С-файлы dbm установлены с первым вариантом, принятым по умолчанию, но содержат комментарии о том, как их отредактировать, чтобы можно было легко выбрать второй вариант. В оставшейся части главы мы полагаем, что в вашей системе совместимость с ndbm – характеристика, принятая по умолчанию.
Подпрограммы dbmКак и библиотека curses, обсуждавшаяся нами в главе 6, средство dbm состоит из заголовочного файла и библиотеки, которая должна компоноваться с программой во время компиляции последней. Библиотека называется просто dbm, но поскольку мы обычно применяем в системе Linux реализацию GNU, необходимо компоновать с этой реализацией, используя в строке компиляции опцию -lgdbm
. Заголовочный файл – ndbm.h.
Прежде чем мы попытаемся описать каждую функцию, важно понять чего старается достичь база данных dbm. Когда вы поймете это, гораздо легче будет уяснить, как применять функции dbm.
Основной элемент базы данных dbm – блок данных, предназначенных для хранения, связанный с блоком данных, действующих как ключ для извлечения данных. У всех баз данных dbm должны быть уникальные ключи для каждого хранящегося блока данных. Значение ключа используется как индекс хранящихся данных. Нет ограничений на ключи или данные и не определено никаких ошибок при использовании данных или ключей слишком большого размера. Стандарт допускает реализацию, ограничивающую размер ключа/данных величиной 1023 байта, но, как правило, ограничений не существует, поскольку реализации оказались более гибкими, чем требования, предъявляемые к ним.
Для манипулирования этими блоками как данными в заголовочном файле ndbm.h определен новый тип данных, названный datum
. Конкретное содержимое этого типа зависит от реализации, но он должен, как минимум, включать следующие элементы:
void *dptr;
size_t dsize
Здесь datum
– тип, который будет определяться оператором typedef. В файле ndbm.h также дано определение dbm, представляющее собой структуру, применяемую для доступа к базе данных, и во многом похожее на определение FILE
, используемое для доступа к файлам. Внутреннее содержимое dbm typedef
зависит от реализации и никогда не должно использоваться.
Для ссылки на блок данных при использовании библиотеки dbm вы должны объявить datum
, задать указатель dptr
для указания на начало данных, а также задать параметр dsize
, содержащий размер данных. На хранящиеся данные и индекс, применяемый для доступа к ним, всегда нужно ссылаться с помощью типа datum
.
О типе DBM
лучше всего думать как об аналоге типа FILE
. Когда вы открываете базу данных dbm, обычно создаются два физических файла: один с расширением pag, а другой с расширением dir. Возвращается один указатель dbm, который применяется для обращения к обоим файлам как к паре. Файлы никогда не следует непосредственно читать и в них не нужно писать; они предназначены для доступа через стандартные операции dbm.
Примечание
В некоторых реализациях эти два файла объединены, и создается один новый файл.
Если вы знакомы с базами данных SQL, то заметите, что в случае базы данных dbm не существует структур таблиц или столбцов. Эти структуры не нужны, т.к. dbm не задает фиксированного размера элементов сохраняемых данных и не требует описания внутренней структуры для них. Библиотека dbm работает с блоками неструктурированных двоичных данных.
Функции доступа dbmТеперь, когда мы рассказали об основах работы библиотеки dbm, можем поподробнее рассмотреть функции. Далее приведены прототипы основных функций dbm.
#include
DBM *dbm_open(const char* filename, int file_open_flags,
mode_t file_mode);
int dbm_store(DBM *database_descriptor, datum key, datum content,
int store_mode);
datum dbm_fetch(DBM* database descriptor, datum key);
void dbm_close(DBM *database descriptor);
dbm_open
Эта функция применяется для открытия имеющихся баз данных и для создания новых баз данных. Аргумент filename
– имя файла базы данных без расширения dir или pag.
Остальные параметры такие же, как второй и третий параметры функции open
, с которой вы встречались в главе 3. Вы можете использовать те же директивы #define
. Второй аргумент управляет возможностью чтения базы данных, записью в нее или обеими операциями. Если создается новая база данных, флаги должны быть двоичными O_READ
с O_CREAT
, чтобы разрешить создание файлов. Третий аргумент задает начальные права доступа к файлам, которые будут созданы.
Функция dbm_open
возвращает указатель на тип DBM
. Он применяется во всех последующих обращениях к базе данных. В случае аварийного завершения возвращается (DBM*)0
.
dbm_store
Эту функцию применяют для ввода данных в базу данных. Как упоминалось ранее, все данные должны сохраняться с уникальным индексом. Для определения данных, которые вы хотите сохранить, и индекса, используемого для ссылки на них, следует задать два типа datum: один для ссылки на индекс, а другой – на реальные данные. Последний параметр store_mode
управляет действиями, совершаемыми при попытке сохранить какие-либо данные с применением ключа, который уже существует. Если установлено значение параметра dbm_insert
, сохранение завершается аварийно и функция dbm_store
возвращает 1. Если установлено значение параметра dbm_replace
, новые данные заменяют существующие и dbm_store
возвращает 0. При возникновении других ошибок функция dbm_store
возвращает отрицательные числа.
dbm_fetch
Подпрограмма dbm_fetch
применяется для извлечения данных из базы данных. Она принимает в качестве параметра указатель dbm, возвращенный предшествующим вызовом функции dbm_open
и тип datum
, который должен быть задан как указатель на ключ. Тип datum
возвращается, если данные, относящиеся к используемому ключу, найдены в базе данных, возвращаемая структура datum
будет иметь значения dptr
и dsize
, ссылающиеся на возвращенные данные. Если ключ не найден, dptr
будет равен null
.
Примечание
Важно помнить, что функция
dbm_fetch
возвращает только параметр типаdatum
, содержащий указатель на данные. Реальные данные могут находиться в локальной области памяти внутри библиотеки dbm и должны быть скопированы в переменные программы перед дальнейшими вызовами функций dbm.
dbm_close
Эта подпрограмма закрывает базу данных, открытую функцией dbm_open
, и должна получить указатель DBM
, возвращенный предшествующим вызовом dbm_open
.
А теперь выполните упражнение 7.12.
Упражнение 7.12. Простая база данных dbm
Познакомившись с основными функциями базы данных dbm, теперь вы знаете, как написать вашу первую программу для работы с dbm (dbm1.c). В этой программе применяется структура, названная test_data
.
1. Первыми представлены файлы #include
, директивы #define
, функция main
и объявление структуры test_data
:
#include
#include
#include
#include
#include
/* В некоторых системах вам нужно заменить вышестоящую строку строкой #include
#include
#define TEST_DB_FILE «/tmp/dbm1_test»
#define ITEMS_USED 3
struct test_data {
char misc_chars[15];
int any_integer;
char more_chars[21];
};
int main() {
2. В функции main
задайте элементы структур items_to_store
и items_received
, строку key
и типы datum
:
struct test_data items_to_store[ITEMS_USED];
struct test_data item_retrieved;
char key_to_use[20];
int i, result;
datum key_datum;
datum data_datum;
DBM *dbm_ptr;
3. Объявив указатель на структуру типа DBM
, откройте вашу тестовую базу данных для чтения и записи, создав ее при необходимости:
dbm_ptr = dbm_open(TEST_DB_FILE, O_RDWR | O_CREAT, 0666);
if (!dbm_ptr) {
fprintf (stderr, «Failed to open databasen»);
exit(EXIT_FAILURE);
}
4. Теперь добавьте данные в структуру items_to_store
:
memset(items_to_store, ' ', sizeof(items_to_store));
strcpy(items_to_store[0].misc_chars, "First! ");
items_to_store[0].any_integer = 47;
strcpy(items_to_store[0].more_chars, «foo»);
strcpy(items_to_store[1].misc_chars, «bar»);
items_to_store[1].any_integer = 13;
strcpy(items_to_store[1].more_chars, "unlucky? ");
strcpy(items_to_store[2].misc_chars, «Third»);
items_to_store[2].any_integer = 3;
strcpy(items_to_store[2].more_chars, «baz»);
5. Для каждого элемента необходимо сформировать ключ для будущих ссылок в виде первой буквы каждой строки и целого числа. Этот ключ затем будет обозначен key_datum
, когда data_datum
сошлется на элемент items_to_store
. Далее вы сохраняете данные в базе данных:
for (i = 0; i < ITEMS_USED; i++) {
sprintf(key_to_use, «%c%c%d»,
items_to_store[i].misc_chars[0], items_to_store[i].more_chars[0], items_to_store[i].any_integer);
key_datum.dptr = (void*)key_to_use;
key_datum.dsize = strlen(key to_use);
data_datum.dptr = (void*)&items_to_store[i];
data_datum.dsize = sizeof(struct.test_data);
result = dbm_store(dbm_ptr, key_datum, data_datum, DBM_REPLACE);
if (result != 0) {
fprintf(stderr, «dbm_store failed on key %sn», key_to_use);
exit(2);
}
}
6. Далее посмотрите, сможете ли вы извлечь эти новые данные, и в заключение следует закрыть базу данных:
sprintf(key_to_use, «bu%d», 13);
key_datum.dptr = key_to_use;
key_datum.dsize = strlen(key_to_use);
data_datum = dbm_fetch(dbm_ptr, key_datum);
if (data_datum.dptr) {
printf(«Data retrievedn»);
memcpy(&item_retrieved, data_datum.dptr, data_datum.dsize);
printf(«Retrieved item – %s %d %sn», item_retrieved.misc_chars,
item_retrieved.any_integer, item_retrieved.more_chars);
} else {
printf(«No data found for key %sn», key_to_use);
}
dbm_close(dbm_ptr);
exit(EXIT_SUCCESS);
}
Когда вы откомпилируете и выполните программу, вывод будет следующим:
$ gcc -о dbm1 -I/usr/include/gdtm dbm1.с -lgdbm
$ ./dbm1
Data retrieved
Retrieved item – bar 13 unlucky?
Вы получите приведенный вывод, если база данных gdbm установлена в режиме совместимости. Если компиляция не прошла, возможно, вам придется изменить директиву include
, как показано в файле, для использования заголовочного файла gdbm-ndbm.h вместо файла ndbm.h и задать в строке компиляции библиотеку совместимости перед основной библиотекой, как показано в следующей строке:
$ gcc -о dbm1 -I/usr/include/gdbm dbm1.с -lgdbm_compat -lgdbm
Как это работает
Сначала вы открываете базу данных, при необходимости создавая ее. Затем вы заполняете три элемента структуры items_to_store
, чтобы использовать их как тестовые данные. Для каждого из трех элементов вы создаете индексный ключ. Чтобы он оставался простым, используйте первые символы каждой из двух строк плюс сохраненное целое.
Далее вы задаете две структуры типа datum
, одну для ключа и другую для сохраняемых данных. Сохранив три элемента в базе данных, вы конструируете новый ключ и настраиваете структуру типа datum
так, чтобы она указывала на него. Затем вы применяете данный ключ для извлечения данных из базы данных. Убедитесь в успехе, проверив, что dptr
в возвращенном datum
не равен null
. Получив подтверждение, вы можете копировать извлеченные данные (которые могут храниться внутри библиотеки dbm) в вашу собственную структуру, применяя возвращенный размер dbm_fetch
(если этого не сделать, при наличии данных переменного размера вы можете скопировать несуществующие данные). В заключение извлеченные данные выводятся на экран, чтобы продемонстрировать корректность их извлечения.