Асинхронные запросы к MySQL на API (libmysqlclient)

в 17:51, , рубрики: c/c++, c++, mysql, метки: ,

Так получается, что сейчас тружусь над планировщиком для MySQL соединений. И тут недавно пришлось покапаться в документации/блогах и т.д. И вот решил поделиться с сообществом как реализовать асинхронные запросы к MySQL серверу на С++ используя API и библиотеку libmysqlclient.

Для того, чтобы начать программировать с использованием API MySQL, нам нужен заголовочный файл mysql.h.
И библиотека libmysqlclient. Для deb-подобных ОС всё это можно поставить так:

sudo apt-get install libmysqlclient-dev libmysqlclient

Компилятору указываем следующее:
-L/path/to/mysqliclientlib/ -lmysqlclient

Если пишите код в Qt Creator, то в файле проекта можно добавить следующее(у вас может быть другой путь):
LIBS += -L/usr/lib/mysql -lmysqlclient

У нас почти всё готово, для того чтобы работать с MySQL на С++.
Определяемся: асинхронные запросы — несколько параллельных запросов к базе без использования потоков (thread). Используя функцию mysql_real_query этого добиться невозможно, т.е. программа будет ожидать результат(ответ) от сервера.

Кроме всего прочего, я использую библиотеку libevent для отлова событий. Поставить её можно так:

sudo apt-get install libevent-dev libevent

Указываем компилятору:
LIBS += -L/usr/lib/mysql -lmysqlclient -L/usr/lib/ -levent
Заголовочный файл:
#include <event.h>

Всё, всё готово. Приступаем. Итак пишем класс который будет посылать запросы асинхронно. У меня этот класс содержит ещё методы — я остановлюсь только на тех, которые имеют отошение к теме.

class CBalanceMySQL
{
    private:
        // ваши приватные члены

        // число одновременных процессов 
        unsigned int thread_count;

        // соединения с базой
        std::vector<MYSQL*> v;
        // события
        std::vector<event*> events;
        // запросы
        std::vector<std::string> queries;

        struct event_base *evbase;

        // указатель на функцию, при успешном выполнении запросы и 
        // ответа от сервера БД
        pFunc p_func;

        // метод для выполнения запросов асинхронно
        void run();
    public:
        // constructor
        CBalanceMySQL();

        // destructor
        ~CBalanceMySQL();

        // Тут ваши остальные публичные методы

        // устанавливаем число процессов одновременных
        void setThreadCount(int);

        // соединяемся с базой
        bool ConnectAllThreads();

        // даём указатель на функцию в случае успеха
        bool setSuccessFunc(void(*)(int, short, void*));

};

В конструкторе класса (или где-то ещё) инициализируем событийную базу:

this->evbase = event_base_new();

Далееб нам будет необходимо установить соединения с сервером и «запомнить» их. Бежим в цикле по заданном числу запросов и сохраняем соединения в векторе v.

bool CBalanceMySQL::ConnectAllThreads() {
    for (uint i = 0; i < this->thread_count; i++) {
        MYSQL *m;
        m = mysql_init(NULL);
        if (!mysql_real_connect(m, this->host, this->user, this->password, NULL, this->port, NULL, 0)) {
            std::cout << mysql_error(m);
            return 0;
        } else {
            this->v.push_back(m);
        }
    }
 return 1;
}

Осталось выполнить нужные нам запросы по этим коннекшнам:

void CBalanceMySQL::run() {
for (uint i = 0; i < this->v.size(); i++) { 
            // событие
            struct event *ev;
            // запрос к конкретному коннекту
            std::string q = this->queries.at(i);
            // ныжный нам коннект
            MYSQL *m = this->v.at(i);
            // выполняем запрос
            mysql_send_query(m, q.c_str() , strlen(q.c_str()));
            ev = new event;
            // регистрируем событие
            // по ответу - вызывается функция по указанному адресу p_func
            event_set(ev, m->net.fd, EV_READ, this->p_func, m);
            event_base_set(this->evbase, ev);
            event_add(ev, NULL);
            events.push_back(ev); 
        }
         // цикл до тех пор пока события не зарегистрируются  
         while (0 == event_base_loop(this->evbase, 0));         
} 

На этом почти всё. Осталось почистить за собой:

for (uint i = 0; i < v.size(); i++) {
        mysql_close(this->v.at(i));
}
    for (uint i = 0; i < events.size(); i++) {
        delete(this->events.at(i));
    }
    event_base_free(this->evbase);

И последнее — функция обработки результата запроса. Она имеет вид:

static void read_result(int fd, short event, void *_userdata) {
    MYSQL *m = (MYSQL*)_userdata;
    if (0 == mysql_read_query_result(m)) {
        MYSQL_RES *res = mysql_store_result(m);
        MYSQL_ROW row = mysql_fetch_row(res);
        // у меня был запрос  SELECT COUNT(*) FROM information_schema.processlist, я просто вывожу число соединений
        cout << "cnt for net.fd = " << fd << " is " << row[0] << endl;
        mysql_free_result(res);
    }
}

На этом всё. Надеюсь кому-то пригодится.
p.s. сильно не пинать — в C++ не гуру.

Автор: modestguy

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js