如何用c++完成一个mysql 的客户端?
代码还需完善,但是核心代码在这里了
参考资料:
MySQL :: MySQL 8.0 C API Developer Guide :: 5.4.22 mysql_fetch_row()
注意几个点:
异步接口:
mysql_real_connect_nonblocking
mysql_real_query_nonblocking
mysql_store_result_nonblocking
mysql_fetch_row_nonblocking
配合异步io,epoll 可以很轻松完成,异步返回结果
/* state of an asynchronous operation */
enum net_async_status {
NET_ASYNC_COMPLETE = 0,
NET_ASYNC_NOT_READY,
NET_ASYNC_ERROR,
NET_ASYNC_COMPLETE_NO_MORE_RESULTS
};
简单上以下代码:
#pragma once
#include <memory>
#include <queue>
#include <functional>
#include <mysql/mysql.h>
#include "non_copyable.h"
#include "non_moveable.h"
#include "mysql_conn_config.h"
#include "mysql_message.h"
enum State{
CONNECT_START,
CONNECT_WAITING,
CONNECT_DONE,
QUERY_START,
QUERY_WAITING,
QUERY_RESULT_READY,
RESULT_START,
RESULT_WAITING,
FETCH_ROW_START,
FETCH_ROW_WAITING,
FETCH_ROW_RESULT_READY,
CLOSE_START,
CLOSE_WAITING,
CLOSE_DONE
};
namespace Event {
class EventLoop;
class EventChannel;
}
namespace Mysql {
class MysqlConnection :public Core::Noncopyable {
public:
MysqlConnection(MysqlConnectionConfig& config);
void start(const std::shared_ptr<Event::EventLoop>& loop);
void query(const char* query, int index);
~MysqlConnection();
private:
bool connect(const std::shared_ptr<Event::EventLoop>& loop);
void onWrite(const Event::EventChannel* channel);
void onRead(const Event::EventChannel* channel);
void onClose(const Event::EventChannel* channel);
void onError(const Event::EventChannel* channel);
void nextStep(int status, int state_wait, const std::function<void()>& onWait, int state_go_on, const std::function<void()>& onContinue);
void onResult();
void onQuery();
void onQueryFinish();
int current_state = CONNECT_START; // State machine current state
MYSQL mysql;
MYSQL *ret;
MYSQL_RES *result;
MYSQL_ROW row;
std::queue<MysqlMessage> current_query_entry;
int err = 0;
int index = 0;
MysqlConnectionConfig mysql_config;
struct timeval now;
};
}
实现文件
#include "mysql/mysql_connection.h"
#include <iostream>
#include "build_expect.h"
#include "os/unix_logger.h"
#include "mysql/mysql_response.h"
#include "event/event_no_buffer_channel.h"
namespace Mysql {
MysqlConnection::MysqlConnection(MysqlConnectionConfig &config) {
mysql_init(&mysql);
mysql_config = config;
mysql_options(&mysql, MYSQL_READ_DEFAULT_GROUP, "async_queries");
// set timeouts to 300 microseconds
uint default_timeout = config.getDefaultTimeout();
mysql_options(&mysql, MYSQL_OPT_READ_TIMEOUT, &default_timeout);
mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &default_timeout);
mysql_options(&mysql, MYSQL_OPT_WRITE_TIMEOUT, &default_timeout);
current_state = CONNECT_START;
}
void MysqlConnection::start(const std::shared_ptr<Event::EventLoop>& loop) {
connect(loop);
}
void MysqlConnection::query(const char *query, int index_) {
MysqlMessage message;
message.query = query;
message.index = index_;
current_query_entry.push(message);
}
void MysqlConnection::nextStep(int status, int state_wait, const std::function<void()>& onWait, int state_go_on, const std::function<void()>& onContinue) {
if (status == NET_ASYNC_ERROR) {
SYSTEM_ERROR_LOG_TRACE(std::to_string(mysql_errno(&mysql)) + ":" + (mysql_error(&mysql)))
return;
}
if (status == NET_ASYNC_NOT_READY) {
current_state = state_wait;
onWait();
return;
}
current_state = state_go_on;
onContinue();
}
void MysqlConnection::onResult() {
net_async_status status = mysql_store_result_nonblocking(&mysql, &result);
nextStep(status, RESULT_WAITING, []{}, FETCH_ROW_START, [this]{
onQueryFinish();
});
}
void MysqlConnection::onQuery() {
if (build_unlikely(current_query_entry.empty())) {
current_state = CLOSE_START;
return;
}
auto message = current_query_entry.front();
if (message.index == 0) {
gettimeofday(&now, nullptr); // start time
}
net_async_status status = mysql_real_query_nonblocking(&mysql, message.query.c_str(), message.query.length());
nextStep(status, QUERY_WAITING, []{}, FETCH_ROW_WAITING, [this]{
onResult();
});
}
void MysqlConnection::onQueryFinish() {
MysqlResponse response(result);
while (row) {
net_async_status status = mysql_fetch_row_nonblocking(result, &row);
if (!row) {
break;
}
nextStep(status, FETCH_ROW_WAITING, [] {}, FETCH_ROW_RESULT_READY,
[this, &response] {
response.addRow(row);
});
}
}
void MysqlConnection::onRead(const Event::EventChannel *channel) {
switch (current_state) {
case CONNECT_WAITING: {
net_async_status status = mysql_real_connect_nonblocking(&mysql, mysql_config.getHost().c_str(), mysql_config.getUser().c_str(),
mysql_config.getPassword().c_str(), mysql_config.getDB().c_str(), mysql_config.getPort(), nullptr, 0);
nextStep(status, CONNECT_WAITING, []{}, CONNECT_DONE, [this]{
onQuery();
});
break;
}
case CONNECT_DONE: {
nextStep(NET_ASYNC_COMPLETE, CONNECT_DONE, []{}, QUERY_START, [this]{
onQuery();
});
break;
}
case QUERY_START: {
onQuery();
break;
}
case QUERY_WAITING: {
onQuery();
break;
}
case RESULT_START: {
onResult();
break;
}
case RESULT_WAITING: {
onResult();
break;
}
case FETCH_ROW_START: {
onQueryFinish();
break;
}
case FETCH_ROW_WAITING: {
onQueryFinish();
break;
}
}
}
void MysqlConnection::onWrite(const Event::EventChannel *channel) {
}
void MysqlConnection::onClose(const Event::EventChannel *channel) {
}
void MysqlConnection::onError(const Event::EventChannel *channel) {
}
bool MysqlConnection::connect(const std::shared_ptr<Event::EventLoop>& loop) {
net_async_status status = mysql_real_connect_nonblocking(&mysql, mysql_config.getHost().c_str(), mysql_config.getUser().c_str(),
mysql_config.getPassword().c_str(), mysql_config.getDB().c_str(), mysql_config.getPort(), nullptr, 0);
if (status == NET_ASYNC_ERROR) {
std::cout << mysql_error(&mysql) << std::endl;
return false;
}
if (NET_ASYNC_NOT_READY == status) {
current_state = CONNECT_WAITING;
} else {
current_state = CONNECT_DONE;
}
std::shared_ptr<Event::EventNoBufferChannel> channel = std::make_shared<Event::EventNoBufferChannel>(loop, mysql.net.fd);
channel->setOnWriteCallable(std::bind(&MysqlConnection::onWrite, this, std::placeholders::_1));
channel->setOnReadCallable(std::bind(&MysqlConnection::onRead, this, std::placeholders::_1));
channel->setOnErrorCallable(std::bind(&MysqlConnection::onError, this, std::placeholders::_1));
channel->setOnCloseCallable(std::bind(&MysqlConnection::onClose, this, std::placeholders::_1));
channel->enable(-1);
return true;
}
MysqlConnection::~MysqlConnection() {
mysql_close(&mysql);
}
}