【C++】web接口服务端 线程池+数据库连接池+生产消费者模型

/*
 * 程序名:webserver.cpp,此程序是数据服务总线的服务端程序。
 * 作者:毛毛
*/
#include "_public.h"
#include "_ooci.h"

CLogFile   logfile;    // 服务程序的运行日志。
CTcpServer TcpServer;  // 创建服务端对象。

void EXIT(int sig);    // 进程的退出函数。

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;    // 初始化互斥锁。
pthread_cond_t  cond =PTHREAD_COND_INITIALIZER;     // 初始化条件变量。
vector<int> sockqueue;                              // 客户端socket的队列。
/*
struct st_clientifno
{
  int  sockid;  // 客户端的socket。
  char ip[16];  // 客户端的ip地址。
};
*/

// 线程信息的结构体。
struct st_pthinfo
{
  pthread_t pthid;       // 线程id。
  time_t    atime;       // 最近一次活动的时间。
};

pthread_spinlock_t spin;         // 用于锁定vthid的自旋锁。
vector<struct st_pthinfo> vthid; // 存放全部线程信息的容器。
void *thmain(void *arg);         // 工作线程主函数。

pthread_t checkpthid;
void *checkthmain(void *arg);    // 监控线程主函数。

void thcleanup(void *arg);       // 线程清理函数。

pthread_t checkpoolid;
void *checkpool(void *arg);      // 检查数据库连接池的线程函数。

// 主程序参数的结构体。
struct st_arg
{
  char connstr[101];  // 数据库的连接参数。
  char charset[51];   // 数据库的字符集。
  int  port;          // web服务监听的端口。
} starg;

// 显示程序的帮助
void _help(char *argv[]);

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer);

// 读取客户端的报文。
int ReadT(const int sockfd,char *buffer,const int size,const int itimeout);

// 从GET请求中获取参数。
bool getvalue(const char *buffer,const char *name,char *value,const int len);

// 判断URL中用户名和密码,如果不正确,返回认证失败的响应报文。
bool Login(connection *conn,const char *buffer,const int sockfd);

// 判断用户是否有调用接口的权限,如果没有,返回没有权限的响应报文。
bool CheckPerm(connection *conn,const char *buffer,const int sockfd);

// 执行接口的sql语句,把数据返回给客户端。
bool ExecSQL(connection *conn,const char *buffer,const int sockfd);

// 数据库连接池类。
class connpool
{
private:
  struct st_conn
  {
    connection conn;            // 数据库连接。
    pthread_mutex_t mutex;      // 用于数据库连接的互斥锁。
    time_t atime;               // 数据库连接上次使用的时间,如果未连接数据库则取值0。
  }*m_conns;                    // 数据库连接池。

  int  m_maxconns;              // 数据库连接池的最大值。
  int  m_timeout;               // 数据库连接超时时间,单位:秒。
  char m_connstr[101];          // 数据库连接参数:用户名/密码@连接名
  char m_charset[101];          // 数据库的字符集。
public:
  connpool();      // 构造函数。
 ~connpool();      // 析构函数。

  // 初始化数据库连接池,初始化锁,如果数据库连接参数有问题,返回false。
  bool init(const char *connstr,const char *charset,int maxconns,int timeout);
  // 断开数据库连接,销毁锁,释放数据库连接池的内存空间。
  void destroy();

  // 从数据库连接池中获取一个空闲的连接,成功返回数据库连接的地址。
  // 如果连接池已用完或连接数据库失败,返回空。
  connection *get();
  // 归还数据库连接。
  bool free(connection *conn);

  // 检查数据库连接池,断开空闲的连接,在服务程序中,用一个专用的子线程调用此函数。
  void checkpool();
};

connpool oraconnpool;  // 声明数据库连接池对象。

int main(int argc,char *argv[])
{
  if (argc!=3) { _help(argv); return -1; }

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

  if (logfile.Open(argv[1],"a+")==false) { printf("logfile.Open(%s) failed.\n",argv[1]); return -1; }

  // 把xml解析到参数starg结构中
  if (_xmltoarg(argv[2])==false) return -1;

  // 服务端初始化。
  if (TcpServer.InitServer(starg.port)==false)
  {
    logfile.Write("TcpServer.InitServer(%d) failed.\n",starg.port); return -1;
  }

  // 初始化数据库连接池。
  if (oraconnpool.init(starg.connstr,starg.charset,10,50)==false)
  {
    logfile.Write("oraconnpool.init() failed.\n"); return -1;
  }
  else
  {
    // 创建检查数据库连接池的线程。
    if (pthread_create(&checkpoolid,NULL,checkpool,0)!=0)
    {
      logfile.Write("pthread_create() failed.\n"); return -1;
    }
  }

  // 启动10个工作线程,线程数比CPU核数略多。
  for (int ii=0;ii<10;ii++)
  {
    struct st_pthinfo stpthinfo;
    if (pthread_create(&stpthinfo.pthid,NULL,thmain,(void *)(long)ii)!=0)
    {
      logfile.Write("pthread_create() failed.\n"); return -1;
    }

    stpthinfo.atime=time(0);        // 把结程的活动时间置为当前时间。
    vthid.push_back(stpthinfo);     // 把线程结构体保存到vthid容器中。
  }

  // 创建监控线程。
  if (pthread_create(&checkpthid,NULL,checkthmain,(void *)0)!=0)
  {
    logfile.Write("pthread_create() failed.\n"); return -1;
  }

  // 如果采用了黑名单或者白名单方法,把黑名单和白名单用参数表管理起来,
  // 服务程序启动的时候,把表中的参数加载到容器中,服务程序在运行的过程中,
  // 专门用一个线程定期的重新加载参数,每隔几分钟加载一次就行了。

  pthread_spin_init(&spin,0);       // 初始化给vthid加锁的自旋锁。

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

    logfile.Write("客户端(%s)已连接。\n",TcpServer.GetIP());

    // 把客户端的socket放入队列,并发送条件信号。
    pthread_mutex_lock(&mutex);               // 加锁。
    sockqueue.push_back(TcpServer.m_connfd);  // 入队。
    pthread_mutex_unlock(&mutex);             // 解锁。
    pthread_cond_signal(&cond);               // 触发条件,激活一个线程。
  }
}

// 工作线程主函数。
void *thmain(void *arg)     
{
  int pthnum=(int)(long)arg;            // 线程的编号。

  pthread_cleanup_push(thcleanup,arg);  // 线程清理函数。

  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);   // 线程取消方式为立即取消。

  pthread_detach(pthread_self());       // 把线程分离出去。

  int  connfd;               // 客户端的socket。
  char strrecvbuf[1024];     // 接收客户端请求报文的buffer。
  char strsendbuf[1024];     // 向客户端发送回应报文的buffer。

  while (true)
  {
    pthread_mutex_lock(&mutex);  // 给缓存队列加锁。

    // 如果缓存队列为空,等待,用while防止条件变量虚假唤醒。
    while (sockqueue.size()==0)
    {
      struct timeval now;
      gettimeofday(&now,NULL);            // 获取当前时间。
      now.tv_sec=now.tv_sec+20;           // 取20秒之后的时间。
      pthread_cond_timedwait(&cond,&mutex,(struct timespec*)&now);    // 等待条件被触发。
      vthid[pthnum].atime=time(0);        // 更新当前线程的活动时间。
    }

    // 从缓存队列中获取第一条记录,然后删除该记录。
    connfd=sockqueue[0];
    sockqueue.erase(sockqueue.begin());

    pthread_mutex_unlock(&mutex);  // 给缓存队列解锁。

    // 以下是处理业务的代码。
    logfile.Write("phid=%lu(num=%d),connfd=%d\n",pthread_self(),pthnum,connfd);

    // 读取客户端的报文,如果超时或失败,关闭客户端的socket,继续回到循环。
    memset(strrecvbuf,0,sizeof(strrecvbuf));
    if (ReadT(connfd,strrecvbuf,sizeof(strrecvbuf),30000)<=0) { close(connfd); continue; }

    // 如果不是GET请求报文不处理,关闭客户端的socket,继续回到循环。
    if (strncmp(strrecvbuf,"GET",3)!=0) { close(connfd); continue; }

    logfile.Write("%s\n",strrecvbuf);

    connection *conn=oraconnpool.get();   // 获取一个数据库连接。

    // 如果数据库连接为空,向客户端返回内部错误,关闭客户端的socket,继续回到循环。
    if (conn==0)
    {
      memset(strsendbuf,0,sizeof(strsendbuf));
      sprintf(strsendbuf,\
             "HTTP/1.1 200 OK\r\n"\
             "Server: webserver\r\n"\
             "Content-Type: text/html;charset=utf-8\r\n\r\n"\
             "<retcode>-1</retcode><message>internal error.</message>");
      Writen(connfd,strsendbuf,strlen(strsendbuf));
      close(connfd); continue;
    }

    // 判断URL中用户名和密码,如果不正确,返回认证失败的响应报文,关闭客户端的socket,继续回到循环。
    if (Login(conn,strrecvbuf,connfd)==false) { oraconnpool.free(conn); close(connfd); continue; }

    // 判断用户是否有调用接口的权限,如果没有,返回没有权限的响应报文,关闭客户端的socket,继续回到循环。
    if (CheckPerm(conn,strrecvbuf,connfd)==false) { oraconnpool.free(conn); close(connfd); continue; }

    // 先把响应报文头部发送给客户端。
    memset(strsendbuf,0,sizeof(strsendbuf));
    sprintf(strsendbuf,\
           "HTTP/1.1 200 OK\r\n"\
           "Server: webserver\r\n"\
           "Content-Type: text/html;charset=utf-8\r\n\r\n");
    Writen(connfd,strsendbuf,strlen(strsendbuf));

    // 再执行接口的sql语句,把数据返回给客户端。
    if (ExecSQL(conn,strrecvbuf,connfd)==false) { oraconnpool.free(conn); close(connfd); continue; }

    oraconnpool.free(conn);;

    close(connfd);
  }

  pthread_cleanup_pop(1);         // 把线程清理函数出栈。
}

// 进程的退出函数。
void EXIT(int sig)  
{
  // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

  logfile.Write("进程退出,sig=%d。\n",sig);

  TcpServer.CloseListen();    // 关闭监听的socket。

  // 取消全部的线程。
  pthread_spin_lock(&spin);
  for (int ii=0;ii<vthid.size();ii++)
  {
    pthread_cancel(vthid[ii].pthid);
  }
  pthread_spin_unlock(&spin);

  sleep(1);        // 让子线程有足够的时间退出。

  pthread_cancel(checkpthid);   // 取消监控线程。
  pthread_cancel(checkpoolid);  // 取消检查数据库连接池的线程。

  pthread_spin_destroy(&spin);
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);

  exit(0);
}

void thcleanup(void *arg)     // 线程清理函数。
{
  pthread_mutex_unlock(&mutex);

  // 把本线程的结构体从存放线程结构体的容器中删除。
  pthread_spin_lock(&spin);
  for (int ii=0;ii<vthid.size();ii++)
  {
    if (pthread_equal(pthread_self(),vthid[ii].pthid)) { vthid.erase(vthid.begin()+ii); break; }
  }
  pthread_spin_unlock(&spin);

  logfile.Write("线程%d(%lu)退出。\n",(int)(long)arg,pthread_self());
}

// 显示程序的帮助
void _help(char *argv[])
{
  printf("Using:/project/tools1/bin/webserver logfilename xmlbuffer\n\n");

  printf("Sample:/project/tools1/bin/procctl 10 /project/tools1/bin/webserver /log/idc/webserver.log \"<connstr>scott/tiger@snorcl11g_132</connstr><charset>Simplified Chinese_China.AL32UTF8</charset><port>8080</port>\"\n\n");

  printf("本程序是数据总线的服务端程序,为数据中心提供http协议的数据访问接口。\n");
  printf("logfilename 本程序运行的日志文件。\n");
  printf("xmlbuffer   本程序运行的参数,用xml表示,具体如下:\n\n");

  printf("connstr     数据库的连接参数,格式:username/password@tnsname。\n");
  printf("charset     数据库的字符集,这个参数要与数据源数据库保持一致,否则会出现中文乱码的情况。\n");
  printf("port        web服务监听的端口。\n\n");
}

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));

  GetXMLBuffer(strxmlbuffer,"connstr",starg.connstr,100);
  if (strlen(starg.connstr)==0) { logfile.Write("connstr is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"charset",starg.charset,50);
  if (strlen(starg.charset)==0) { logfile.Write("charset is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"port",&starg.port);
  if (starg.port==0) { logfile.Write("port is null.\n"); return false; }

  return true;
}

// 读取客户端的报文。
int ReadT(const int sockfd,char *buffer,const int size,const int itimeout)
{
  if (itimeout > 0)
  {
    struct pollfd fds;
    fds.fd=sockfd;
    fds.events=POLLIN;
    int iret;
    if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 ) return iret;
  }

  return recv(sockfd,buffer,size,0);
}

// 判断URL中用户名和密码,如果不正确,返回认证失败的响应报文。
bool Login(connection *conn,const char *buffer,const int sockfd)
{
  char username[31],passwd[31];

  getvalue(buffer,"username",username,30); // 获取用户名。
  getvalue(buffer,"passwd",passwd,30);     // 获取密码。

  // 查询T_USERINFO表,判断用户名和密码是否存在。
  sqlstatement stmt;
  stmt.connect(conn);
  stmt.prepare("select count(*) from T_USERINFO where username=:1 and passwd=:2 and rsts=1");
  stmt.bindin(1,username,30);
  stmt.bindin(2,passwd,30);
  int icount=0;
  stmt.bindout(1,&icount);
  stmt.execute();
  stmt.next();

  if (icount==0)   // 认证失败,返回认证失败的响应报文。
  {
    char strbuffer[256];
    memset(strbuffer,0,sizeof(strbuffer));

    sprintf(strbuffer,\
           "HTTP/1.1 200 OK\r\n"\
           "Server: webserver\r\n"\
           "Content-Type: text/html;charset=utf-8\r\n\r\n"\
           "<retcode>-1</retcode><message>username or passwd is invailed</message>");
    Writen(sockfd,strbuffer,strlen(strbuffer));

    return false;
  }

  return true;
}

// 从GET请求中获取参数。
bool getvalue(const char *buffer,const char *name,char *value,const int len)
{
  value[0]=0;

  char *start,*end;
  start=end=0;

  start=strstr((char *)buffer,(char *)name);
  if (start==0) return false;

  end=strstr(start,"&");
  if (end==0) end=strstr(start," ");

  if (end==0) return false;

  int ilen=end-(start+strlen(name)+1);
  if (ilen>len) ilen=len;

  strncpy(value,start+strlen(name)+1,ilen);

  value[ilen]=0;

  return true;
}

// 判断用户是否有调用接口的权限,如果没有,返回没有权限的响应报文。 
bool CheckPerm(connection *conn,const char *buffer,const int sockfd)
{
  char username[31],intername[30];

  getvalue(buffer,"username",username,30);    // 获取用户名。
  getvalue(buffer,"intername",intername,30);  // 获取接口名。

  sqlstatement stmt;
  stmt.connect(conn);
  stmt.prepare("select count(*) from T_USERANDINTER where username=:1 and intername=:2 and intername in (select intername from T_INTERCFG where rsts=1)");
  stmt.bindin(1,username,30);
  stmt.bindin(2,intername,30);
  int icount=0;
  stmt.bindout(1,&icount);
  stmt.execute();
  stmt.next();

  if (icount!=1)
  {
    char strbuffer[256];
    memset(strbuffer,0,sizeof(strbuffer));

    sprintf(strbuffer,\
           "HTTP/1.1 200 OK\r\n"\
           "Server: webserver\r\n"\
           "Content-Type: text/html;charset=utf-8\r\n\n\n"\
           "<retcode>-1</retcode><message>permission denied</message>");

    Writen(sockfd,strbuffer,strlen(strbuffer));

    return false;
  }

  return true;
}

// 执行接口的sql语句,把数据返回给客户端。
bool ExecSQL(connection *conn,const char *buffer,const int sockfd)
{
  // 从请求报文中解析接口名。
  char intername[30];
  memset(intername,0,sizeof(intername));
  getvalue(buffer,"intername",intername,30);  // 获取接口名。

  // 从接口参数配置表T_INTERCFG中加载接口参数。
  char selectsql[1001],colstr[301],bindin[301];
  memset(selectsql,0,sizeof(selectsql)); // 接口SQL。
  memset(colstr,0,sizeof(colstr));       // 输出列名。
  memset(bindin,0,sizeof(bindin));       // 接口参数。
  sqlstatement stmt;
  stmt.connect(conn);
  stmt.prepare("select selectsql,colstr,bindin from T_INTERCFG where intername=:1");
  stmt.bindin(1,intername,30);     // 接口名。
  stmt.bindout(1,selectsql,1000);  // 接口SQL。
  stmt.bindout(2,colstr,300);      // 输出列名。
  stmt.bindout(3,bindin,300);      // 接口参数。
  stmt.execute();  // 这里基本上不用判断返回值,出错的可能几乎没有。
  stmt.next();

  // 准备查询数据的SQL语句。
  stmt.prepare(selectsql);

  // http://192.168.174.132:8080?username=ty&passwd=typwd&intername=getzhobtmind3&obtid=59287&begintime=20211024094318&endtime=20211024113920
  // SQL语句:   select obtid,to_char(ddatetime,'yyyymmddhh24miss'),t,p,u,wd,wf,r,vis from T_ZHOBTMIND where obtid=:1 and ddatetime>=to_date(:2,'yyyymmddhh24miss') and ddatetime<=to_date(:3,'yyyymmddhh24miss')
  // colstr字段:obtid,ddatetime,t,p,u,wd,wf,r,vis
  // bindin字段:obtid,begintime,endtime

  // 绑定查询数据的SQL语句的输入变量。
  // 根据接口配置中的参数列表(bindin字段),从URL中解析出参数的值,绑定到查询数据的SQL语句中。
  //////////////////////////////////////////////////
  // 拆分输入参数bindin。
  CCmdStr CmdStr;
  CmdStr.SplitToCmd(bindin,",");

  // 声明用于存放输入参数的数组,输入参数的值不会太长,100足够。
  char invalue[CmdStr.CmdCount()][101];
  memset(invalue,0,sizeof(invalue));

  // 从http的GET请求报文中解析出输入参数,绑定到sql中。
  for (int ii=0;ii<CmdStr.CmdCount();ii++)
  {
    getvalue(buffer,CmdStr.m_vCmdStr[ii].c_str(),invalue[ii],100);
    stmt.bindin(ii+1,invalue[ii],100);
  }
  //////////////////////////////////////////////////

  // 绑定查询数据的SQL语句的输出变量。
  // 根据接口配置中的列名(colstr字段),bindout结果集。
  //////////////////////////////////////////////////
  // 拆分colstr,可以得到结果集的字段数。
  CmdStr.SplitToCmd(colstr,",");

  // 用于存放结果集的数组。
  char colvalue[CmdStr.CmdCount()][2001];

  // 把结果集绑定到colvalue数组。
  for (int ii=0;ii<CmdStr.CmdCount();ii++)
  {
    stmt.bindout(ii+1,colvalue[ii],2000);
  }
  //////////////////////////////////////////////////

  // 执行SQL语句
  char strsendbuffer[4001];  // 发送给客户端的xml。
  memset(strsendbuffer,0,sizeof(strsendbuffer));

  if (stmt.execute() != 0)
  {
    sprintf(strsendbuffer,"<retcode>%d</retcode><message>%s</message>\n",stmt.m_cda.rc,stmt.m_cda.message);
    Writen(sockfd,strsendbuffer,strlen(strsendbuffer)); 
    logfile.Write("stmt.execute() failed.\n%s\n%s\n",stmt.m_sql,stmt.m_cda.message); return false;
  }
  strcpy(strsendbuffer,"<retcode>0</retcode><message>ok</message>\n");
  Writen(sockfd,strsendbuffer,strlen(strsendbuffer)); 

  // 向客户端发送xml内容的头部标签<data>。
  Writen(sockfd,"<data>\n",strlen("<data>\n")); 

  // 获取结果集,每获取一条记录,拼接xml报文,发送给客户端。
  //////////////////////////////////////////////////
  char strtemp[2001];        // 用于拼接xml的临时变量。

  // 逐行获取结果集,发送给客户端。
  while (true)
  {
    memset(strsendbuffer,0,sizeof(strsendbuffer));
    memset(colvalue,0,sizeof(colvalue));

    if (stmt.next() != 0) break;  // 从结果集中取一条记录。

    // 拼接每个字段的xml。
    for (int ii=0;ii<CmdStr.CmdCount();ii++)
    {
      memset(strtemp,0,sizeof(strtemp));
      snprintf(strtemp,2000,"<%s>%s</%s>",CmdStr.m_vCmdStr[ii].c_str(),colvalue[ii],CmdStr.m_vCmdStr[ii].c_str());
      strcat(strsendbuffer,strtemp);
    }

    strcat(strsendbuffer,"<endl/>\n");   // xml每行结束的标志。

    Writen(sockfd,strsendbuffer,strlen(strsendbuffer));   // 向客户端返回这行数据。 
  }  
  //////////////////////////////////////////////////

  // 向客户端发送xml内容的尾部标签</data>。
  Writen(sockfd,"</data>\n",strlen("</data>\n"));

  logfile.Write("intername=%s,count=%d\n",intername,stmt.m_cda.rpc);

  // 写接口调用日志表T_USERLOG。

  return true;
}

connpool::connpool()
{
  m_maxconns=0;
  m_timeout=0;
  memset(m_connstr,0,sizeof(m_connstr));
  memset(m_charset,0,sizeof(m_charset));
  m_conns=0;
}

// 初始化数据库连接池,初始化锁,如果数据库连接参数有问题,返回false。
bool connpool::init(const char *connstr,const char *charset,const int maxconns,int timeout)     
{
  // 尝试数据库,验证数据库连接参数是否正确。
  connection conn;
  if (conn.connecttodb(connstr,charset)!=0)
  {
    printf("连接数据库失败。\n%s\n",conn.m_cda.message); return false;
  }
  conn.disconnect();

  strncpy(m_connstr,connstr,100);
  strncpy(m_charset,charset,100);
  m_maxconns=maxconns;
  m_timeout=timeout;

  // 分配数据库连接池内存空间。
  m_conns=new struct st_conn[m_maxconns];

  for (int ii=0;ii<m_maxconns;ii++)
  {
    pthread_mutex_init(&m_conns[ii].mutex,0);    // 初始化锁。
    m_conns[ii].atime=0;                         // 数据库连接上次使用的时间初初化为0。
  }

  return true;
}

connpool::~connpool()
{
  destroy();
}

// 断开数据库连接,销毁锁,释放连接池。
void connpool::destroy()
{
  for (int ii=0;ii<m_maxconns;ii++)
  {
    m_conns[ii].conn.disconnect();             // 断开数据库连接。
    pthread_mutex_destroy(&m_conns[ii].mutex); // 断开数据库连接。
  }

  delete []m_conns;         // 释放数据库连接池的内存空间。
  m_conns=0;

  memset(m_connstr,0,sizeof(m_connstr));
  memset(m_charset,0,sizeof(m_charset));
  m_maxconns=0;
  m_timeout=0;
}

// 1)从数据库连接池中寻找一个空闲的、已连接好的connection,如果找到了,返回它的地址。
// 2)如果没有找到,在连接池中找一个未连接数据库的connection,连接数据库,如果成功,返回connection的地址。
// 3)如果第2)步找到了未连接数据库的connection,但是,连接数据库失败,返回空。
// 4)如果第2)步没有找到未连接数据库的connection,表示数据库连接池已用完,也返回空。
connection *connpool::get()
{
  int pos=-1;       // 用于记录第一个未连接数据库的数组位置。

  for (int ii=0;ii<m_maxconns;ii++)
  {
    if (pthread_mutex_trylock(&m_conns[ii].mutex)==0)       // 尝试加锁。
    {
      if (m_conns[ii].atime>0)       // 如果数据库连接是已连接的状态。
      {
        printf("取到连接%d。\n",ii); 
        m_conns[ii].atime=time(0);   // 把数据库连接的使用时间设置为当前时间。
        return &m_conns[ii].conn;    // 返回数据库连接的地址。
      }

      if (pos==-1) pos=ii;          // 记录第一个未连接数据库的数组位置。
      else pthread_mutex_unlock(&m_conns[ii].mutex);     // 释放锁。
    }
  }

  if (pos==-1)   // 如果连接池已用完,返回空。
  {
    printf("连接池已用完。\n"); return NULL;
  }

  // 连接池没有用完,让m_conns[pos].conn连上数据库。
  printf("新连接%d。\n",pos);

  // 连接数据库。
  if (m_conns[pos].conn.connecttodb(m_connstr,m_charset)!=0)
  {
    // 如果连接数据库失败,释放锁,返回空。
    printf("连接数据库失败。\n");
    pthread_mutex_unlock(&m_conns[pos].mutex);  // 释放锁。
    return NULL;
  }
  
  m_conns[pos].atime=time(0);      // 把数据库连接的使用时间设置为当前时间。

  return &m_conns[pos].conn;
}

// 归还数据库连接。 
bool connpool::free(connection *conn) 
{
  for (int ii=0;ii<m_maxconns;ii++)
  {
    if (&m_conns[ii].conn==conn)
    {
      printf("归还%d\n",ii);
      m_conns[ii].atime=time(0);      // 把数据库连接的使用时间设置为当前时间。
      pthread_mutex_unlock(&m_conns[ii].mutex);        // 释放锁。
      return true;
    }
  }

  return false;
}

// 检查连接池,断开空闲的连接。
void connpool::checkpool()
{
  for (int ii=0;ii<m_maxconns;ii++)
  {
    if (pthread_mutex_trylock(&m_conns[ii].mutex)==0)  // 尝试加锁。
    {
      if (m_conns[ii].atime>0)    // 如果是一个可用的连接。
      {
        // 判断连接是否超时。
        if ( (time(0)-m_conns[ii].atime)>m_timeout ) 
        {
          printf("连接%d已超时。\n",ii);
          m_conns[ii].conn.disconnect();     // 断开数据库连接。
          m_conns[ii].atime=0;               // 重置数据库连接的使用时间。
        }
        else  
        {
          // 如果没有超时,执行一次sql,验证连接是否有效,如果无效,断开它。
          // 如果网络断开了,或者数据库重启了,那么就需要重连数据库,在这里,只需要断开连接就行了,
          // 重连的操作交给get()函数。
          if (m_conns[ii].conn.execute("select * from dual")!=0)
          {
            printf("连接%d已故障。\n",ii);
            m_conns[ii].conn.disconnect();     // 断开数据库连接。
            m_conns[ii].atime=0;               // 重置数据库连接的使用时间。
          }
        }
      }

      pthread_mutex_unlock(&m_conns[ii].mutex);   // 释放锁。
    }
    
    // 如果尝试加锁失败,表示数据库连接正在使用中,不必检查。
  }
}

void *checkpool(void *arg)    // 检查数据库连接池的线程函数。
{
  while (true)
  {
    oraconnpool.checkpool();  sleep(30);
  }
}

void *checkthmain(void *arg)    // 监控线程主函数。
{
  while (true)
  {
    // 遍历工作线程结构体的容器,检查每个工作线程是否超时。
    for (int ii=0;ii<vthid.size();ii++)
    {
      // 工作线程超时间为20秒,这里用25秒判断超时,足够。
      if ((time(0)-vthid[ii].atime)>25)
      {
        // 已超时。
        logfile.Write("thread %d(%lu) timeout(%d).\n",ii,vthid[ii].pthid,time(0)-vthid[ii].atime);

        // 取消已超时的工作线程。
        pthread_cancel(vthid[ii].pthid);

        // 重新创建工作线程。
        if (pthread_create(&vthid[ii].pthid,NULL,thmain,(void *)(long)ii)!=0)
        {
          logfile.Write("pthread_create() failed.\n"); EXIT(-1);
        }

        vthid[ii].atime=time(0);      // 设置工作线程的活动时间。
      }
    }

    sleep(3);
  } 
}










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值