И.А. Волкова, И.Г. Головин, М.Г. Мальковский - Модельный SQL-интерпретатор (1119418), страница 3
Текст из файла (страница 3)
u_short sin_port; /* номер порта */
struct in_addr sin_addr; /* сетевой номер ЭВМ */
char sin_zero[8];
};
struct in_addr {
u_long s_addr;
};
Типы u_short (unsigned short) и u_long ( unsigned long) определены во включаемом файле <sys/types.h>. Номера портов и сетевые номера компьютеров должны указываться в сетевом байтовом порядке.
Пример-оболочка программы “Клиент” для сети ЭВМ
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
extern int errno;
void main ()
{ char c;
int s;
FILE *fp;
char hostname[64];
struct hostent *hp;
struct sockaddr_in sin;
/* в этом примере клиент и сервер выполняются на одном компьютере, но программа легко обобщается на случай разных компьютеров. Для этого можно, например, использовать хост-имя не собственного компьютера, как ниже, а имя компьютера, на котором выполняется процесс-сервер */
/* прежде всего получаем hostname собственной ЭВМ: */
gethostname (hostname, sizeof (hostname));
/* затем определяем сетевой номер своей машины: */
if ((hp = gethostbyname (hostname)) == NULL){
fprintf (stderr, “%s: unknown host.\n”, hostname);
exit (1);
}
/* получаем свой сокет-дескриптор: */
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) { perror (“client: socket”); exit (1);
}
/* создаем адрес, по которому будем связываться с сервером: */
sin.sin_family = AF_INET;
sin.sin_port = htons (1234);
/* копируем сетевой номер: */
bcopy (hp -> h_addr, &sin.sin_addr, hp -> h_length);
/* пытаемся связаться с сервером: */
if ( connect ( s, &sin, sizeof (sin)) < 0 ) {
perror (“client: connect”); exit (1);
}
/*--------------------------------------------- */
/* читаем сообщения сервера */
fp = fdopen (s, “r”);
c = fgetc (fp);
/* обрабатываем информацию от сервера
...................................
*/
/* посылаем ответ серверу */
send (s, “client”, 7, 0);
/* продолжаем диалог с сервером, пока в этом есть
необходимость
............................
*/
/* завершаем сеанс работы */
close (s);
exit (0);
}
Пример-оболочка программы “Сервер” для сети ЭВМ
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
extern int errno;
void main (){
char c;
int d, d1, fromlen;
FILE *fp;
char hostname[64];
struct hostent *hp;
struct sockaddr_in sin, fromsin;
/* получаем хост-имя собственной ЭВМ: */
gethostname (hostname, sizeof (hostname));
/* определяем сетевой номер своей машины: */
if ((hp = gethostbyname (hostname)) == NULL) {
fprintf (stderr, “%s: unknown host.\n”, hostname);
exit (1);
}
/* получаем свой сокет-дескриптор: */
if ((d = socket (AF_INET, SOCK_STREAM, 0)) < 0) { perror (“client: socket”); exit (1);
}
/* создаем адрес, который свяжется с сокетом: */
sin.sin_family = AF_INET;
sin.sin_port = htons (1234);
/* копируем сетевой номер: */
bcopy (hp->h_addr, &sin.sin_addr, hp->h_length);
/* связываем адрес с сокетом */
if ( bind ( d, &sin, sizeof (sin)) < 0 ){
perror (“server: bind”); exit (1);
}
/* слушаем запросы на сокет */
if ( listen ( d, 5) < 0 ) {
perror (“server: listen”); exit (1);
}
/* связываемся с клиентом через неименованный сокет с дескриптором d1;
после установления связи адрес клиента содержится
в структуре fromsin
*/
if (( d1 = accept ( d, &fromsin, &fromlen)) < 0 ) { perror (“server: accept”); exit (1);
}
/* ----------------------------------------- */
/* пишем клиенту: */
send (d1, “server”, 7, 0);
/* читаем запрос клиента */
fp = fdopen (d1, “r”);
c = fgetc (fp);
/* ................................ */
/* обрабатываем запрос клиента,
посылаем ответ и т.д.
...........................
*/
/* завершаем сеанс работы */
close (d);
exit (0);
}
2. БД и СУБД
Обычно, говоря о базе данных, подразумевают некоторую систему базы данных, имеющую в своем составе следующие основные компоненты: непосредственно базу данных (БД), систему управления базой данных (СУБД) и язык данных (язык общения пользователя с БД, в нашем случае это модельный язык SQL).
Собственно БД - совокупность данных, хранящихся во внешней памяти. В системе базы данных обычно выделяют три основных уровня хранения информации: внутренний, концептуальный и внешний. Внутренний уровень наиболее близок к физической памяти, т.е. связан со способом фактического хранения информации. Внешний уровень наиболее близок к пользователям, т.е. связан с тем, как отдельные пользователи представляют себе эти данные. Концептуальный уровень - промежуточный уровень между двумя другими - есть представление полного информационного содержания базы данных в несколько абстрактной форме по сравнению со способом физического хранения данных.
СУБД - программа, которая управляет всем доступом к базе данных.В общих чертах это происходит следующим образом:
- пользователь выдает запрос на доступ к БД, используя конкретный язык данных;
- СУБД принимает запрос, анализирует и интерпретирует его, обследуя по очереди внешнее представление, отображение “внешний - концепту-альный”, концептуальное представление, отображение “концептуальный - внутренний” и структуру хранения;
- СУБД выполняет необходимые операции над хранимой БД;
- СУБД выдает ответ пользователю, сначала выбирая все требуемые экземпляры хранимых записей, затем получая экземпляры концептуальных записей и уже потом формируя требуемый экземпляр внешней записи.
Ядром любой системы данных является модель данных, которая в частности определяет выбор языка данных. Имеются три широкоизвестные модели данных: реляционная, иерархическая и сетевая [ 1 ].
В данной разработке для хранения информации предлагается создать реляционную БД, состоящую из нескольких (2 -5) таблиц.
На логическом (концептуальном) и внешнем уровнях каждая таблица представляет собой совокупность записей. Все записи одной таблицы имеют одинаковую структуру. Каждая запись состоит из нескольких полей.
С каждым полем связываются следующие характеристики: имя поля, тип поля, длина поля и значение поля.
Имена, типы, и длины полей задаются при определении структуры таблицы.
На физическом (внутреннем) уровне таблицы предлагается реализовать с помощью готовой библиотеки функций для работы с файлами данных. Файл с таблицей помимо данных (записей) содержит описание структуры таблицы, определяющей поля данной таблицы. Эта структура загружается в оперативную память при открытии соответствующей таблицы и используется при всех операциях, производимых над таблицами.
Для работы с таблицами реализованы процедуры, позволяющие:
- создавать новые таблицы,
- удалять существующие таблицы,
- открывать и закрывать существующие таблицы,
- перемещаться по записям таблицы,
- редактировать отдельные записи таблицы,
- добавлять новые записи в таблицу,
- удалять существующие записи из таблицы.
Операции работы с таблицами реализованы через операции базового (io.h) ввода-вывода:
- создание или открытие файла (open),
- закрытие файла (close),
- удаление файла (unlink),
- чтение из файла (read),
- запись в файл (write),
- установка на определенную позицию в файле (lseek).
Каждая таблица описывается одним файлом данных. Этот файл состоит из двух частей - заголовка, в котором описывается структура таблицы, и собственно записей. Заголовок содержит информацию о числе полей и для каждого поля - его тип (строковый или длинный целый). Для строковых полей, кроме того, хранится их длина. Заголовок файла представляется в памяти структурой struct TableStruct, описанной ниже.
Все операции с таблицами (кроме создания и удаления таблицы) происходят через дескриптор таблицы типа THandle, получаемый в результате вызова функции открытия таблицы. Таким образом, гарантируется, что пользователь-программист не может работать с таблицами иначе, чем через явно описанный ниже интерфейс. Тип THandle представляет собой указатель на структуру struct Table:
typedef struct Table* THandle;
Эта структура является “скрытой” от пользователя БД и содержит информацию, необходимую для перевода операций над таблицами в операции базового ввода/вывода. С точки зрения пользователя работа с таблицами ведется только через указатель типа THandle и соответствующие функции.
Ниже приводится перечень структур данных и прототипов функций работы с таблицами (файл table.h), которые необходимо использовать для выполнения задания.
#include <stdio.h>
#include <stdlib.h>
#if !defined(_Table_h_)
#define _Table_h
#define MaxFieldNameLen 50 /* Максимальное число символов в названии поля */
#define MaxTableNameLen 12 /* Максимальное число символов в имени таблицы */
#define MaxNumFields 100 /* Максимальное количество полей в записи таблицы */
enum Errors /* Определяет возможные коды ошибок при работе с таблицами */
{
OK, /* Ошибок нет */
CantCreateTable,
CantOpenTable,
FieldNotFound,
BadHandle,
.................
};
/* Возможные сообщения об ошибках */
char* ErrorText[]=
{ "Success.",
"Can't create table.",
"Can't open table.",
"Field not found.",
"Bad table handle",
..................
};
/* Каждой таблице, с которой работает пользователь, должен соответствовать дескриптор типа THandle, получаемый от процедуры открытия таблицы. Все операции работы с таблицами используют этот дескриптор для ссылок на конкретную таблицу.*/
typedef struct Table * THandle;
typedef unsigned Bool; /* Булевский тип */
#define TRUE 1
#define FALSE 0
/* Следующий тип определяет возможные типы данных, хранимых в таблицах */
enum FieldType
{
Text, /* строка ( не более 256 символов) */
Long, /* целое длинное знаковое число */
};
/* Эта структура задает описание одного поля таблицы */
struct FieldDef
{
char name[MaxFieldNameLen]; /* имя данного поля */
enum FieldType type; /* тип поля */
unsigned len; /* длина поля в байтах */
};
/* Следующая структура определяет структуру таблицы и используется при создании новой таблицы. Информация об этой структуре хранится в табличном файле */
struct TableStruct
{
unsigned numOfFields; /* число полей в таблице */
struct FieldDef *fieldsDef; /* динамический массив,
каждый элемент которого - описание поля таблицы */
};
/******************************************************/
/* Ниже описываются прототипы функций для работы */
/* с таблицами. После каждого прототипа следует */
/* краткий комментарий */
enum Errors createTable(char *tableName,
struct TableStruct *tableStruct);
/* Функция создает новую таблицу с заданным именем и структурой. При этом создается новый файл в текущей директории, в начальных областях файла сохраняется структура таблицы */
enum Errors deleteTable(char *tableName);
/* Функция удаляет таблицу с заданным именем */
enum Errors openTable(char *tableName, THandle *tableHandle);
/* Функция открывает таблицу с заданным именем для работы. По адресу tableHandle записывается дескриптор, который должен использоваться для ссылок на данную таблицу. При этом открывается файл таблицы. На основе заголовка файла таблицы, хранящего информацию о структуре таблицы, в оперативной памяти заполняется соответствующий элемент типа struct TableStruct. Создаются буферы для работы с текущей записью и для создания новой записи. Размер буферов определяется на основе структуры таблицы. */
enum Errors closeTable(THandle tableHandle);
/* Функция закрывает таблицу для использования. При этом освобождается место, занимаемое данной таблицей,и буферы, закрывается файл таблицы. */
enum Errors moveFirst(THandle tableHandle);
/* Функция устанавливает указатель файла на первую запись (если она есть) и считывает запись в буфер текущей записи. Если таблица пуста, то состояние буфера текущей записи не определено. При этом функции afterLast и beforeFirst выдают значение TRUE.*/
enum Errors moveLast(THandle tableHandle);
/* Функция устанавливает указатель файла на последнюю запись (если она есть) и считывает запись в буфер текущей записи.. Если таблица пуста, то состояние буфера текущей записи не определено. При этом функции afterLast и beforeFirst выдают значение TRUE.*/
enum Errors moveNext(THandle tableHandle);
/* Функция устанавливает указатель файла на следующую в файле запись (если она есть) и считывает запись в буфер текущей записи.. Если буфер уже находился на последней записи, то он переходит в состояние “после последней”, в котором содержимое буфера не определено. При этом функция afterLast выдает значение TRUE.*/