...
...

MySQL C++. Простые решения

MySQL & C++. Простые решения

Здравствуйте, уважаемые любители сами знаете чего! Сегодня я хотел бы вновь поговорить о СУБД MySQL. Мне пришло множество откликов от читателей, которых интересовало, каким же образом можно использовать это замечательное творение рук человеческих:-), не устанавливая web-сервер и не используя обработчик PHP- или Perl-скриптов. Типичный случай, когда может понадобиться база данных, придумать несложно.

Но загвоздка в том, что большинство примеров использования MySQL в Интернете посвящено именно веб-приложениям (оно, в принципе, и понятно — такова специфика). И это зачастую отпугивает программистов. Прямым конкурентом MySQL в нише небольших приложений на малых предприятиях, пожалуй, следует считать MsAccess. Пусть многие со мной не согласятся, но Access — это не совсем то, что следует использовать при наличии локальной сети в организации, пусть и база данных предполагает быть небольшой. Но ведь и в MicroSoft (не правда ли, звучит как насмешка: их программные продукты уже давно с бо-о-ольшой натяжкой можно назвать Micro:)) не сразу предполагали, что Windows будет занимать 1 Гб и более. Так что… А если данные используются не на одном компьютере, а сразу на нескольких, то синхронизация таблиц без централизованного сервера СУБД становится головной болью. (Встречаются извращения, когда каждый день в обязанности сотрудников входит "скачивание" с сетевого диска последней версии файла MsAccess. А теперь представьте себе ситуацию, когда данные были внесены некорректно — файл-то уже скачали.

Весело, не правда ли? В таких случаях можно просто установить на одной машине сервер, раздать пользователям логины с соответствующими правами и навсегда забыть о проблемах (не забывайте только почаще делать резервное копирование: мало ли что). Причем, если клиент для БД написан грамотно, у пользователей вообще будет полная иллюзия работы ТОЛЬКО на своей машине, и будет выполняться одно из самых часто нарушаемых правил совместной работы в локальной сети: все важные данные хранить на сервере. Так ведь легче и архивировать, и восстанавливать, если что. Итак, чем мы займемся? А займемся мы написанием приложения, которое будет работать с сервером MySQL без использования "продуктов сторонних разработчиков", web-сервера и т.п. Вообще если брать общий случай, когда база данных создана (под этим предлагаю понимать создание таблиц и связей, внесение в них данных, создание шаблонов запросов, а также раздачу привилегий пользователям), все готово для работы. Вместе с сервером версии 3.23.38 (который мы будем рассматривать) поставляется программка mysql.exe, которая может запросто работать по сети, но, правда, ТОЛЬКО в консоли:-). Но настоящих программистов вид консоли ведь не должен пугать? (Бывают и такие, с дипломами БГУ, причем:-)). Но для конечных пользователей это, я согласен, неприемлемо.

А посему рассмотрим, как можно использовать MFC для наших нужд. Если вы посмотрите на каталог, куда установлен ваш сервер (c:\mysql\ по умолчанию), вы увидите, кроме всего прочего, две интересные папки: include и lib. В первой из них вы найдете *.h-файлы, содержащие описание функций, которые мы будем использовать, а во втором каталоге лежит *.lib-файл, который нам тоже понадобится: libmySQL.lib (c:\mysql\lib\debug). Все эти файлы следует переписать в соответствующие директории, где находятся ваши заголовочные и библиотечные файлы (например, c:\Program Files\ Microsoft Visual Studio\Vc98\ Include и c:\Program Files\ Microsoft Visual Studio\Vc98\ lib, если VC устанавливался по умолчанию). Как видите, разработчики позаботились о том, чтобы нам с вами можно было как можно проще использовать их сервер в своих приложениях. Все описанные там функции отвечают за сетевое взаимодействие с сервером, отправку запросов, закрытие соединения т.п. Разработчику прикладных программ не нужно заботиться о сокетах и тому подобном. Еще нам понадобится dll-библиотека libmySQL.dll (c:\mysql\lib\ debug), которую следует копировать в каталог проекта, использующего функции, описанные в этих файлах. Но о создании приложения чуть попозже. Итак, как выглядит работа с MySQL-сервером из С\С++? На это можно посмотреть в примере, опять же, поставляемом вместе с сервером c:\mysql\examples\libmysqltest\. Там дан исходный код программы, использующий libmysql.dll для связи с сервером и получения данных от него. Но это консольное приложение. И, как я уже сказал, это нам не подходит. Итак, кратко расскажу, как мы будем из своего приложения получать данные от сервера. Сначала нужно к нему подключиться.

Для подключения к серверу необходимо вызвать функцию mysql_init(), которая проинициализирует HANDLE, а после собственно создаст подключение, вызвав mysql_real_connect().

MYSQL *mysql_init(MYSQL *mysql), где MYSQL — структура, содержащая HANDLE для одного подключения к серверу.
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned int client_flag), где соответственно host — компьютер, на котором запущена СУБД MySQL, user — имя юзера для подключения, passwd — пароль, db — название предполагаемой для использования базы данных, port — порт, unix_socket — сокет или pipe-канал, который необходимо использовать. client_ flag может принимать несколько значений:

CLIENT_COMPRESS — используется сжатие.
CLIENT_FOUND_ROWS — возвращать число найденных строк.
CLIENT_IGNORE_SPACE — делает все имена функций зарезервированными словами.
CLIENT_INTERACTIVE — разрешает interactive_timeout секунд бездействовать (вместо wait_timeout) перед закрытием подключения.
CLIENT_NO_SCHEMA — запрещает синтаксис вида "db_name.tbl_name.col_name" (имя_базы_данных.имя_таблицы.имя_колонки). Используется для ODBC.
CLIENT_ODBC — устанавливает то, что это клиент ODBC.
CLIENT_SSL — используется защищенный протокол SSL.

Небольшой пример:
MYSQL mysql;
mysql_init(&mysql);
mysql_options(&mysql,MYSQL_READ_DEFAULT_GROUP,"your_prog_name");
if (!mysql_real_connect(&mysql,"host", "user","passwd","database",0,NULL,0))

{
fprintf("Ошибка соединения с сервером: Error: %s\n",
mysql_error(&mysql));//О получении ошибок см. ниже
}

После того, как соединение установлено, с базой данных можно работать. Закрыть соединение можно при помощи mysql_close(): void mysql_close (MYSQL *mysql).
Еще полезной функцией является mysql_ping(). Она пингует сервер (вернее, проверяет, действительно ли текущее соединение, задаваемое получаемым параметром), и, если соединение потеряно, пытается переподключиться (иногда полезно проверять подключение, если предполагается, что программа может долгое время не использоваться юзером активно).

int mysql_ping(MYSQL *mysql)

Когда подключение создано, серверу можно отсылать запросы при помощи функций mysql_query() или mysql_real_ query(). Разница между ними в том, что первая функция в качестве запроса использует строку, заканчивающуюся нулевым символом. А вторая используется, если в запросе есть двоичные данные с содержанием множества нулевых символов.

int mysql_query(MYSQL *mysql, const char *query)

Возвращает ноль, если запрос выполнен удачно.
Если запрос не использует конструкцию SELECT, то количество измененных записей можно найти при помощи вызова mysql_affected_rows()
my_ulonglong mysql_affected_rows (MYSQL *mysql).
Существует два варианта работы с результатами запроса, возвращенных сервером. Первый из них состоит в том, чтобы использовать функцию mysql_store_result(), которая получает все строки результата запроса и хранит их в буфере-клиенте. Второй состоит в использовании mysql_use_re-sult(). Фактически эта функция не получает никаких строк от сервера. Она как бы инициализирует поиск.
MYSQL_RES *mysql_store_ result(MYSQL *mysql). Желательно использование этой функции для запросов, содержащих SQL конструкции SELECT, SHOW, DESCRIBE, EXPLAIN. Однако ничто вам не запрещает использовать ее и для других запросов (INSERT, например).

MYSQL_RES *mysql_use_ result(MYSQL *mysql). Эта функция не получает все данные от сервера сразу, поэтому не требуется хранить их во временном буфере клиента. Это может понадобиться, если вам нет необходимости выводить все результаты сразу, а нужно лишь показывать их "постранично". Однако вы не сможете использовать mysql_ data_seek(), mysql_row_seek(), mysql_row_tell(), mysql_num_ rows(), или mysql_affected_rows () с результатом, возвращенным mysql_use_result(), а также выполнять другие запросы, пока не "используете" все данные предыдущего. Кроме того, вы должны вызвать mysql_ free_result(), как только закончите работать с результатами запроса.
В обоих случаях получить доступ к записям, удовлетворяющим запросу, можно при помощи функции mysql_fetch_ row(). При ее использовании с mysql_store_result() вы получаете доступ к строкам, уже полученным от сервера, а при использовании с mysql_use_ result() вы фактически получаете доступ к записи на сервере. Информация относительно размера данных доступна при вызове mysql_fetch_lengths (). И вызывайте mysql_free_ result() для очистки памяти.

Преимущество mysql_store_ result() состоит в том, что при помощи этой функции вы получаете произвольный доступ к полученным данным, двигаясь по ним при помощи mysql_ data_seek () или mysql_row_ seek (). Вы можете также получить информацию о количестве строк, вызвав mysql_num_ rows (). Однако этот способ имеет и свои недостатки. При обработке больших объемов данных может возникнуть опасность переполнения буфера.
Преимущество mysql_use_ result() состоит в том, что к клиенту предъявляются более низкие требования по количеству используемой памяти (и производительности соответственно). Недостатком является то, что вы должны обрабатывать данные достаточно быстро, чтобы не потерять связь с сервером. Кроме того, вы не имеете произвольного доступа к результатам. И вы не можете узнать, сколько записей в ответе на запрос (пока не обработаете их все).
У всех бывают ошибки. Для получения информации об ошибках используются функции mysql_errno () и mysql_ error (). Первая возвращает номер ошибки, вторая — текстовое пояснение. Следует помнить, что они возвращают только ошибку, которую вызвала последняя выполненная вами функция.
unsigned int mysql_errno(MYSQL *mysql)
char *mysql_error(MYSQL *mysql).

Более подробное описание функций можно найти в мануале, поставляемом вместе с сервером (правда, на английском, но, я думаю, это для вас не проблема): c:\mysql\docs\ manual.html.
Итак, пишем наше клиентское приложение. Уметь оно будет немного, а конкретней, следующее: подключаться к базе данных, пинговать подключение и соответственно переподключаться если что, посылать запросы и выводить их результаты пользователю. Запросы будут набираться в командной строке. Создаем MFC-приложение с таким вот интерфейсом:



Приложение будет называться SQL_client.
Ниже в таблице представлены обработчики событий взаимодействия пользователя с интерфейсом, а также переменные, ответственные за хранение введенных данных:



Кнопку ОК оставим без изменений. Дополнительно следует объявить следующую функцию: void CSQL_clientDlg ::Add_str_in_box(), которая будет служить для вставки последней выполненной команды в Combo Box CMD.
Также следует объявить следующие переменные:
MYSQL mysql — эта структура будет хранить данные о подключении.
BOOL error_connect — флаг упешного подключения к серверу. Его следует проинициализировать "error_connect= FALSE;" в конце функции BOOL CSQL_clientDlg::OnInitDialog() что в SQL_clientDlg.cpp.
BOOL flg_msg_error_connect; — флаг потери соединения.

В заголовки файлов необходимо включить строку #in-clude <mysql.h> для того, чтобы использовать функции, описываемые в *.h-файлах, о которых говорилось в начале статьи. В проект также следует включить libmysql.lib (щелкнув правой кнопкой по ветке SQL_client files на закладке FileView-> Add Files To Pro-ject…). Теперь создадим таймер. Заходим в ClssWizard и на закладке Message Map для Object Ids CSql_clientDlg задаем Message WM_TIMER и создаем функцию. Тело ее должно выглядеть следующим образом:

//Пингуем сервер
void CSQL_clientDlg::OnTimer(UINT nIDEvent)
{
//В зависимости от установленных флагов сообщаем информацию пользователю
int png=mysql_ping(&mysql);
switch(png)
{
case 0:
error_connect=TRUE;
if(flg_msg_error_connect)
{
m_out+="Соединение с сервером восстановлено";
UpdateData(FALSE);
flg_msg_error_connect=FALSE;
}
break;
default:
if(error_connect)
{
m_out+="ERROR. Соединение с сервером потеряно. Попытка переподключения...\r\n";
UpdateData(FALSE);
error_connect=FALSE;
flg_msg_error_connect=TRUE;
};
break;

};
UpdateData(TRUE);
CDialog::OnTimer(nIDEvent);
}

Эта функция выдает сообщение о потере соединения только при первом провале проверки подключения функцией mysql_ping(&mysql), а также при восстановлении соединения.
Кажется, все, подготовительный этап закончен.
Теперь осталось соответствующим образом изменить тела функций. Начнем по порядку. Добавление строчек в Combo-Box (только для удобства:-)). Это удобно в том случае, если вам нужно повторить длинную команду, а вы ее предусмотрительно не скопировали в буфер. Ведь это все же не настоящая консоль.

void CSQL_clientDlg::Add_str_in_box()
{
//Вставляем в командную строку последнюю введенную команду
CComboBox *Box;
//Получаем указатель...
Box = (CComboBox *)(this-> GetDlgItem(IDC_cmd));
//Вставляем...
Box-> AddString(m_cmd);
}

Подключаться к серверу будем специальной конструкцией, которую будем вызывать и в случае (если пользователь забыл подключиться сразу) попытки сделать запрос. Функция подключения к серверу выглядит следующим образом:

void CSQL_clientDlg::connect()
{
//Обработка незаполненных полей, заставляем пользователя заполнить все поля
UpdateData(TRUE);
mysql_init(&mysql);
if(m_host=="" || m_user=="" || m_password=="" || m_bd=="")
{
MessageBox("Не все поля заполнены!");
m_out+="Не все поля заполнены!";
UpdateData(FALSE);
return;

}
//Обрабатываем ошибки
if (!mysql_real_connect(&mysql,m_host, m_user,m_password,m_bd,0,NULL,0))
{
MessageBox("Error");
//Выводим текст возможных ошибок.
m_out+=mysql_error(&mysql);
}
//Сообщаем пользователю об успешном подключении
else
{
MessageBox("OK");
m_out+="Подключение к серверу MySQL прошло успешно!Теперь можно работать.\r\n";
error_connect=TRUE;
flg_msg_error_connect=FALSE;

}
//Запускаем таймер, по которому будет пинговаться сервер
SetTimer( 1, 5000, NULL );
UpdateData(FALSE);
return;
}
Осталось только отправить запрос на сервер и выдать результаты пользователю:
void CSQL_clientDlg::Ongo()
{
//Обьявляем переменные
CString tmp;
MYSQL_RES * res ;
MYSQL_FIELD* fd ;
MYSQL_ROW row ;
unsigned int i,num_fields;
unsigned long *lengths;
char buf[255];
my_ulonglong raws_nubm;
//Обновляем данные
UpdateData(TRUE);
//Добавляем строки в combo box
Add_str_in_box();
//Проверка подключения — подключены или нет
if(error_connect)
{
//Делаем запрос
mysql_query( &mysql, m_cmd );
tmp=mysql_error(&mysql);
m_out+=tmp;
m_out+="\r\n";
UpdateData(FALSE);
//Если есть ошибка, не выводим результаты
if(tmp=="")
{
//Получаем результаты запроса
res = mysql_store_result(&mysql);
//Получаем количество возвращенных записей
m_out+="Количество записей, удовлетворяющих запросу\r\n";
raws_nubm=mysql_num_rows(res);
//Сообщаем пользователю
itoa( raws_nubm, buf, 10 );
m_out+= (CString)&buf[0];
m_out+="\r\n";
UpdateData(FALSE);
num_fields = mysql_num_fields(res);
//Последовательно выводим все полученные данные, пока в структуре еще есть записи.
while ((row = mysql_fetch_row(res)))
{
lengths = mysql_fetch_lengths(res);
for(i = 0; i < num_fields; i++)
{
m_out+=row[i]; m_out+=" | ";
}
UpdateData(FALSE);
m_out+=("\r\n");
}
m_out+=tmp;
tmp="";
//Освобождаем память
mysql_free_result(res);
}

}
//Обрабатываем ошибки
else
{
m_out+="Error:Нет подключения к серверу. Попытка подключения...\r\n ";
UpdateData(FALSE);
connect();

};
UpdateData(FALSE);
}

Работа приложения выглядит следующим образом. Пользователь вводит данные подключения, после чего можно нажать либо кнопку Connect, либо GO. При этом в любом случае вызывается функция CSQL_client Dlg::connect(), которая подключается к серверу MySQL и запускает таймер, который каждые 5 секунд проверяет наличие соединения (период пингования зависит от настроек сервера, т.е. времени, через которое он закрывает соединение, сети и т.д.). Теперь сервер может принимать и выполнять запросы. Запросы пользователь вводит в командной строке CMD, после чего они заносятся в список ComboBox'a. При нажатии на GO результаты запросов выводятся построчно через разделитель "|". Ну вот, пожалуй, и все. Только учтите: когда будете переносить свое приложение на другие машины, вам понадобятся некоторые dll-библиотеки (кроме прочих, "стандартных"). Все, конечно, зависит от конкретной системы, но в данном приложении используются следующие(WinXP Rus):

libmySQL.dll
MFC42D.DLL
MFC42LOC.DLL
MFCO42D.DLL
MSCTF.DLL
MSVCRT.DLL
MSVCRTD.DLL

Но рекомендую все же на всякий случай просмотреть, какие библиотеки использует данное приложение в вашей системе.
Найти их можно в c:\windows\system32 (кроме libmy SQL.dll, она есть в директории, в которую установлен сервер mysql). Как обычно, исходные тексты можно получить от меня по почте. И еще раз напоминаю, что данный пример является только примером, поэтому он ограничен по функциональности и не выделяется своей производительностью. Это уже ваша работа:-).
До свидания, и удачных вам коннектов!

Спичеков Александр aka MentAlzavR,
Zavr6@mail.ru



© Компьютерная газета

полезные ссылки
Видеокамеры дял охранных систем