글쓴이 : 한동훈 ddoch@hitel.kol.co.kr
http://kingkong.kcaf.or.kr/lecture/lecture.html
날 짜 : 1997.5.30
저작권 : 상업적인 용도가 아닌한 어디로든 이동 및 게제 가능



▧ 목 차 ▧
▧ ▧
▧ 1. 들어가는 말 ▧
▧ 2. 도입과 전개 ▧
▧ 3. 여러 함수들 ▧
▧ 4. 그외의 다른 것 ▧
▧ 5. 프로그램을 한번 짜봅시다 ▧



1. 들어가는 말

안녕하세요. ddoch 한동훈입니다.

리눅스를 사용하시면서, 데이터 처리를 많이 하실 것입니다. 예전에 DOS에서 돌아
가던 dBASE 같은 것을 사용하시던 기억이 어렴풋이 떠오르시는 분들도 계실겁니다.
많은 분들 중에서 리눅스에서 쓸만한 데이터베이스가 없나하고 찾으시는 분들도 많
이 계시더군요.

'디비입문'… 글쎄요..
적당한 입문책을 하나 구하셔서 보셔도 좋을 것입니다. DB에 대해 전문프로그래
머가 아닌 일반 프로그래머나 사용자라면 굳이 그럴 필요까지는 없을 겁니다.

잘 아시겠지만, 리눅스에서 돌아가는 관계형 데이터베이스 시스템(RDBMS, 줄여서
'RDB' 라고도 하죠)으로는 많은 것들이 있습니다. 대표적인 것으로는 공개용인
postgreSQL이 있으며, 공개용은 아닌 mSQL, 그외에 다른 툴들이 많이 있습니다.
그외에도 현재 많은 상당히 훌륭한 기능들을 가진 RDBMS들이 나오고 있는 상태
입니다. 이러한 것들은 최종사용자에게도 훌륭한 DB 매니져가 되고, 프로그래머
에게도 활용할 수 있는 다양한 기능들을 제공합니다.

기회가 있으면, postgreSQL 같은 것은 다음기회에 소개하기로 하고,
이번 기회에 말씀드릴 것은, 프로그래머에게 요긴한 DB 관리 툴입니다.

"프로그램 작성중 DB관련 부분을 어떻게 처리하십니까?" 라는 질문에, RDBMS와 연
동하여 처리하시는 분들도 계실 것이고 모든 루틴을 내부적으로 만들어서 사용하
시는 분, C 나 C++로 짜여진 DB 처리툴을 사용하시는 분들도 있을 것입니다.
저도 사실 RDBMS 나 DB 처리툴들은 잘 사용하지 않았습니다. 사용하면 좋을 텐데,
단지 귀찮다는 하나의 이유만으로 내부적으로 펑션을 만들어서 처리를 많이 해
왔던 것 같습니다.

하지만, 프로그램이 대형화 되고, 중요한 DB를 다룬다던지, 또는 퍼포먼스 향상,
다중 프로세스 환경에서의 여러문제 들을 해결하고 싶다면 DB 처리전문 툴이나,
RDBMS 를 사용하시길 바랍니다.

gdbm 은 내부적으로 DB를 전문적으로 처리하는 여러 함수로 구성된 라이브러리입
니다. GNU 'dbm'은 Philip A. Nelson 씨에 의해 쓰여졌습니다. 사실 GNU dbm은
다른 표준 UNIX dbm 인 'dbm' 이나 'ndbm'과 호환성을 유지하기 위해서 내부에 따
로 정의된 헤더파일과 라이브러리를 가지고 있습니다.

이 소프트웨어들은 하이텔 리눅스동자료실('gdbm173.tgz')이나 sunsite.unc.edu
의 "/pub/Linux/libs/db/" 에서 찾을 수 있습니다. 현재 쉽게 구할 수 있는 gdbm
의 버젼은 1.7.3 이며, 소스를 구하셔서 설치를 하시기 바랍니다. 물론, 공유라이
브러리와 헤더파일만으로 된 바이너리 배포판으로 구해서 설치해도 gdbm 을 사용하
는 데는 지장이 없지만 혹시라도 모를 'dbm' 이나 'ndbm' 과의 호환성을 유지하기
위한 라이브러리 및 헤더파일은 빠져 있으니 주의하시기 바랍니다. gdbm 은 1.7.1
의 메뉴얼을 내부에 info 파일형식으로 포함하고 있습니다. 그외에 버클리의 'db'
도 있긴 하지만, 버클리의 db 보다는 GPL을 따르고 있는 gdbm을 사용하시기를 권장
합니다.

'gdbm' 을 사용하면 어떤 것이 좋을까요?

1) 데이터 파일의 삭제, 갱신, 편집 등등의 관리에 직접 신경을 쓰지 않아도
됩니다. 이 작업은 gdbm 이 최적화된 상태를 유지하면서 에러상태와 여러
가지의 경우에 gdbm 이 내부적으로 대응하니 신경 쓸 필요는 없습니다.

2) 데이터 검색에 최선의 알고리즘을 사용하므로 속도면이나 퍼포먼스 면에서
상당한 효과를 가져올 수 있습니다. DB 를 일일이 하위 레이어까지 손으로
짤 경우에 발생하는 각종 고효율 알고리즘을 구현하기 위해서 머리를 썩일
필요성이 없다는 것입니다. 이 작업은 gdbm 이 최선의 효율성을 유지하면서
해쉬테이블을 통해 대신합니다.

3) DB 구축과 운용에 노력이 훨씬 적게 들어갑니다.
gdbm 에 적절한 데이터를 넘겨줌으로써 DB 구축은 끝이 나므로 나머지 노력
을 다른 곳에 쏟을 수 있습니다.
물론, RDBMS 의 장점인 복잡한 관계를 처리하거나 융통성을 발휘하지는 못
하지만 그만큼 사용이나 운용이 용이하다는 장점이 있습니다.

4) 'dbm', 'ndbm' 과의 호환성을 유지하는 루틴을 가질 수 있습니다.
gdbm 에서 제공하는 라이브러리를 사용하여 표준 UNIX 'dbm' 이나 'ndbm' 과
호환하는 프로그램을 짤 수 있습니다. 그리고 'dbm' 이나 'ndbm' 에서 사용
하던 데이터베이스 파일을 'gdbm' 이 사용하는 파일의 포맷으로 유틸리티를
사용하여 바꿀 수 있습니다. 물론, GNU 'dbm' 의 기능은 더욱더 향상된 기능
을 제공합니다.


+—————–+ request +—————–+
| GDBM function | ————-> | internal DB |
| | DB name, key | |
| gdbm_open | | db create |
| gdbm_fetch | result | hash table |
| ……… | <————- | searching … |
+—————–+ modified DB +—————–+
| return value | | |
+————————————-+ +——————-+
User area GDBM Internal area

[ GDBM 데이터 베이스 메니져의 개념 ]


결론적으로 말씀드리면, DB 구축이나 DB 내부에까지 프로그래머가 신경쓸 필요없
이 gdbm 라이브러리가 제공해주는 인터페이스 함수를 통해서 적절한 데이터만 넘
겨주면 된다는 이야기이므로 DB 프로그래밍에 아주 편리하게 사용할 수 있습니다.

단, 12개의 함수만 익히시면 여러분들의 DB 프로그래밍에 활력소가 될 것입니다.

본 강좌는 gdbm 의 info 메뉴얼에 바탕하면서, 예를 들어가면서 설명하도록 하겠
습니다.


2. 도입과 전개

gdbm 은 표준 UNIX dbm 의 함수와 유사하게 작동하는 데이터 베이스 함수모음집
입니다. 이 함수들을 사용하여 데이터베이스 파일들을 만들거나 처리할 수 있습
니다. gdbm 의 기본적으로 key/data 를 짝으로 데이터베이스 파일에 저장하여 처
리 합니다. key 는 중복되지 않는 유일한 값이어야 하며, 각각의 key는 단 하나의
데이터아이템과 결부지어져야 합니다. key는 곧장 순서대로 정리된 상태로 접근
할 수 없습니다. gdbm 의 기본적인 유닛은 다음의 구조체입니다.

typedef struct {
char *dptr;
int dsize;
} datum;

이 구조체는 크기가 제 각각인 key와 data 아이템을 허용합니다.
char * 형인 dptr 에 얼마던지 크고 복잡한 구조체라도 주소를 저장하여 값을 꺼
집에 낼때는 형변환하여 사용할 수 있습니다.

key/data 는 한짝으로 gdbm 의 디스크 파일에 저장이 됩니다. 이것은 gdbm 데이터
베이스라고 부릅니다. 하나의 응용 프로그램이 데이터베이스의 key와 data를 처리
하기위해서는 gdbm 데이터베이스를 열어야 합니다. 당연한 이야기겠지요..
또한, gdbm 에서는 하나의 어플리케이션이 동시에 여러 데이터베이스를 여는 것이
가능합니다. 하나의 어플리케이션이 한개의 gdbm 데이터베이스를 열 때, 그것은
'reader' 나 'writer'로 나타낼 수 있습니다. 보통 gdbm 데이터베이스는 한번에
하나의 writer에 의해 많이 개봉됩니다. 그러나, 많은 reader 들은 해당 데이터
베이스를 동시에 열 수 있습니다. reader 와 writer 는 동시에 gdbm 데이터베이스
를 열지는 못합니다.

gdbm 은 데이터베이스를 다루기위해서 다음의 함수들을 지원합니다. 함수이름과
인자들에서도 그 역할을 짐작할 수 있을 것입니다. 12개 정도의 함수셋이니 그리
많은 것은 아닐 겁니다. 이 함수들은 'gdbm.h'에 선언되어 있는데, 'gdbm.h'는
설치시 '/usr/include' 나 '/usr/local/include' 에 위치 할 것입니다.

#include

GDBM_FILE gdbm_open(name, block_size, flags, mode, fatal_func);
void gdbm_close(dbf);
int gdbm_store(dbf, key, content, flag);
datum gdbm_fetch(dbf, key);
int gdbm_delete(dbf, key);
datum gdbm_firstkey(dbf);
datum gdbm_nextkey(dbf, key);
int gdbm_reorganize(dbf);
void gdbm_sync(dbf);
int gdbm_exists(dbf, key);
char *gdbm_strerror(errno);
int gdbm_setopt(dbf, option, value, size)


3. 여러 함수들

1) 데이터 베이스 열기

여기에서는 gdbm 시스템을 초기화시키는 것을 설명합니다. 초기화 루틴실행 후에
0 바이트짜리 파일이 생겼다면 정상적으로 수행된 것입니다.
'gdbm' 파일을 개봉하는 프로시져는 다음과 같습니다.

GDBM_FILE dbf;
dbf = gdbm_open(name, block_size, flags, mode, fatal_func);

각각의 파라메터는 다음과 같습니다.

char *name
파일의 완전한 이름. gdbm 에서는 확장자 개념도 없으며 특별한 확장명을 뒤
에 붙이지도 않습니다.

int block_size
이것은 초기화 할 동안 다양한 생성을 위한 크기를 결정하기 위해 사용합니다.
실제적으로는 디스크에서 메모리로 한번에 전송하는 크기를 나타냅니다. 이전
에 그 파일이 초기화 되었다면 이 인자는 무시되며, 최소값은 512이며, 만일
이 인자의 값이 512보다 작다면 해당 파일 시스템의 블록사이즈가 사용되며,
다른 경우는 block_size 값이 사용됩니다.
따라서, 최대의 효율을 낼 수 있는 크기를 지정하면 좋을 것입니다.

int flags
flags 가 GDBM_READER 로 세트되어 있으면, 단지 데이터베이스 파일을 읽기만
을 할 수 있습니다. 따라서 'gdbm_store', 'gdbm_delete' 루틴을 호출하면 당
연히 실패하게 됩니다.
많은 reader 들은 또한, 동시에 한 데이터베이스에 접근할 수 있습니다.
flags 가 GDBM_WRITER 이면, 데이터베이스를 읽고 쓰기를 모두 할 수 있으며,
배타적인 접근이 필요합니다. (동시에 한파일에 쓰기를 시도할 경우, 어떤 불
상사가 일어나는 지는 추축에 맡기겠습니다. :))
flags 가 GDBM_WRCREAT 로 세트되어 있으면, 데이터베이스를 읽고 쓰기를 모두
할 수 있으며, 데이터베이스가 존재하지 않는다면, 새로운 것을 하나 만듭니
다.
flags 가 GDBM_NEWDB 로 세트되어 있으면, 데이터베이스가 기존에 있는지 없는
지에 상관하지 않고 새로운 것을 하나 만들며, 읽기 쓰기를 모두 할 수 있습
니다.
모든 writer (GDBM_WRITER, GDBM_WRCREAT, GDBM_NEWDB) 에는 GDBM_FAST 플래
그를 추가로 세팅할 수 있습니다. 이 옵션은 최대한의 지연쓰기를 함으로써
속도상의 잇점을 가져오는 역할을 합니다. 따라서, 이 옵션을 사용하면, 속
도상의 잇점은 가져올 수 있으나 writer 가 비정상적인 중단을 할 경우에는,
데이터가 일치하지 않을 수도 있습니다.
만일 에러가 발생했다면, NULL을 리턴값으로 돌려주고, 적당한 값이
gdbm_errno 에 세트가 될 것입니다. 에러가 없다면, gdbm 파일 디스크럽트
포인터가 리턴됩니다.

int mode
파일 모드. (chmod(2)와 open(2)에서 사용하는 파일모드)

void (*fatal_func) ()
치명적인 에러가 발생하였을 경우에 gdbm 이 호출할 함수입니다. 이 함수의
인자는 문자열만이 가능합니다. 그 값이 NULL이라면 gdbm 이 제공할 것이며,
gdbm 은 기본 함수를 사용합니다.


리턴값인, 'dbf' 는 gdbm 파일에 접근하는 다른 모든 함수들이 사용하는 포인터
입니다. 만일 리턴값이 NULL 이라면, gdbm_open 은 실패한 것이며, 에러는 gdbm
에러를 세트하는 gdbm_errno 와 파일 시스템의 에러가 표시되는 errno 에 세트됩
니다. (에러코드는 'gdbm.h'를 참조하세요.)

이후에서 설명하는 모든 호출들에 있어서 'dbf' 는 gdbm_open 이 리턴한 포인터를
참조합니다.

2) 데이터베이스 닫기

열었으면 닫는 것은 당연한 것이겠죠? 무더운 여름도 다가오는 데 냉장고에서 시원
한 수박을 한덩어리 꺼내고는 문을 안닫아놓으면, 다음에 시원한 음료수를 빼먹
을 수 없을 겁니다. 🙂

여러 라이브러리 함수들을 사용하시다 보면, 기본적으로 초기화부분(init 또는
open), 처리부분, 마무리부분(close 또는 free..)으로 되어 있는 경우가 많습니다.

gdbm 라이브러리에도 닫는 함수는 있습니다.

gdbm_close(dbf);

GDBM_FILE dbf
유일한 하나의 인자는 gdbm_open 에서 리턴된 포인터입니다.

gdbm_close 가 호출되면 gdbm 파일을 닫고 'dbf' 에 할당된 모든 메모를 자유롭
게 합니다.

3) 데이터베이스에 삽입 또는 대체

gdbm_store 는 데이터베이스에서 레코드를 삽입하거나 대체하는 역할을 합니다.

ret = gdbm_store(dbf, key, content, flag);

각각의 인자는 다음과 같습니다.

GDBM_FILE dbf
gdbm_open 이 리턴한 포인터

datum key
`key' 데이터

datum content
key 와 관련되는 data

int flag
이 플래그는 데이터베이스에서 해당 key 가 이미 있을 경우에 취할 행동을
정의하는 역할을 합니다.
GDBM_REPLACE 는 이미 있는 data를 새로운 'content' 로 대체합니다.
GDBM_INSERT 는 'key' 가 이미 있을 경우에 아무 행동도 하지 않고 에러를
리턴하도록 합니다.

리턴된 'ret' 의 값은 다음과 같습니다.

-1
호출자가 공식적인 writer 가 아니거나, 'key' 나 'content' 에 NULL 필드를
지정하여 데이터베이스에 아이템을 저장하지 못한 경우입니다.
'key' 와 'content' 둘다 NULL 값이 아닌 데이터 포인터(dptr) 필드를 가져
야 합니다. 다른 함수가 NULL 데이터포인터(dptr) 필드를 에러를 검사하기
위해 사용하였다면, NULL 필드는 유용한 데이터가 될 수 없습니다.

+1
인자 'flag' 가 GDBM_INSERT 이고 'key' 가 이미 데이터베이스에 있어서 아이
템이 저장되지 못한 경우입니다.

0
성공적일 경우. 'content' 는 'key' 와 연관되었으며, 디스크상의 파일이 업
데이트되어서 새로운 데이터베이스의 구조는 이 함수가 리턴하기 전부터 영향
을 미칩니다.

만일, 데이터베이스에 이미 있는 'key' 에 해당된 데이터를 저장한다면, gdbm은
GDBM_REPLACE 가 세트되어 있는 경우에, 오래된 데이터를 새로운 데이터로 대
체할 것입니다.
아울러, 하나의 같은 key 값에 두개의 데이터 아이템을 지정할 수 없으며, 이럴
경우에 gdbm_store 는 에러를 발생하지 않습니다.

gdbm 의 크기는 'dbm' 이나 'ndbm' 과 같이 제한되지는 않습니다. 여러분들의
데이터는 원하는 만큼 크게 만들 수 있습니다.


4) 데이터 베이스에서 레코드를 검색하기

데이터베이스를 검색하는 함수는 gdbm_fetch 함수입니다. 이 함수는 주어진
키값으로 이와 관련된 정보를 리턴합니다. 리턴된 구조체의 포인터는 동적으로
할당된 메모리 블록을 가르키는 포인터입니다.

content = gdbm_fetch(dbf, key);

각각의 인자는 다음과 같습니다.

GDBM_FILE dbf
gdbm_open 에서 리턴된 포인터

datum key
key data

'content' 에서 리턴된 datum 은 발견된 데이터에 대한 포인터 입니다. 만일 데이
터 포인터(dptr)가 NULL 이라면 데이터를 발견하지 못한 것입니다. 데이터포인터
(dptr) 가 NULL 이 아니라면 dptr 은 할당된 데이터를 가르킵니다. 'gdbm' 은
자동적으로 이 데이터를 메모리 헤제 하지는 않습니다. 여러분들은 이것을 다 사
용하고 나서 자유롭게 만들어야 합니다. 이러한 장점은 나중에 다시 사용하기 위
해서 이 결과를 복사할 필요가 없다는 이야기입니다. 포인터만 저장하면 나중에
다시 사용할 수 있겠지요.

gdbm_fetch 와는 조금 다르게 간단하게 해당하는 key 에 대응하는 데이터가 있는
지를 간단하게 살표볼 수도 있습니다.

ret = gdbm_exists(dbf, key);

해당 인자는 다음과 같습니다.

GDBM_FILE dbf
gdbm_open 에서 리턴된 포인터

datum key
'key' data

이 루틴은 어떠한 메모리도 할당하지 않으며, 단지 해당하는 'key' 가 존재하는
지 하지 않는 지에 따라 true 나 faluse 를 리턴합니다.

5) 데이터베이스에서 레코드 제거하기

데이터베이스에서 레코드를 제거하기 위해서는 다음과 같이 하면 됩니다.

ret = gdbm_delete(dbf, key);

각각의 인자는 다음과 같습니다.

GDBM_FILE dbf
gdbm_open 에서 리턴된 포인터

datum key
'key' 데이터

아이템이 없거나 요청자가 reader 라면 ret 값은 -1 입니다. 성공적으로 제거했
다면 0을 리턴합니다.

gdbm_delete 는 key 에 관련된 아이템과 key 를 데이터베이스 'dbf' 에서 제거를
합니다. 디스크상의 파일은 업데이트되고 새로운 데이터베이스의 구조는 이 함수
가 리턴하기전 부터 영향을 미칩니다.

6) 레코드에 연속적으로 접근하기

다음의 두개의 함수는 데이터베이스에서 모든 아이템을 접근할 수 있습니다.
이러한 접근은 'key' 값이 연속되지 않습니다. 그러나 데이터베이스에서 모든
'key' 를 한번 방문한다는 것은 보증합니다. 방문하는 순서는 해쉬 값과 관련
이 있습니다. gdbm_firstkey 는 데이터베이스에서 모든 키를 방문하기 시작하
는 역할을 합니다. gdbm_nextkey 는 'dbf' 의 해쉬 구조에서 다음 엔트리를 발
견하고 읽어들입니다.

key = gdbm_firstkey(dbf);

nextkey = gdbm_nextkey(dbf, key);

각각의 인자는 다음과 같습니다.

GDBM_FILE dbf
gdbm_open 에서 리턴된 포인터

datum `key'
datum nextkey
'key' data

리턴값은 둘다 datum 형입니다. 만일 key.dptr 이나 nextkey.dptr 이 NULL이라면
, 첫번째 'key' 나 다음 'key' 가 없을 경우입니다. dptr 포인터는 malloc 에
의해 할당된 데이터에 대한 포인터이며 gdbm 은 그것을 알아서 free 하지 않는
다는 것을 다시 한번 유의하세요.

이 함수들은 데이터베이스를 읽기모드 알고리즘으로 방문한다고 합니다.

파일 '방문' 은 해쉬테이블에 기초합니다. gdbm_delete 는 해쉬 테이블을 재정
렬합니다. 원래 key 의 순서는 모든 경우에 있어서 변경되지 않은 채로 남아있
다는 것을 보증하지는 못합니다. 만약 루프가 다음과 같이 실행된다면 어떤 몇
몇 key 는 방문하지 않게 만들 수도 있습니다.

key = gdbm_firstkey ( dbf );
while ( key.dptr ) {
nextkey = gdbm_nextkey ( dbf, key );
if ( some condition ) {
gdbm_delete ( dbf, key );
free ( key.dptr );
}
key = nextkey;
}

7) 데이터베이스의 동시성

데이터베이스를 처음에 GDBM_FAST 플래그로 열었다면, gdbm 은 변경된 내용이
디스크로 완전하게 쓰여지기를 기다리지 않습니다. 이를 경우 어플리케이션이
비정상적인 중단이 되었을 때 데이터베이스가 변조될 위험성을 가지고 있습니
다. 다음의 함수를 사용한다면 현재에 변화된 모든 것들을 해당 데이터베이스
파일로 쓰기를 할 수 있습니다.

gdbm_sync(dbf);

쉘명령에서 'sync'로 지연쓰기 된 것을 디스크로 쓰는 것과 비슷합니다.

GDBM_FILE dbf
gdbm_open에서 리턴된 포인터

이 함수는 보통 많은 변화가 있고 난 다음에, 오랜시간동안 wait 를 하기 이전에
호출합니다.
gdbm_close 는 자동적으로 'gdbm_sync' 를 호출한것과 같은 효과를 가지며, 데
이터베이스에서 변화가 있고난 다음에 즉각적으로 닫혔다면 더이상의 호출은 필
요없습니다.

8) 에러 메시지

gdbm 의 에러코드를 에러 문자열로 변환하기 위해서는 다음 루틴을 사용해야 합
니다.

ret = gdbm_strerror(errno)

gdbm_error errno
gdbm 에러코드. 보통 gdbm_errno 입니다.

이 함수는 사용자가 읽을 수 있는 문자열을 반환합니다.

9) 옵션 세팅하기

gdbm 은 현재 열려져 있는 데이터베이스상에서 어떤 옵션을 새로이 설정할 수
있는 능력을 제공합니다.

ret = gdbm_setopt(dbf, option, value, size)

각각의 인자의 의미는 다음과 같습니다.

GDBM_FILE dbf
gdbm_open 에서 리턴된 포인터

int option
세트될 옵션

int *value
'option' 이 세트될 값에 대한 포인터

int size
'value' 에 의해 포인터되는 데이터의 길이

유효한 옵션은 다음과 같습니다.

GDBM_CACHESIZE – 내부 버킷 캐쉬 사이즈를 설정합니다. 이 옵션은 GDBM_FILE
디스크럽트당 한번씩만 설정될 수 있습니다. 그리고 데이터
베이스를 처음 접근한 이후로는 100으로 자동으로 세팅됩니
다.

GDBM_FASTMODE – fast mode 를 켜거나 끕니다. 이 옵션을 사용하면 이미 열려
서 활성화된 데이터베이스에서 fast mode 를 토글할 수 있습
니다.
value 는 TRUE 나 FALSE 로 세팅합니다.

함수의 수행이 실패되었을 때에는 -1 이 리턴되며, 성공적일 경우에는 0을 리턴
합니다. 실패했을 경우 전역변수인 gdbm_errno 가 적절하게 세팅됩니다.

예를 들면, gdbm_open 을 사용하여 이미 열려져 있는 데이터 베이스를, 이전에는
어떻게 접근되었던지간에, 케쉬 10을 사용하여 세팅한다면, 다음과 같은 코드가
될것입니다.

int value = 10;
ret = gdbm_setopt(dbf, GDBM_CACHESIZE, &value, sizeof(int));


이제 gdbm 의 12개 함수를 다 알아보았습니다.

다음으로 그외의 간단한 몇가지를 알아보겠습니다.

(다음 장으로 넘기겠습니다.)





1) 두가지 유용한 변수

* gdbm_error gdbm_errno

이 변수는 gdbm 에서의 에러에 대한 정보를 가지고 있습니다.
gdbm.h 에 이러한 에러 값들에 대한 정의가 들어 있습니다.

* const char * gdbm_version

버젼 정보를 가지고 있는 문자열입니다.

2) UNIX 표준 dbm 과 ndbm 과의 호환성

GNU 'dbm' (gdbm) 파일은 엉성하지 않습니다. 여러분들은 UNIX 'cp' 명령을 사용
하여 복사할 수 있으며, 또한 복사하는 프로세스가 확장하지도 않습니다.

gdbm 파일은 UNIX 'dbm' 과 'ndbm' 을 사용하는 프로그램에 있어서 호환모드가
있습니다.

GNU 'dbm' 은 'dbm' 과 호환되는 함수를 가지고 있습니다. 'dbm' 과의 호환성을
위해서는, 여러분들은 'dbm.h'를 포함해야 합니다.

호환 모드에 있어서는, gdbm 파일 포인터가 사용자에게 필요치 않으며, 한번
에 단 하나의 파일만이 열릴 수 있습니다. 호환모드에 있어서 모든 사용자들은
writer 로 간주됩니다. gdbm 파일이 읽기모드라면, 그것은 writer 로서 실패할
것이나, reader 로서 그것을 열기를 시도할 것입니다. 데이터를 가르키는 datum
구조제에서 리턴된 모든 포인터는 gdbm 이 free를 할 것입니다. 그 포인터들은
static 포인터로 취급되어야 합니다. 표준 UNIX 'dbm' 이 그런것 처럼 말입니다.
호환되는 함수의 이름은 UNIX dbm 함수의 이름과 같습니다. 아래에서 보이는 것
처럼….

int dbminit(name);
int store(key, content);
datum fetch(key);
int delete(key);
datum firstkey();
datum nextkey(key);
int dbmclose();

표준 UNIX 의 'dbm' 과 GNU 'dbm' 은 파일의 데이터 구조가 다릅니다.
* 여러분들은 표준 UNIX 'dbm' 파일을 GNU 'dbm' 으로 접근할 수 없습니다.*
만일 여러분들이 오래된 데이터베이스를 GNU 'dbm' 으로 사용하려면, 이전에
'conv2gdbm' 프로그램을 사용해야 합니다. ('conv2gdbm' 프로그램은 gdbm 소스
배포본에 포함되어 있습니다.)

또한, GNU 'dbm' 은 'ndbm' 과 호환되는 함수를 가지고 있습니다. ndbm 호환성
함수를 사용하려면, 헤더파일 'ndbm.h' 를 포함해야 합니다.

'ndbm' 과 마찬가지로, 모든 리턴된 datum 은 static 스토로지로 간주됩니다.
따라서 여러분들은 메모리를 free 할 필요는 없으며, 'ndbm' 와 호환되는 함수
들이 그러한 일을 대신할 것입니다.

그 함수들은 다음과 같습니다.

DBM *dbm_open(name, flags, mode);
void dbm_close(file);
datum dbm_fetch(file, key);
int dbm_store(file, key, `content', flags);
int dbm_delete(file, key);
datum dbm_firstkey(file);
datum dbm_nextkey(file);
int dbm_error(file);
int dbm_clearerr(file);
int dbm_dirfno(file);
int dbm_pagfno(file);
int dbm_rdonly(file);

만일, UNIX 'dbm' 이나 'ndbm' 을 사용한 오래된 C 프로그램을 컴파일해서
'gdbm' 파일을 사용하기를 원한다면, 다음의 'cc' 명령어를 실행하세요.

cc … -L /usr/local/lib -lgdbm

'-L/usr/local/lib' 는 여러분들이 'gdbm' 라이브러리를 가지고 있는 장소에
따라 다를 수 있습니다.

3) 'dbm' 파일을 'gdbm' 파일 포맷으로 변환하기

'conv2gdbm' 프로그램은 'dbm' 데이터베이스를 'gdbm' 으로 변환합니다.
사용법은 다음과 같습니다.

conv2gdbm [-q] [-b block_size] dbm_file [gdbm_file]

옵션은 다음과 같습니다.

-q
conv2gdbm 은 조용하게 작업을 수행하도록 합니다.

block_size
gdbm_open 의 block_size 와 같습니다.

dbm_file
'.pag' 나 '.dir' 확장자를 달지 않는 'dbm' 파일의 이름

gdbm_file
완전한 gdbm 파일의 이름입니다. 만일 지정하지 않았다면, gdbm 파일의 이름은
dbm 파일이름에서 확장자를 뺀것을 기본으로 취합니다.

conv2gdbm dbmfile

이러한 사용예는 'dbmfile.pag' 나 'dbmfile.dir' 을 'gdbm' 파일인 'dbmfile'
로 변환합니다.

4) 버그 리포팅

혹시 GNU 'dbm' 에 버그가 있다면, 신중하게 진짜 버그인지를 확인하시고, 버그
리포팅에 관련된 문서를 읽어보시고 아래의 주소로 연락을 주십시요.

Internet: `bug-gnu-utils@prep.ai.mit.edu'.
UUCP: `mit-eddie!prep.ai.mit.edu!bug-gnu-utils'.

혹시, 제의나 질문, 본래의 영문 info 파일에 버그가 있다던지 해서 제작자와
접촉하시려면, 아래로 연락하십시요.

e-mail: phil@cs.wwu.edu
us-mail: Philip A. Nelson
Computer Science Department
Western Washington University
Bellingham, WA 98226

또한, 메인테이너와 연락하시려면 아래로 연락하십시요.

e-mail: downsj@CSOS.ORST.EDU

5. 프로그램을 한번 짜봅시다.

gdbm 의 소스 배포본 배부에는 테스트 프로그램이 'gdbm' , 'dbm' , 'ndbm' 별로
하나씩 들어 있습니다. 이것을 참조하시면 도움이 되실 겁니다.
그 테스트 프로그램에서는 문자열로 된 키하나와 역시 문자열로 된 데이터 하나만
을 다루고 있지만 이것은 더욱 더 확대가능합니다.
앞서도 말씀드렸지만, gdbm 의 가장 기본적인 데이터형은 다음과 같습니다.

typedef struct {
char *dptr;
int dsize;
} datum;

이 구조체는 key 와 data 가 다같이 사용하는 구조체이므로 key 와 data 의 기본
구조는 같습니다. 다만 dptr에 어떤 주소를 저장하느냐에 따라서 용도가 달라지
므로 항상 구별하시기 바랍니다.

dptr 이 char * 이긴 하지만 얼마든지 복잡하고 덩치 큰 구조체라도 그 주소를
저장 할 수 있습니다. 가령, 여러가지의 데이터 형이 들어 있는 SOME 라는 구조
체가 있다면,

typedef struct something {
int a;
char b;
…..
} SOME;

datum data_data;
SOME mine;
data_data.dptr = &mine;
data_data.dsize = sizeof(SOME);

/* DB open and some DB handle functions … */

printf("%d\n", ((CARD *)(data_data.dptr))->a);

이런 식으로 사용할 수 있습니다. 즉, datum 이 dptr 은 char * 이지만 dptr 만
이후에 형변환만 해주면 얼마든지 다른 구조체도 사용할 수 있다는 이야기입니다.

gdbm 을 사용하여 프로그램을 짜면, 외부 인터페이스만 이용하게 되므로, 소스의
용량은 상당히 줄어들게 됩니다. 다만 datum 형 변수들이 어떻게 사용하였는지
만 잘 살펴나가면 별 무리가 없습니다.

자, 그럼 이제 프로그램을 한번 짜볼까요?

gdbm 을 사용해서 프로그램을 짤 때에는 key 가 유일한 값이 되어야 하기 때문에
사전에 어떤 값을 key 로 사용할 것인지 생각하셔야 합니다. 'key' 값은 어떤
형태가 되어야 한다는 제한은 없지만, 가령 '이름' 과 같은 경우에는 얼마든지
같아질 수 있으므로 피해야 합니다. 따라서, 예제 프로그램에서는 '일련번호'를
key 로 사용하겠습니다.
data 는, '이름' 과 '이메일주소', '회사명', '전화번호', '삐삐번호' 를 가진
CARD 라는 데이터 형을 dptr로 지정을 해서 사용하겠습니다.

프로그램은 간단한 명함관리 프로그램으로, 간단한 입력, 검색, 삭제, 출력 만
을 지원하는 간단한 기능만 갖추어져 있습니다. 검색 부분을 조금 추가하시면,
키 값만이 아니라 각 CARD 의 멤버로 검색도 가능하게 만들 수 있습니다. 그외
의 기능은 여러분들이 한번 추가해보세요.

그럼, 프로그램을 살펴봅시다.

—————————————————————————–

/* gdbm 테스트 프로그램 : 명함관리
*
* Designed by Han-donghun, 1997.5.30
*
* name : name.c
*
* Compile : gcc -o name name.c -lgdbm
* Usage : name [database]
*
* The default database is 'name.gdbm'.
*
* This is simple namecard manager program,
* using gdbm.
*
*/

#include
#include
#include
#include /* gdbm main header file*/

#define NAME_SIZE 40
#define EMAIL_SIZE 40
#define CORP_SIZE 40
#define TEL_SIZE 20
#define PAGER_SIZE 20

typedef struct _card {
char name[NAME_SIZE];
char email[EMAIL_SIZE];
char corp[CORP_SIZE];
char tel[TEL_SIZE];
char pager[PAGER_SIZE];
} CARD;

CARD card;

/* key is 8 unique number */
/* data is structured CARD */

void main(int argc, char *argv[]) {
char *my_db = "name.gdbm";
char cmd_ch;
int done = 0;
int cache_size = 100;
GDBM_FILE dbf;
datum key_data;
datum data_data;
datum return_data;
char key_line[512];
char data_line[512];

/* Initialize variables. */
key_data.dptr = NULL;
data_data.dptr = NULL;

if (argc >= 2) {
my_db = argv[1];
}

dbf = gdbm_open(my_db, 2048, GDBM_WRCREAT, 00664, NULL);

if (dbf == NULL) {
printf("gdbm_open failed, %s\n", gdbm_strerror(gdbm_errno));
exit(1);
}
if (gdbm_setopt(dbf, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
{
printf("gdbm_setopt failed, %s\n", gdbm_strerror(gdbm_errno));
exit(1);
}


/* Welcome message. */
printf("\nWelcome to the gdbm test program. Type ? for help.\n\n");
while (!done) {
printf("명 령 => ");
cmd_ch = getchar();
if (cmd_ch != '\n') {
char temp;
do
temp = getchar();
while (temp != '\n' && temp != EOF);
}
if (cmd_ch == EOF) cmd_ch = 'q';
switch (cmd_ch) {
case '\n':
printf("\n");
break;

case 'c': /* 명함 갯수 */
{
int temp;
temp = 0;
if (key_data.dptr != NULL) free (key_data.dptr);
return_data = gdbm_firstkey (dbf);
while (return_data.dptr != NULL) {
temp++;
key_data = return_data;
return_data = gdbm_nextkey (dbf, key_data);
free (key_data.dptr);
}
printf (" %d 개의 명함이 있습니다.\n\n", temp);
key_data.dptr = NULL;
}
break;

case 'i': /* 명함 입력 */
if (key_data.dptr != NULL) free (key_data.dptr);

printf("키 -> ");
gets (key_line);
key_data.dptr = key_line;
key_data.dsize = strlen (key_line) + 1;
printf("이 름 -> ");
gets(data_line);
strncpy(card.name, data_line, NAME_SIZE-1);
printf("이메일 -> ");
gets(data_line);
strncpy(card.email, data_line, EMAIL_SIZE-1);
printf("회 사 -> ");
gets(data_line);
strncpy(card.corp, data_line, CORP_SIZE);
printf("전 화 -> ");
gets(data_line);
strncpy(card.tel, data_line, TEL_SIZE);
printf("삐 삐 -> ");
gets(data_line);
strncpy(card.pager, data_line, PAGER_SIZE);

data_data.dptr = (char *)&card;
data_data.dsize = sizeof(CARD);
if (gdbm_store (dbf, key_data, data_data, GDBM_REPLACE) != 0)
printf ("명함을 찾을 수 없습니다.\n");
printf ("\n");
key_data.dptr = NULL;
break;

case 'd': /* 명함 삭제 */
if (key_data.dptr != NULL) free (key_data.dptr);
printf ("키 -> ");
gets (key_line);
key_data.dptr = key_line;
key_data.dsize = strlen (key_line)+1;
if (gdbm_delete (dbf, key_data) != 0)
printf ("해당 데이터가 없거나 지우지 못했습니다.\n");
printf ("\n");
key_data.dptr = NULL;
break;

case 's': /* 명함 검색 */
if (key_data.dptr != NULL) free (key_data.dptr);
printf("키 -> ");
gets(key_line);
key_data.dptr = key_line;
key_data.dsize = strlen (key_line)+1;
return_data = gdbm_fetch (dbf, key_data);
if (return_data.dptr != NULL) {
printf("\n");
printf("키 : %s\n", key_line);
printf("이 름 : %s\n", ((CARD *)(return_data.dptr))->name);
printf("이메일 : %s\n", ((CARD *)(return_data.dptr))->email);
printf("회 사 : %s\n", ((CARD *)(return_data.dptr))->corp);
printf("전 화 : %s\n", ((CARD *)(return_data.dptr))->tel);
printf("삐 삐 : %s\n", ((CARD *)(return_data.dptr))->pager);
printf("\n");
free (return_data.dptr);
}
else
printf ("명함이 없습니다.\n\n");
key_data.dptr = NULL;
break;

case 'l': /* 명함 목록 */
if (key_data.dptr != NULL) free(key_data.dptr);
key_data = gdbm_firstkey(dbf);
while (key_data.dptr != NULL){
printf("\n");
return_data = gdbm_fetch(dbf, key_data);
printf("키 : %s\n", key_data.dptr);
printf("이 름: %s\n", ((CARD *)(return_data.dptr))->name);
printf("이메일: %s\n", ((CARD *)(return_data.dptr))->email);
printf("회 사: %s\n", ((CARD *)(return_data.dptr))->corp);
printf("전 화: %s\n", ((CARD *)(return_data.dptr))->tel);
printf("삐 삐: %s\n", ((CARD *)(return_data.dptr))->pager);
free(return_data.dptr);
return_data = gdbm_nextkey(dbf, key_data);
free(key_data.dptr);
key_data = return_data;
}
printf("\n");
break;
case 'q': /* 종료 */
done = 1;
break;

case '?': /* 도움말 */
case 'h': /* 도움말 */
printf("\n");
printf("c – 명함 갯수\n");
printf("d – 명함 삭제\n");
printf("s – 명함 검색\n");
printf("i – 명함 입력\n");
printf("l – 명함 전체목록\n");
printf("q – 종 료\n");
printf("\n");
break;

default:
printf("틀린 명령입니다.\n\n");
break;
}
} /* while (!done) */
gdbm_close(dbf);
}

—————————————————————————-

200 라인도 채 되지 않는 간단한 프로그램입니다.

다음은 테스트 내용입니다.

—————————————————————————-

queen:~/gdbm$ name test.gdbm

Welcome to the gdbm test program. Type ? for help.

명 령 => ?

c – 명함 갯수
d – 명함 삭제
s – 명함 검색
i – 명함 입력
l – 명함 전체목록
q – 종 료

명 령 => i
키 -> 1
이 름 -> 리눅서
이메일 -> linuxer@linux.or.kr
회 사 -> FREE MAN
전 화 -> 0000-1111-2222
삐 삐 -> 012-3333-4444

명 령 => i
키 -> 35
이 름 -> 또치
이메일 -> ddoch@hitel.kol.co.kr
회 사 -> 둘리 영화 출연 중
전 화 -> 0000-1111-5555
삐 삐 -> 012-9999-8888

명 령 => c
2 개의 명함이 있습니다.

명 령 => l

키 : 1
이 름: 리눅서
이메일: linuxer@linux.or.kr
회 사: FREE MAN
전 화: 0000-1111-2222
삐 삐: 012-3333-4444

키 : 35
이 름: 또치
이메일: ddoch@hitel.kol.co.kr
회 사: 둘리 영화 출연 중
전 화: 0000-1111-5555
삐 삐: 012-9999-8888

명 령 => d
키 -> 35

명 령 => l

키 : 1
이 름: 리눅서
이메일: linuxer@linux.or.kr
회 사: FREE MAN
전 화: 0000-1111-2222
삐 삐: 012-3333-4444

명 령 => c
1 개의 명함이 있습니다.

명 령 => q
queen:~/gdbm$ ls -l test.gdbm
-rw-r–r– 1 ddoch users 6469 May 30 21:32 test.gdbm
queen:~/gdbm$



사실, gdbm 은 대형 DB 구축에 사용하기에는 조금 부족한 점이 보입니다.
key 와 data 의 연관성이 부족한 면이 보이며, key 값이나 data 의 각각의 항목
에 따른 순차적인 정렬의 문제에 있어서도 생각해 봐야 할 점이 있다고 봅니다.

하지만, 그외의 다양한 장점이 있으므로 충분히 활용할 만 하실 겁니다.
DB 에 SQL 을 사용하는 postgres 와 같은 외부 인터페이스를 사용하는 것도 하나
의 방법이 되겠지요

GNU gdbm (GNU DataBase Manager) 프로그래밍 강좌

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다