加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Oracle调用接口(OCI)源码剖析(1):创建数据库连接

发布时间:2020-12-12 16:00:16 所属栏目:百科 来源:网络整理
导读:概述 在笔者所开发过的产品中,有很多都需要与Oracle数据库打交道。为了实现C代码与Oracle数据库的消息交互,Oracle公司为广大的开发者们提供了一个统一的调用接口OCI(Oracle Call Interface)。只要按照规范来调用OCI中的函数,就能够实现C代码与Oracle数据

概述
在笔者所开发过的产品中,有很多都需要与Oracle数据库打交道。为了实现C代码与Oracle数据库的消息交互,Oracle公司为广大的开发者们提供了一个统一的调用接口OCI(Oracle Call Interface)。只要按照规范来调用OCI中的函数,就能够实现C代码与Oracle数据库的交互。

具体而言,OCI的C语言API包括了两个文件:db_ora_oci_ux.h和db_ora_oci_ux.c。db_ora_oci_ux.h是头文件,而所有与数据库的交互操作的实现都是在db_ora_oci_ux.c中完成的。

本文对OCI的创建数据库连接操作的源码进行简单的剖析。

OCI中建立数据库连接的源码剖析
在OCI中,建立数据库连接的操作是由CDbCreateDb函数实现的,其代码如下:

void *CDbCreateDb(INT8 *pDbType,INT8*pServer,INT8 *pDbName,INT8 *pUser,INT8 *pPwd)
{
   CDbRecordset *pcolbuf = NULL;
   OCIHDBC       hdbc    = NULL;
   CDb          *hDb     = NULL;

   if (NULL == pServer)
   {
       WriteLog("CDbCreateDb: CDbCreateDb[0] failed",NULL,NULL);
       return NULL;
   }

   /* 申请句柄指针空间 */
   hDb = (CDb *)OsGetUB(sizeof(CDb));
   if (NULL == hDb)
   {
       WriteLog("CDbCreateDb: CDbCreateDb[1] failed",NULL);
       return NULL;
   }
   hDb->hdbc = NULL;
   hDb->hRec = NULL;

   pthread_mutex_lock(&s_dbmutex);
   /* 初始化数据库连接 */
   hdbc = DoDbInit((text *)pServer,(text *)pUser,(text *)pPwd);
   if (NULL == hdbc)
   {
       pthread_mutex_unlock(&s_dbmutex);
       OsRetUB((UINT8*)hDb);
       return NULL;
   }

   /* 创建结果集 */
   pcolbuf = DoRecInit();
   if (NULL == pcolbuf)
   {
       pthread_mutex_unlock(&s_dbmutex);
       OsRetUB((UINT8*)hDb);
       DoDbFree(hdbc);
       return NULL;
   }
   hDb->hdbc = hdbc;
   hDb->hRec = pcolbuf;
   hDb->iDbType = CDB_TYPE_ORACLE;

   pthread_mutex_unlock(&s_dbmutex);

   returnhDb;
}

从该函数的代码实现中,我们可以看到:
1)建立数据库连接包括这几步操作:第一步,申请句柄指针空间;第二步,初始化数据库连接;第三步,创建结果集。

2)申请句柄指针空间操作是由OsGetUB函数实现的,初始化数据库连接操作是由DoDbInit函数实现的,创建结果集操作是由DoRecInit函数实现的。

3)为了防止在多个流程中同时调用该函数,在初始化数据库连接之前采用了加锁操作,这保证了每一个创建数据库的操作所返回的句柄是唯一的。

4)如果初始化数据库连接操作函数DoDbInit执行失败了,程序就会执行OsRetUB函数来释放句柄指针空间(该操作与之前的申请句柄指针空间操作对应起来)。

5)如果创建结果集操作函数DoRecInit执行失败了,程序除了执行OsRetUB函数来释放句柄指针空间之外,还会执行DoDbFree函数来释放数据库连接(该操作与之前的初始化数据库连接操作对应起来)。

初始化数据库连接操作函数DoDbInit的代码如下:

static void *DoDbInit(text *dblink,text*uid,text *pwd)
{
   OCIHDBC hdbc = NULL;
   sword   rc   = (sword)0;
   char errBuf[200];
   sb4  errcode;

   /* 申请所有句柄指针保存空间 */
   hdbc = (OCIHDBC)OsGetUB(sizeof(t_envctx));
   if (NULL == hdbc)
   {
       WriteLog("DoDbInit: OsGetUB failed",NULL,NULL);
       return NULL;
   }

   /* 创建OCI环境 */
   if (OCIInitialize((ub4)OCI_THREADED|OCI_OBJECT,(dvoid *)0,(dvoid * (*)(dvoid *,size_t))0,dvoid*,(void (*)(dvoid *,dvoid*))0))
   {
       WriteLog("DoDbInit: OCIInitialize fail",NULL);
       return NULL;
   }

   if(OCIEnvInit((OCIEnv **)&hdbc->envhp,(ub4)OCI_DEFAULT,(size_t)0,(dvoid **)0))
   {
       WriteLog("DoDbInit: OCIEnvInit fail",NULL);
       return NULL;
   }

   /*申请错误句柄 */
   if(OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->errhp,(ub4)OCI_HTYPE_ERROR,(dvoid **)0))
   {
       WriteLog("DoDbInit: OCIHandleAlloc allocate errhp fail",NULL);
       return NULL;
   }
   /* 申请服务器句柄 */
   if(OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->srvhp,(ub4)OCI_HTYPE_SERVER,(dvoid **)0))
   {
       OCIErrorGet((dvoid *)hdbc->errhp,1,&errcode,(text*)errBuf,(ub4)sizeof(errBuf),(ub4)OCI_HTYPE_ERROR);
       OCIHandleFree((dvoid *)hdbc->errhp,(ub4) OCI_HTYPE_ERROR);
       WriteLog("DoDbInit: OCIHandleAlloc allocate srvhp fail",NULL);
       WriteLog(errBuf,NULL);
       return NULL;
   }
   /* 申请服务环境句柄 */
   if(OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->svchp,(ub4)OCI_HTYPE_SVCCTX,(dvoid * *)0))
   {
       OCIErrorGet((dvoid *)hdbc->errhp,(ub4)OCI_HTYPE_ERROR);
       OCIHandleFree((dvoid *)hdbc->srvhp,(ub4) OCI_HTYPE_SERVER);
       OCIHandleFree((dvoid *)hdbc->errhp,(ub4) OCI_HTYPE_ERROR);
       WriteLog("DoDbInit: OCIHandleAlloc allocate svchp fail",NULL);
       return NULL;
   }
   /* 连接数据库 */
   if (OCIServerAttach(hdbc->srvhp,hdbc->errhp,dblink,(sb4)strlen((char*)dblink),(ub4)OCI_DEFAULT))
   {
       /* 释放环境句柄,系统自动释放在其下所分配的所有其它句柄 */
       OCIErrorGet((dvoid *)hdbc->errhp,(ub4)OCI_HTYPE_SERVER);
       OCIHandleFree((dvoid *)hdbc->svchp,(ub4)OCI_HTYPE_SVCCTX);
       OCIHandleFree((dvoid *)hdbc->errhp,(ub4)OCI_HTYPE_ERROR);
       OCIHandleFree((dvoid *)hdbc->envhp,(ub4)OCI_HTYPE_ENV);
       OsRetUB((UINT8*)hdbc);
       WriteLog("DoDbInit: OCIServerAttach fail",NULL);
       return NULL;
   }
   /* 设置服务环境的服务器属性 */
   OCIAttrSet((dvoid *)hdbc->svchp,(dvoid *)hdbc->srvhp,(ub4)0,(ub4)OCI_ATTR_SERVER,hdbc->errhp);

   /* 申请用户会话句柄 */
   OCIHandleAlloc((dvoid *)hdbc->envhp,(dvoid **)&hdbc->authp,(ub4)OCI_HTYPE_SESSION,(dvoid **)0);
   /* 设置会话所使用的用户帐户和密码 */
   if (OCIAttrSet((dvoid *)hdbc->authp,(dvoid *)uid,(ub4)strlen((char *)uid),(ub4)OCI_ATTR_USERNAME,hdbc->errhp))
   {
       OCIServerDetach(hdbc->srvhp,(ub4)OCI_DEFAULT);
       OCIHandleFree((dvoid *)hdbc->srvhp,(ub4)OCI_HTYPE_ERROR);
       OCIHandleFree((dvoid *)hdbc->authp,(ub4)OCI_HTYPE_SESSION);
       OCIHandleFree((dvoid *)hdbc->envhp,(ub4)OCI_HTYPE_ENV);
       OsRetUB((UINT8*)hdbc);
       WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_USERNAME] fail",NULL);
       return NULL;
   }
   if (OCIAttrSet((dvoid *)hdbc->authp,(dvoid *)pwd,(ub4)strlen((char *)pwd),(ub4)OCI_ATTR_PASSWORD,(ub4)OCI_HTYPE_ENV);
       OsRetUB((UINT8*)hdbc);
       WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_PASSWORD] fail",NULL);
       return NULL;
   }
   /* 建立数据库操作会话 */
   if ( (rc = OCISessionBegin(hdbc->svchp,hdbc->authp,(ub4)OCI_CRED_RDBMS,(ub4)OCI_DEFAULT)))
   {
       DoDbErrProc(hdbc->errhp,rc,"OCISessionBegin");
       OCIServerDetach(hdbc->srvhp,(ub4)OCI_HTYPE_ENV);
       OsRetUB((UINT8*)hdbc);
       WriteLog("DoDbInit: OCISessionBegin fail",NULL);
       return NULL;
   }

   /* 设置会话服务环境 */
   if (OCIAttrSet((dvoid *)hdbc->svchp,(dvoid *)hdbc->authp,(ub4)OCI_ATTR_SESSION,hdbc->errhp))
   {
       OCISessionEnd(hdbc->svchp,(ub4)0);
       OCIServerDetach(hdbc->srvhp,(ub4)OCI_HTYPE_ENV);
       OsRetUB((UINT8*)hdbc);
       WriteLog("DoDbInit:OCIAttrSet[OCI_ATTR_SESSION] fail",NULL);
       return NULL;
   }
   hdbc->stmthp = NULL;
   return hdbc;
}

下面对DoDbInit函数进行分析:
1)该函数的执行流程是这样的:第一步,申请所有句柄指针保存空间;第二步,创建OCI环境;第三步,申请错误句柄;第四步,申请服务器句柄;第五步,申请服务环境句柄;第六步,连接数据库;第七步,设置服务环境的服务器属性;第八步,申请用户会话句柄,第九步,设置会话所使用的用户帐户和密码;第十步,建立数据库操作会话;第十一步,设置会话服务环境。

2)实现以上十一步操作的函数均是OCI底层提供的(都以OCI打头)。不管哪一步操作执行失败,都会输出相关的日志,可供排查问题。

3)所有OCI主要句柄数据结构OCIHDBC的实现如下:

/* 所有OCI主要句柄数据结构 */
typedef struct
{
   OCIEnv        *envhp;              /* 环境句柄 */
   OCIError      *errhp;              /* 错误句柄 */
   OCIServer     *srvhp;              /* 服务器句柄 */
   OCISvcCtx     *svchp;              /* 服务环境句柄 */
   OCISession    *authp;              /* 会话句柄 */
   OCIStmt       *stmthp;             /* 语句句柄 */
}t_envctx;
typedef t_envctx  *OCIHDBC;           /* 方便使用定义OCIHDBC数据类型 */

以上不同的操作是对OCIHDBC结构体中对应的句柄赋值。

创建结果集操作函数DoRecInit的代码如下:

static CDbRecordset *DoRecInit()
{
   CDbRecordset *hRecordset;

   hRecordset = (CDbRecordset *)OsGetUB(sizeof(CDbRecordset));
   if (NULL == hRecordset)
   {
       WriteLog("DbInitRecordset: DbInitRecordset[0] fail",NULL);
       return NULL;
   }

   memset((void *)hRecordset,0,sizeof(CDbRecordset));
   return hRecordset;
}

下面对DoRecInit函数进行分析:
1)该函数的作用是初始化结果集,首先,该函数执行OsGetUB函数申请句柄指针空间,然后执行memset函数初始化结果集。

2)结果集结构体CDbRecordset的代码如下:

typedef struct CDbRecordsetTag
{
   void           *cmd; /* 命令缓冲区 */
   int            sqltype;                          /* 1:select 2:other*/
   int            colCount;                         /* 返回列数 */
   char          colfieldname[CDB_MAX_COL_NUM][40];/* 每列列名 */
   int           colfieldlength[CDB_MAX_COL_NUM];  /* 列名宽度 */
   int           pColWidth[CDB_MAX_COL_NUM];      /* 每列宽度 */
   int           pColType[CDB_MAX_COL_NUM];       /* 列类型 */
   char          pRecordBuf[CDB_MAX_COL_NUM][CDB_MAX_COL_WIDTH];/* 列数据 */
   int           pRetColWidth[CDB_MAX_COL_NUM];
   short         pRetIndicator[CDB_MAX_COL_NUM];
} CDbRecordset;

如果后续操作要从数据库中获取数据,那么这些数据就用CDbRecordset结构体来存储。

释放数据库连接操作函数DoDbFree的代码如下:

static void DoDbFree(OCIHDBC hdbc)
{
   if (NULL == hdbc)
   {
       return;
   }
   OCISessionEnd(hdbc->svchp,hdbc->errhp,hdbc->authp,(ub4)0);
   OCIServerDetach(hdbc->srvhp,(ub4)OCI_DEFAULT);

   OCIHandleFree((dvoid *)hdbc->srvhp,(ub4)OCI_HTYPE_SERVER);
   OCIHandleFree((dvoid *)hdbc->svchp,(ub4)OCI_HTYPE_SVCCTX);
   OCIHandleFree((dvoid *)hdbc->errhp,(ub4)OCI_HTYPE_ERROR);
   OCIHandleFree((dvoid *)hdbc->authp,(ub4)OCI_HTYPE_SESSION);   
   OCIHandleFree((dvoid *)hdbc->envhp,(ub4)OCI_HTYPE_ENV);

   OsRetUB((UINT8*)hdbc);
   hdbc = NULL;
}

从代码可以看出,该函数的功能是依次释放在DoDbInit函数中所申请的句柄指针。所有的以OCI开头的函数都是OCI底层提供的。

创建数据库连接函数CDbCreateDb的调用
在我们编写C代码创建数据库连接的时候,只需要将db_ora_oci_ux.h文件头包括进来,同时直接调用CDbCreateDb函数就可以了。
示例代码如下:

INT32 main(void)
{
   INT8 szDBServerName[50] = {0};
   INT8 szDBName[50]       = {0};
   INT8 szDBUser[50]       = {0};
   INT8 szDBPwd[50]        = {0};

   void *pDBHandle         = NULL;

   // 获取数据库各参数的值
   memcpy(szDBServerName,"db10_10_10_10",strlen("db10_10_10_10"));
   memcpy(szDBName,"dbp_166",strlen("dbp_166"));
   memcpy(szDBUser,strlen("dbp_166"));
   memcpy(szDBPwd,strlen("dbp_166"));

   // 连接数据库
   pDBHandle = CDbCreateDb("Oracle",szDBServerName,szDBName,szDBUser,szDBPwd);

   if (pDBHandle == NULL)    // 连接失败
   {
       printf("ConnectDB failed! ServiceName:%s,DBName:%s,User:%s,Pwd:%s",szDBPwd);

       return -1;
   }

   printf("ConnectDB success! ServiceName:%s,szDBPwd);

   return 0;
}

说明:
1)CDbCreateDb函数的五个输入参数分别是:数据库类型、数据库服务名、数据库名、用户名和密码。除了数据库类型之外,其他几个参数都和具体的数据库有关,需要在安装Oracle数据库的时候进行设置。

2)只有在数据库句柄分配成功(也就是数据库连接建立成功)的情况下,程序才能执行后续操作;如果数据库句柄分配失败,要及时找到失败原因。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读