C 语言实现MySQL连接池

来源:互联网 发布:桌面滚动字幕软件 编辑:程序博客网 时间:2024/06/05 07:16

引言:数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个; 连接池技术大多运用在高并发服务器的后面;在现有的大型高并发服务器上,每一次线程与数据库的数据交换都属于网络连接,频繁的启停连接是极不合理的,特别是在大型Web服务器上过长时间的延迟在用户体验上极其糟糕,而重复的建立与断开数据库连接对程序的运行也有相当大的影响;

连接池是一个抽象的概念,它包含了一堆数据库连接、连接管理线程、对外提供的接口;
1.线程通过调用连接池接口获得一个数据库连接,使用后无需销毁连接,只需要在逻辑上放弃连接;当下一个线程获取时仍然能够正常使用;
2.连接池管理线程动态的对数据库连接进行管理,合理的增删,在满足外来调用的基础上保证连接数量的合理利用, 它就像一个吝啬的雇主; 增加和删除都会改变连接池的当前的状态,所以在下图数据库连接部池分的互斥锁将保证对状态操作的唯一性;
3.数据库连接应该在使用线程上加互斥锁,即使我们有十个使用了连接的线程,但在同一时间对同一个数据的修改我们也应该保证只有一个线程在操作,该功能在下图的线程部分中的互斥锁实现;

该图为一个简单的连接池实现模型,我们将以该图来实现一个最基础的连接池,该数据库采用MySQL
这里写图片描述

一、数据库连接池的抽象结构体(圆圈1)

#define IP_LEN      15#define DBNAME_LEN  64#define DBUSER_LEN  64#define PASSWD_LEN  64#define POOL_MAX_NUMBER 20typedef struct _SQL_NODE SQL_NODE;                /* 连接节点 */typedef struct _SQL_CONN_POOL SQL_CONN_POOL;      /* 连接池 *//* 连接节点 */typedef struct _SQL_NODE{  MYSQL            fd;                  /* MYSQL对象文件描述符 */  MYSQL            *mysql_sock;         /* 指向已经连接的MYSQL的指针 */  pthread_mutex_t  lock;                /* 互斥锁; 用在线程对数据的操作限制*/  int              used;                /* 使用标志 */  int              index;               /* 下标 */  enum{                                 /* 连接状态 */     DB_DISCONN, DB_CONN               }sql_state;}SQL_NODE;/* 连接池 */typedef struct _SQL_CONN_POOL{  int        shutdown;                   /* 是否关闭 */  SQL_NODE   sql_pool[POOL_MAX_NUMBER];  /* 一堆连接 */  int        pool_number;                /* 连接数量 */  int        busy_number;                /* 被获取了的连接数量 */  char       ip[IP_LEN+1];               /* 数据库的ip */  int        port;                       /* 数据库的port,一般是3306 */  char       db_name[DBNAME_LEN+1];      /* 数据库的名字 */  char       user[DBUSER_LEN+1];         /* 用户名 */  char       passwd[PASSWD_LEN+1];       /* 密码 */}SQL_CONN_POOL;

二、函数概况 (圆圈3) : 服务器创建连接池(创建节点),分配连接到线程,处理并归还;

/*创建连接池*/SQL_CONN_POOL *sql_pool_create(int connect_pool_number, char ip[], int port,                                char db_name[], char user[], char passwd[]);/*节点创建连接*/int create_db_connect(SQL_CONN_POOL *sp, SQL_NODE *node);/*销毁连接池*/void sql_pool_destroy(SQL_CONN_POOL *sp);/*取出一个未使用的连接*/SQL_NODE *get_db_connect(SQL_CONN_POOL *sp);/*归回连接*/void release_node(SQL_CONN_POOL *sp, SQL_NODE *node);/*增加或删除连接*/SQL_CONN_POOL *changeNodeNum(SQL_CONN_POOL *sp, int op);

三、分部实现
3.1 创建连接池函数 ==> 思路:规划一段内存空间存放数据库的连接,并初始化连接池状态信息;

/*创建连接池*/SQL_CONN_POOL *sql_pool_create(int connect_pool_number, char ip[], int port,                                char db_name[], char user[], char passwd[]){   SQL_CONN_POOL *sp = NULL;   /* 错误输入检测 */   if (connect_pool_number < 1)   {      printf("connect_pool_number < 1. defalut 1 \n");      connect_pool_number = 1;   }   /* 为连接池划分内存空间 */   if ((sp=(SQL_CONN_POOL *)malloc(sizeof(SQL_CONN_POOL))) == NULL)   {      printf("malloc SQL_CONN_POOL error.\n");      return NULL;   }   sp->shutdown    = 0;              //开启连接池   sp->pool_number = 0;              //连接数量   sp->busy_number = 0;              //正在使用的连接数   strcpy(sp->ip, ip);               //数据库IP   sp->port = port;                  //数据库Port   strcpy(sp->db_name, db_name);     //数据库名字   strcpy(sp->user, user);           //使用用户名   strcpy(sp->passwd, passwd);       //密码   /* 创建连接 */   if (connect_pool_number > POOL_MAX_NUMBER)     connect_pool_number = POOL_MAX_NUMBER;   for (int index=0; index < connect_pool_number; index++)   {      //创建失败, 自定义函数,创建节点函数;      if (0 != create_db_connect(sp, &sp->sql_pool[index]))      {         //销毁连接池,自定义函数,销毁连接池;         sql_pool_destroy(sp);         return NULL;      }      //创建成功      sp->sql_pool[index].index = index;      sp->pool_number++;      printf("create database pool connect:-%d-.\n",sp->sql_pool[index].index);    }   return sp;}

3.2 创建连接节点 ==> 思路:创建连接前加锁,成功后记得设置该节点的自动连接与超时限定;特别注意当一个连接在一定时间后没有使用后会断开连接,所以我们需要设置自动连接,当连接断开后自动重新连接;

int opt=1; //超时时间int res=0; //0正常 -1初始化失败 1 连接失败 do {    if (shutdown == 1)         return -1;    /* 节点加锁 */    pthread_mutex_init(&node->lock, NULL);    /* 初始化mysql对象 */    if (NULL == mysql_init(&node->fd))    {      printf("mysql init error. \n");      res = -1;      break;    }    if (!(node->mysql_sock = mysql_real_connect(         &node->fd, sp->ip, sp->user, sp->passwd, sp->db_name, sp->port, NULL, 0)))    {      printf("can not connect to mysql.\n");      node->sql_state = DB_DISCONN;      res = 1;      break;    }    //使用状态与连接状态    node->used = 0;    node->sql_state = DB_CONN;    //设置自动连接开启    mysql_options(&node->fd, MYSQL_OPT_RECONNECT, &opt);    opt = 3;    //设置连接超时时间为3s,3s未连接成功则超时    mysql_options(&node->fd, MYSQL_OPT_CONNECT_TIMEOUT, &opt);    res = 0; }while(0); return res;

3.3 连接池销毁

void sql_pool_destroy(SQL_CONN_POOL *sp){  printf("destroy sql pool ... ... \n");  sp->shutdown = 1; //关闭连接池  for (int index=0; index < sp->pool_number; index++)  {     if (NULL != sp->sql_pool[index].mysql_sock)     {        mysql_close(sp->sql_pool[index].mysql_sock);        sp->sql_pool[index].mysql_sock = NULL;     }     sp->sql_pool[index].sql_state = DB_DISCONN;      sp->pool_number--;  }}

3.4 从连接池中取出一个能够使用的连接;为了保证取出每一个连接的可能性大致相同,这里将开始访问地址下标用随机数;在取出时也做了连接状态检测,若断开则重新建立连接

/*取出一个未使用的连接*/SQL_NODE *get_db_connect(SQL_CONN_POOL *sp){  //获取一个未使用的连接,用随机值访问index,保证每次访问每个节点的概率基本相同  int start_index = 0, index = 0, i;  int ping_res;       if (shutdown == 1)     return NULL;  srand((int)time(0)); //根据当前时间生成随机数  start_index = rand() % sp->pool_number; //访问的开始地址  for (i=0; i < sp->pool_number; i++)  {    index = (start_index + i) % sp->pool_number;    if (!pthread_mutex_trylock(&sp->sql_pool[index].lock))    {       if (DB_DISCONN == sp->sql_pool[index].sql_state)       {          //重新连接          if (0 != create_db_connect(sp, &(sp->sql_pool[index])))          {            //重新连接失败            release_node(sp, &(sp->sql_pool[index]));            continue;          }       }       //检查服务器是否关闭了连接       ping_res = mysql_ping(sp->sql_pool[index].mysql_sock);       if (0 != ping_res)       {         printf("mysql ping error.\n");         sp->sql_pool[index].sql_state = DB_DISCONN;         release_node(sp, &(sp->sql_pool[index]));        //释放连接       }       else       {         sp->sql_pool[index].used = 1;         sp->busy_number++;              //被获取的数量增1         break ;                         //只需要一个节点       }    }  }  if (i == sp->pool_number)    return NULL;  else    return &(sp->sql_pool[index]);}

3.5 回归连接

/*归回连接*/void release_node(SQL_CONN_POOL *sp, SQL_NODE *node){  node->used = 0;  sp->busy_number--;  pthread_mutex_unlock(&node->lock);}

四、连接池管理(圆圈2)
这里我的实现并没有采用一个自动的方案,只是提供了一个接口;有需求的朋友可以自行实现;我提一个解决方案:在初始化连接池阶段创建一个管理者线程,该线程隔一段时间就去检查连接池,通过一组逻辑代码去决定是否添加或删除连接;

/*增加或删除连接*/SQL_CONN_POOL *changeNodeNum(SQL_CONN_POOL *sp, int op)  //增加或减少5个连接{   int Num = 5;      /* 此处可以用宏代替,根据自己的需求来实现 */   int index;           int endindex;   if (op == 1)  //增加    0减少   {     endindex = sp->pool_number + Num;     /*创建连接*/     for (index=sp->pool_number; index < endindex; index++)     {         //创建失败         if (0 != create_db_connect(sp, &sp->sql_pool[index]))         {            //销毁连接池            sql_pool_destroy(sp);            return NULL;         }         //创建成功         sp->sql_pool[index].index = index;                                                                            sp->pool_number++;         printf("create database pool connect:-%d-.\n",sp->sql_pool[index].index);       }   }   else if (op == 0)   {      endindex = sp->pool_number - Num -1;      //减少连接      for (index=sp->pool_number-1; index>endindex && index>=0; index--)      {                 if (NULL != sp->sql_pool[index].mysql_sock)          {             mysql_close(sp->sql_pool[index].mysql_sock);             sp->sql_pool[index].mysql_sock = NULL;          }          sp->sql_pool[index].sql_state = DB_DISCONN;           sp->pool_number--;          printf("delete database pool connect:-%d-.\n",sp->sql_pool[index].index);      }   }   return sp;}

五、mian函数调用测试

int main()  {      SQL_CONN_POOL *sp =     sql_pool_create(10, "localhost", 3306, "ceshi", "root", "qc123456");      SQL_NODE *node  = get_db_connect(sp);      SQL_NODE *node2 = get_db_connect(sp);    if (NULL == node)      {         printf("get sql pool node error.\n");         return -1;      }     printf("--%d-- \n", node->index);    printf("busy--%d--\n", sp->busy_number);    if (mysql_query(&(node->fd), "select * from c1"))    {                                                            printf("query error.\n");                                             return -1;                                          }    else          printf("succeed!\n");      changeNodeNum(sp, 0);//减少    changeNodeNum(sp, 1);//增加    sql_pool_destroy(sp);    return 0;  } 

感谢你看完我的文章,希望你的疑惑能够得到解答

原创粉丝点击