APP服务端优化——3.增加数据库连接池

老规矩,先备份。

一、数据库连接池的介绍

  其实就是定义多个数据库连接对象,这里可以用数组,也就是定义一个数据库连接对象数组,然后让每个数据库连接对象都去连接数据库。他们连接好数据库之后,我们又叫他们为数据库连接池。

  当我们要使用数据库时,就从数据库连接池中取出一个对象来使用,通过这个已经连接好数据库的对象来操作数据库。那么我们就不用自己去定义一个数据库连接对象,然后再去等待连接数据库。这样就省下了自己去连接数据库的时间。

  这个像是学校的水房,当我们要打热水了,不是自己去接一个水龙头到学校的锅炉中;而是使用学校已经安装好的水龙头,我们直接使用就可以打到热水了。我们就省下了每次要打热水就要自己接一个水龙头的时间。数据库连接池也是这原理,让数据库连接对象先连接好数据库,这就是先安装好水龙头;然后从数据库连接池中取出一个空闲对象使用,这就像是直接去寻找使用一个空闲的水龙头。因为有时候不只是你要使水龙头,或者不止一个线程要使用数据库;而是多个人,多个线程要使用。

  所以你要检查一下那些水龙头是已经被使用了,这里我们可以理解为被人使用了,就相当于被人上锁了。在线程中也是,要检查哪些数据库连接对象是被上锁的,哪些是空闲的。有多少个水龙头就需要多少把锁,有多少个数据库连接对象就有多少把锁,这样有人使用的时候,就可以用这些锁来上锁了。

这里用互斥锁

二、数据库连接池的实现

1.定义数据连接对象数组和锁

  这一步其实就是定义数据库连接池的大小。也就是要在水房中装多少个水龙头。有多少个数据库连接池就有多少把锁
在这里插入图片描述

2.初始化数据库连接池

  这个步骤就是让数据库连接对象去连接数据库,还有初始化锁就是把锁做出来。这里最好是写一个日志
在这里插入图片描述

在这里插入图片描述
  有多个数据库连接对象,多把锁,也已经知道数目,用一个for来初始化。连接成功一个数据库,就创建一个把锁。
在这里插入图片描述

3.将初始化锁放在客户端连接上来前

  因为客户端连接到了服务端,就是要使用数据库连接对象和锁。那么我们要在客户端连接上来之前将数据库连接对象准备好(初始化)。

  当创建数据库连接池失败时,要将已经连接的socket关闭,所以要调用EXIT()
在这里插入图片描述
在这里插入图片描述

三、数据库连接池的使用

  创建好了数据库连接池,那么接下来就是要使用数据库连接池了。

1.函数去掉数据库连接对象参数

  在之前没有采用数据库连接池时,是在线程的主函数中连接数据库,然后将数据库连接对象传给各个函数,现在需要将函数的这个参数去掉,因为我们不用传参数了,直接使用数据库连接池中的对象。
在这里插入图片描述

2.从数据库连接池中获取一个连接对象

  当需要连接数据库时,我们就从数据库连接池中获取一个数据库连接对象,那要怎么获取呢?需要定义一个函数,去从连接池中遍历,如果遇到一个空闲的对象,就返回那个对象。

(1)定义获取一个连接对象函数 getconns(connection *conn)

下面步骤的代码改为这样:
在这里插入图片描述

(1.1)如何判断某个对象是空闲的

  获取数据库连接池的方法就是,去遍历连接池,也就是遍历数组,看哪个连接对象没有被上锁。那没有被上锁的状态是什么样的呢?

  其实要看某个对象有没有被上锁,可能我们是无法看出来,就像去上厕所一样。可能表面看不出来某个坑位是否被使用了,那么就去推一下门,如果能推进去说明我们就可以用了。

那么看连接池中的连接对象是否被上锁了或者说是否是空闲的,也是一样的道理。我们去给那个对象加锁,如果加锁成功了就说明这个对象没有被使用,是空闲的;如果加锁失败了,就是正在被使用的。
在这里插入图片描述

(1.2)如果连接池中没有空闲的对象了怎么处理

  在上面的步骤中,如果没有空闲的连接对象,就直接return false 了。正确的做法是,没有空闲的了,那么我就等一段时间再去看看有没有空闲的,如果超过规定的次数了,还是没有那么我就走了。就是轮询

等待一段时间,我们利用sleep,再加上一个循环来实现。
在这里插入图片描述

(2)使用数据库连接对象

  下面的步骤将要操作数据库,所以我们定义一个数据库连接对象的指针,并且传给获取对象的函数。这样就能获得一个数据库连接对象。
在这里插入图片描述

3.解锁

(1)解锁的思路

  当线程使用完了数据库,不需要使用了,那么就将数据库连接对象解锁(不是摧毁),就像上厕所一样,上完了之后要将门解锁,走出去给下一个人使用。数据库连接对象解锁了,给另外的需要的线程使用。

  解锁的思路就是在连接池中找到线程正在使用的对象,然后解锁。在这里插入图片描述

(2)线程中解锁的位置

(2.1)操作数据库失败后

  当线程操作数据库失败,比如主键冲突,会返回;此时也要注意解锁
在这里插入图片描述

(2.2)线程使用完了数据库,不再对数据库进行操作了

  比如说一个业务要进行三次数据库操作,不能操作了一次就解锁了。
在这里插入图片描述

4.释放数据库连接对象和摧毁锁

  在线程退出时,要将数据库连接对象释放和摧毁锁;我们将释放数据库连接对象和锁的事情放在线程退出时调用的函数 EXIT 中。我们自定义一个释放函数
在这里插入图片描述

四、注意事项

1.要采用非阻塞加锁

  给数据库连接对象加锁的时候,要采用非阻塞的方式,不用阻塞的方式。我们正常的流程是,先看看第一个有没有锁,如果有就去看第二个,不是一直在那里等,这个就是非阻塞加锁。如果是阻塞加锁,如果看的那个对象被加锁了,我们就一直在那里等待它解锁,不去看其他的。
在这里插入图片描述

2.解锁的位置

(1)有return的地方就释放

  当业务函数中有return 的地方都要解锁,不然连接对象一下子就耗光了。
在这里插入图片描述

(2)每次操作数据库才获取连接,操作完一次就释放

在这里插入图片描述
在这里插入图片描述

五、源码

#include "_freecplus.h"
#include "_ooci.h"

#define MAXCONNS 10  // 数据库连接池的大小。
pthread_mutex_t mutex[MAXCONNS];  // 锁数组。
connection conns[MAXCONNS];  // 数据库连接数组。
bool initconns();   // 初始数据库连接池。
connection *getconns();  // 从连接池中获取一个数据库连接。
bool freeconns(connection *in_conn);  // 释放数据库连接。

// 业务请求
struct st_biz
{
  int  bizid;               // 业务代码
  char userid[51];          // 设备ID
  int  ttytype;             // 用户的设备类型,0-未知;1-IOS;2-Andriod,2-鸿蒙。
  int  usertype;            // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
  double lon;
  double lat;
  double height;
  char   obtid[11];
  char   xmlbuffer[1001];
};

// 把xml解析到参数stbiz结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz);

CTcpServer TcpServer;
CLogFile   logfile;

// 程序退出时调用的函数
void EXIT(int sig);

// 与客户端通信线程的主函数
void *pth_main(void *arg);

// 心跳业务
bool biz10000(int clientfd);

// 新用户登录业务
bool biz10001(struct st_biz *stbiz,int clientfd);

// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd);

// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn);

// 存放客户端已连接的socket的容器
vector<int> vclientfd;
void AddClient(int clientfd);      // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);   // 关闭客户端的socket并从vclientfd容器中删除,

// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd);

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("\n");
    printf("Using:/htidc/shtqapp1/bin/shtqappserver1 logfilename port\n");

    printf("Example:/htidc/shtqapp1/bin/shtqappserver1 /log/shtqapp/shtqappserver1.log 5015\n\n");
    printf("本程序是上海天气APP软件的服务端。\n");
    printf("logfilename 日志文件名。\n");
    printf("port 用于传输文件的TCP端口。\n");

    return -1;
  }

  // 关闭全部的信号和输入输出
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
  // 但请不要用 "kill -9 +进程号" 强行终止
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);

  // 打开程序运行日志,这是一个多进程程序,日志不能自动切换
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
  }

  logfile.Write("shtqappserver started(%s).\n",argv[2]);

  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); EXIT(-1);
  }

  // 保存服务端的listenfd到vclientfd
  AddClient(TcpServer.m_listenfd);

  if (initconns()==false)  // 初始化数据库连接池。
  {
    logfile.Write("initconns() failed.\n"); EXIT(-1);
  }

  while (true)
  {
    // 等待客户端的连接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); continue;
    }

    pthread_t pthid;   // 创建一线程,与新连接上来的客户端通信
    if (pthread_create(&pthid,NULL,pth_main,(void*)(long)TcpServer.m_connfd)!=0)
    {
      logfile.Write("创建线程失败,程序退出。n"); close(TcpServer.m_connfd); EXIT(-1);
    }

    logfile.Write("%s is connected.\n",TcpServer.GetIP());

    // 保存每个客户端的socket到vclientfd
    AddClient(TcpServer.m_connfd);
  }

  return 0;
}

// 退出时调用的函数
void EXIT(int sig)
{
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

  if (sig>0) signal(sig,SIG_IGN);

  logfile.Write("tcpfileserver1 exit,sig=%d...\n",sig);

  // 关闭vclientfd容器中全部的socket
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    close(vclientfd[ii]);
  }

  for (int ii=0;ii<MAXCONNS;ii++)
  {
    logfile.Write("disconnect and pthread_mutex_destroy.\n");
    conns[ii].disconnect();
    pthread_mutex_destroy(&mutex[ii]);
  }

  exit(0);
}

// 与客户端通信线程的主函数
void *pth_main(void *arg)
{
  int clientfd=(long) arg; // arg参数为新客户端的socket。

  pthread_detach(pthread_self());

  struct st_biz stbiz;

  int  ibuflen=0;
  char strRecvBuffer[1024]; // 接收报文的缓冲区

  while (true)
  {
    memset(strRecvBuffer,0,sizeof(strRecvBuffer));

    // 接收客户端的业务请求报文,如果返回false,认为是客户端退出或网络原因,不写错误日志
    if (TcpRead(clientfd,strRecvBuffer,&ibuflen,50) == false)
    {
      // logfile.Write("TcpRead() failed.\n"); 
      break;
    }

    logfile.Write("strRecvBuffer=%s\n",strRecvBuffer);  // xxxxxx

    // 把参数解析出来
    xmltobiz(strRecvBuffer,&stbiz);

    if (stbiz.bizid==10000)    // 心跳报文
    {
      if (biz10000(clientfd)==true) continue;
      else break;
    }

    // 新用户登录 
    if (stbiz.bizid==10001)    
    {
      connection *conn = getconn();
      if (biz10001(&stbiz,clientfd,conn)==true) 
      freeconn(conn);
      continue;
      else break;
    }

    // 获取天气实况
    if (stbiz.bizid==10002)    
    {
      if (biz10002(&stbiz,clientfd)==true) continue;
      else break;
    }

    // 体力活

    logfile.Write("非法报文%s\n",strRecvBuffer); break;
  }

  RemoveClient(clientfd);

  pthread_exit(0);
}

// 把xml解析到参数starg结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz)
{
  memset(stbiz,0,sizeof(struct st_biz));

  // 业务代码
  GetXMLBuffer(strxmlbuffer,"bizid",&stbiz->bizid);
  // logfile.Write("bizid=%d\n",stbiz->bizid);

  // 用户设备ID
  GetXMLBuffer(strxmlbuffer,"userid",stbiz->userid,50);
  // logfile.Write("userid=%s\n",stbiz->userid);

  GetXMLBuffer(strxmlbuffer,"obtid",stbiz->obtid,10);
  // logfile.Write("obtid=%s\n",stbiz->obtid);

  GetXMLBuffer(strxmlbuffer,"lat",&stbiz->lat);
  // logfile.Write("lat=%lf\n",stbiz->lat);

  GetXMLBuffer(strxmlbuffer,"lon",&stbiz->lon);
  // logfile.Write("lon=%lf\n",stbiz->lon);

  GetXMLBuffer(strxmlbuffer,"height",&stbiz->height);
  // logfile.Write("height=%lf\n",stbiz->height);

  strncpy(stbiz->xmlbuffer,strxmlbuffer,1000);

  return;
}

// 心跳业务
bool biz10000(int clientfd)
{
  char strSendBuffer[1024]; // 发送报文的缓冲区

  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"ok");

  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10000 TcpWrite() failed.\n"); return false;
  }

  return true;
}

// 新用户登录
bool biz10001(struct st_biz *stbiz,int clientfd)
{
  CTimer Timer;
  char strSendBuffer[1024]; // 发送报文的缓冲区
  
  connection *conn=getconns();  // 获取一个数据库连接。

  // 插入用户基本信息表T_USERINFO
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->ttytype);
  if (stmt.execute() != 0)
  {
    if (stmt.m_cda.rc!=1)
    {
      logfile.Write("insert T_USERINFO failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
    }
  }

  logfile.Write("insert T_USERINFO =%lf\n",Timer.Elapsed());

  // 插入用户请求日志表
  if (InsertUSERLOG(stbiz,conn)==false) { freeconns(conn); return false; }

  logfile.Write("insert T_USERLOG =%lf\n",Timer.Elapsed());

  char strobtid[6],strobtname[31],strlon[11],strlat[11];
  stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
  stmt.bindout(1,strobtid,5);
  stmt.bindout(2,strobtname,30);
  stmt.bindout(3,strlon,10);
  stmt.bindout(4,strlat,10);
  if (stmt.execute() != 0)
  {
    logfile.Write("select T_OBTCODE failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
  }

  while (true)
  {
    memset(strobtid,0,sizeof(strobtid)); 
    memset(strobtname,0,sizeof(strobtname));
    memset(strlon,0,sizeof(strlon)); 
    memset(strlat,0,sizeof(strlat));
    memset(strSendBuffer,0,sizeof(strSendBuffer));

    if (stmt.next()!=0) break;

    sprintf(strSendBuffer,"<obtid>%s</obtid><obtname>%s</obtname><lon>%s</lon><lat>%s</lat><endl/>",strobtid,strobtname,strlon,strlat);

    if (TcpWrite(clientfd,strSendBuffer) == false)
    {
      logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
    }
  }

  logfile.Write("select =%lf\n",Timer.Elapsed());

  // 最后发送一个ok
  strcpy(strSendBuffer,"ok");
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
  }

  freeconns(conn);

  return true;
}

// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn)
{
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERLOG(logid,userid,atime,bizid,obtid,lon,lat,height,xmlbuffer) values(SEQ_USERLOG.nextval,:1,sysdate,:2,:3,:4,:5,:6,:7)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->bizid);
  stmt.bindin(3, stbiz->obtid,10);
  stmt.bindin(4,&stbiz->lon);
  stmt.bindin(5,&stbiz->lat);
  stmt.bindin(6,&stbiz->height);
  stmt.bindin(7, stbiz->xmlbuffer,10000);

  if (stmt.execute() != 0)
  {
    logfile.Write("insert T_USERLOG failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
  }

  return true;
}

// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd)
{

   return true;
}

// 把客户端新的socket加入vclientfd容器中
void AddClient(int clientfd)
{
  vclientfd.push_back(clientfd);
}

// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd)
{
  for (int ii=0;ii<vclientfd.size();ii++)
  {
    if (vclientfd[ii]==clientfd) 
    { close(clientfd); vclientfd.erase(vclientfd.begin()+ii); return; }
  }
}

bool initconns()   // 初始数据库连接池
{
  for (int ii=0;ii<MAXCONNS;ii++)
  {
    logfile.Write("%d,connecttodb and pthread_mutex_init.\n",ii);

    // 连接数据库
    if (conns[ii].connecttodb("shtqapp/pwdidc@snorcl11g_198","Simplified Chinese_China.ZHS16GBK",true)!=0)
    {
      logfile.Write("conns[%d].connettodb() failed.\n",ii); return false;
    }

    pthread_mutex_init(&mutex[ii],0); // 创建锁
  }

  return true;
}

connection *getconns()
{
  // for (int jj=0;jj<1000;jj++)
  while (true)
  {
    for (int ii=0;ii<MAXCONNS;ii++)
    {
      if (pthread_mutex_trylock(&mutex[ii])==0) 
      {
        // logfile.Write("jj=%d,ii=%d\n",jj,ii);
        logfile.Write("get conns is %d.\n",ii);
        return &conns[ii]; 
      }
    }
  
    usleep(10000);
  }
}

bool freeconns(connection *in_conn)
{
  for (int ii=0;ii<MAXCONNS;ii++)
  {
    if (in_conn==&conns[ii]) 
    {
      logfile.Write("free conn %d.\n",ii);
      pthread_mutex_unlock(&mutex[ii]); in_conn=0; return true;
    }
  }

  return false;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值